PostgreSQL负n_distinct值行为与文档描述不符的技术疑问
PostgreSQL负
n_distinct值的行为解析:表规模不影响单值估计行数的原因 核心逻辑拆解
PostgreSQL对负n_distinct的定义包含两个关键规则:
当设置为大于等于-1的负值时,ANALYZE会假设列中非空不同值的数量与表大小呈线性关系;具体数值为估计表大小乘以给定数值的绝对值。例如,值为-1表示列中所有值均唯一,值为-0.5表示每个值平均出现两次。
结合查询规划阶段的行数计算逻辑,可推导出单值估计行数的公式:
- 不同值数量 = 表行数 × |
n_distinct| - 单值估计行数 = 表行数 ÷ 不同值数量
将两个公式合并后,表行数会被抵消:单值估计行数 = 表行数 ÷ (表行数 × |n_distinct|) = 1/|n_distinct|
这就是表规模变化不影响单值估计行数的原因——最终结果仅由n_distinct的绝对值决定,和表行数无关。
对应测试验证
你的测试结果完全匹配上述逻辑:
- 当
n_distinct=-0.5时,1/0.5=2,所以无论表是10000行还是510000行,查询select * from tab where a = 5的估计行数均为2,和文档中“每个值平均出现两次”的描述完全一致。 - 当
n_distinct=-0.001时,1/0.001=1000,两种表规模下估计行数均为1000,符合计算结果。
对比正数n_distinct的场景(如设为20),此时不同值数量被固定为20,单值估计行数为10000/20=500,这是固定不同值数量的静态计算,和负数的动态比例逻辑本质不同。
测试代码复现
create table tab(a int); insert into tab select * from generate_series(1,10000); analyze tab; explain select * from tab where a = 5; -- Correctly estimates 1 row --Seq Scan on tab (cost=0.00..170.00 rows=1 width=4) -- Filter: (a = 5) alter table tab alter column a set (n_distinct = 20); analyze tab; explain select * from tab where a = 5; -- Correctly estimates 1000/20 = 500 rows --Seq Scan on tab (cost=0.00..170.00 rows=500 width=4) -- Filter: (a = 5) alter table tab alter column a set (n_distinct = -0.5); analyze tab; explain select * from tab where a = 5; --Seq Scan on tab (cost=0.00..170.00 rows=2 width=4) -- Filter: (a = 5) alter table tab alter column a set (n_distinct = -0.1); analyze tab; explain select * from tab where a = 5; --Seq Scan on tab (cost=0.00..170.00 rows=10 width=4) -- Filter: (a = 5) alter table tab alter column a set (n_distinct = -0.001); analyze tab; explain select * from tab where a = 5; -- Seq Scan on tab (cost=0.00..170.00 rows=1000 width=4) -- Filter: (a = 5) insert into tab select * from generate_series(10001,500000); alter table tab alter column a set (n_distinct = -0.5); analyze tab; explain select * from tab where a = 5; --Gather (cost=1000.00..6889.67 rows=2 width=4) -- Workers Planned: 1 -- -> Parallel Seq Scan on tab (cost=0.00..5889.47 rows=1 width=4) -- Filter: (a = 5) alter table tab alter column a set (n_distinct = -0.001); analyze tab; explain select * from tab where a = 5; --Gather (cost=1000.00..6989.47 rows=1000 width=4) -- Workers Planned: 1 -- -> Parallel Seq Scan on tab (cost=0.00..5889.47 rows=588 width=4) -- Filter: (a = 5)
内容的提问来源于stack exchange,提问作者Peter
相关产品推荐
相关产品推荐

