我们有一个包含 6M 条记录的表。它包含分层实体,即每个实体都有一个到父级的链接 - Parent_id 字段。
95% 的行是顶级实体,即它们有一个空的 parrent_id 字段(空字符串)。
单个实体的递归查询(以获取其所有子实体)存在性能问题。查询规划器错误地估计子项计数,并且更喜欢通过 parrent_id 字段进行“顺序扫描”而不是“索引扫描”。 我认为规划器是这样工作的,因为我们的 parrent_id 值分布不均匀。 pg_stats 显示: 最常见的值:'' n_distinct:73(事实上,几乎所有子实体(表的 5%)都有不同的 parrent_id)
分析器似乎没有获取足够的行。然而,当我们将空字符串值更改为 NULL 时,一切都会变得更好。规划器使用索引。
我不是专家,我想知道这种 NULL 技巧是否是常见做法,或者这只是一个意外,分析器进程可能会随着时间的推移破坏统计数据,导致性能下降?
最佳答案
我猜你遇到的情况是这样的:
CREATE TABLE rare (x integer);
INSERT INTO rare
SELECT CASE WHEN random() < 0.05
THEN i
ELSE 0
END
FROM generate_series(1, 100000) AS i;
ANALYZE rare;
SELECT null_frac, n_distinct, most_common_vals, most_common_freqs
FROM pg_stats
WHERE tablename = 'rare'
AND attname = 'x';
null_frac | n_distinct | most_common_vals | most_common_freqs
-----------+------------+------------------+-------------------
0 | 1471 | {0} | {0.9526333}
(1 row)
SELECT count(DISTINCT x) FROM rare;
count
-------
4904
(1 row)
对于 NULL 值,它看起来像这样:
TRUNCATE rare;
INSERT INTO rare
SELECT CASE WHEN random() < 0.05
THEN i
ELSE NULL
END
FROM generate_series(1, 100000) AS i;
ANALYZE rare;
SELECT null_frac, n_distinct, most_common_vals, most_common_freqs
FROM pg_stats
WHERE tablename = 'rare'
AND attname = 'x';
null_frac | n_distinct | most_common_vals | most_common_freqs
------------+--------------+------------------+-------------------
0.95093334 | -0.049066663 | |
(1 row)
不同之处在于,在第二种情况下,PostgreSQL 意识到非空值几乎是唯一的(它们实际上是唯一的),因此它将不同值的数量表示为负比率。负数,将其与正常情况区分开来,以及比率,因为如果数据发生变化,比率仍然更准确。
PostgreSQL 仅采用表的样本来计算统计信息,因此不同值的绝对计数(本例中为 1471)当然远远超出了标准,但分数(当使用分数大小计算时!)是相当准确。
在第一种情况下,PostgreSQL 会估计
SELECT DISTINCT x FROM rare;
有 1471 个结果行,而在第二种情况下,它将估计 100000 * 0.049066663 ≈ 4907
行。
现在这解释了您所看到的内容,但是您可以采取哪些措施来改进估计?
答案是采取更大的样本:
ALTER TABLE rare ALTER x SET STATISTICS 1000;
ANALYZE rare;
然后 PostgreSQL 将采用更大的样本并得出更准确的估计。
关于postgresql:为什么空值比空字符串提高了性能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65734658/