PostgreSQL统计信息正确时Explain Analyze为何误估行数
问题成因
这个估算偏差本质是PostgreSQL 13优化器的两个固有逻辑限制共同导致的,42这个看似无依据的估算值完全可以按照优化器的计算逻辑复现:
- 等价条件传递的边界限制
当你直接用p.id=53做过滤时,优化器的等价类推导逻辑会直接生成c.id_ref=53的常量过滤条件,扫描child_tb_ten_6分区时可以直接命中id_ref列的高频值(MCV)统计——id_ref=53在该分区的出现频率是1,因此直接算出准确的475759行。
但当你用p.descr='xxx'做过滤时,优化器虽然能算出父表过滤后仅返回1行、对应唯一的p.id值,却不会把这个未知的id常量值传递给子表的id_ref列做MCV匹配。这个限制的核心是:PostgreSQL不会自动推导跨表、跨列的1:1映射关系,哪怕父表的descr列有唯一索引,优化器也不会在计划阶段把descr对应的id值查出来,再代入子表做行数估算。 - 未知值的选择率假设+并行估算修正
当优化器无法确定子表id_ref要匹配的具体常量值时,会默认假设这个待匹配的值不属于已知的高频值集合,而是落在剩余的低频值区间,按平均低频值选择率计算:- 全局
child_tb的id_ref列统计信息中,前两个高频值(53、4)的频率和为0.7968+0.0307≈0.8275,剩余59个值总频率为0.1725,平均每个低频值的选择率约为0.1725/59≈0.0029 - 异常执行计划选择了2个并行worker,算上主进程共3个并行扫描进程,单进程分配到的
child_tb_ten_6扫描行数约为475759/3≈158586 - 单进程的join估算行数经过join去重修正后约为18行,3个进程的估算行数加总后再经过并行成本修正,就得到了总估算值42行。
- 全局
你做的物化CTE测试得到7799的估算值,是因为物化CTE阻断了优化器的跨表条件推导,优化器不会触发「排除高频值」的逻辑,直接按平坦分布假设(分区总行数/全局id_ref去重值数量=475759/61≈7799)计算,反而得到了量级更合理的结果。
可行的解决方法
- 最稳妥的方案是调整查询写法,先通过descr查出对应的父表id,再用id做子表过滤,也就是你第一次测试的写法,避免让优化器自己做跨列的值推导。
- 可以升级到PostgreSQL 14及以上版本,该版本修复了分区表并行join场景下的MCV匹配逻辑,对于唯一索引侧过滤后单值的场景,能正确触发常量传递得到准确的行数估算。
- 若不方便改语句或升级版本,可以尝试在
child_tb的(id_tenant, id_ref)列上创建扩展统计信息,一定程度上修正联合分布的选择率估算,但该方法对跨表条件传递的场景效果有限。
内容的提问来源于stack exchange,提问作者milosla1
相关产品推荐
相关产品推荐

