Postgres 9.3.14升级至9.4.9后主键索引误用致CPU过高
这是Postgres从9.3升级到9.4时常见的规划器行为或统计信息偏差问题,我给你几个针对性的解决思路,按优先级排序:
1. 强制更新全表统计信息(最优先尝试)
Postgres 9.4对查询规划器的成本估算逻辑做了调整,如果升级后没有重新收集统计数据,规划器会基于旧的、不准确的字段分布信息做出错误决策。先执行以下命令更新目标表的统计信息:
ANALYZE VERBOSE a;
如果服务器负载允许,最好全库更新统计信息:
ANALYZE VERBOSE;
VERBOSE参数可以让你看到统计信息收集的细节,确认是否覆盖了col_text和col_int的字段分布情况——这两个字段的过滤选择性是规划器判断路径的关键。
2. 创建适配查询的复合索引(根治方案)
你的目标查询同时涉及col_text+col_int的过滤,以及a.id的排序,直接创建一个包含过滤字段+排序字段的复合索引,给规划器明确的最优路径:
CREATE INDEX idx_a_col_text_col_int_id ON a (col_text, col_int, id);
这个索引可以让查询直接定位到符合条件的行,并且行已经按id有序,完全避免了全索引扫描+过滤的低效操作,也不需要额外排序。
3. 调整规划器成本参数(优化决策逻辑)
如果不想新增索引,可以尝试调整规划器对排序和元组处理的成本评估,让它更倾向于选择“过滤+排序”的路径:
-- 会话级临时调整,测试效果 SET cpu_sort_cost = 0.001; SET cpu_tuple_cost = 0.01;
如果测试有效,可以把这些参数写入postgresql.conf永久生效,具体数值可以根据你的服务器CPU性能微调。
4. 临时禁用特定规划器选项(应急方案)
如果个别查询急需修复,又不想全局调整参数,可以在查询前临时覆盖规划器选项(不建议长期使用):
BEGIN; SET LOCAL enable_indexscan = off; SELECT a.id as a_id, b.col_id as col_id FROM a INNER JOIN b ON b.id = a.b_id WHERE (a.col_text = 'pqrs' AND a.col_int = 1) ORDER BY a.id ASC LIMIT 1; COMMIT;
这个方案适合临时救火,长期来看还是建议用前两种方法从根源解决。
5. 检查work_mem配置
Postgres 9.4对排序的内存使用逻辑有调整,如果work_mem设置过小,规划器会认为排序的内存成本过高,转而选择低效的主键索引扫描。可以适当调大该参数测试:
-- 会话级临时调整 SET work_mem = '64MB';
调整后重新查看执行计划,确认是否会切换到更优的过滤+排序路径。
补充说明
你提到移除ORDER BY后查询正常,这是因为此时规划器不需要考虑输出有序性,会优先选择过滤效率最高的索引。而带ORDER BY时,9.4的规划器错误评估了“主键索引有序扫描”和“过滤+排序”的成本差异——更新统计信息能让规划器获取更准确的字段选择性数据,创建复合索引则直接跳过了规划器的成本判断,给它最优路径。
内容的提问来源于stack exchange,提问作者Rahul Sharma

