PostgreSQL按日分区表SELECT查询耗时过长问题求助
让我帮你分析下这个PostgreSQL分区表查询慢的问题,从你的执行计划和表结构里能找到几个关键原因:
1. 主表cdr被无效扫描,耗时极高
你的执行计划显示,主表cdr的索引扫描耗时5019ms,读取了大量缓冲(shared hit=178464 read=708130),但最终返回0行。这说明主表cdr中大概率存储了大量数据——而继承式分区的主表应该仅作为模板,绝对不能存储实际业务数据,否则会导致规划器无意义地扫描主表,浪费大量资源。
修复步骤:
先确认主表是否有数据:
SELECT count(*) FROM ONLY cdr;
如果结果大于0,把主表的数据迁移到对应分区子表,再清空主表:
-- 示例:迁移2018-05-24的数据到对应子表 INSERT INTO cdr_2018_05_24 SELECT * FROM ONLY cdr WHERE cdr_date >= '2018-05-24' AND cdr_date < '2018-05-25'; -- 清空主表 DELETE FROM ONLY cdr;
后续要确保所有新数据都插入到对应子表(可以通过触发器实现自动路由,或者升级到PostgreSQL 10+的原生分区表)。
2. 约束排除(Constraint Exclusion)未正确生效
继承式分区依赖constraint_exclusion参数让规划器自动排除不需要扫描的分区。如果这个参数设置不当,规划器会扫描所有分区(包括主表),导致额外开销。
检查并配置:
查看当前参数值:
SHOW constraint_exclusion;
如果结果不是partition或on,设置为合理值:
-- 会话级临时生效 SET constraint_exclusion = partition; -- 永久生效(修改postgresql.conf后重启数据库) constraint_exclusion = partition
注意:如果主表有数据,约束排除不会自动排除主表,所以第一步清空主表是必要前提。
3. 子表cdr_2018_05_25走全表扫描的合理性
你的cdr_2018_05_25子表有cdr_date索引,但执行计划用了全表扫描。这是因为你的查询范围几乎覆盖了该子表的所有数据(子表CHECK约束是5月25日0点到26日0点左右,查询截止到25日23:59),PostgreSQL规划器认为全表扫描比索引扫描更高效——这其实是合理选择:全表扫描可以直接顺序读取数据,而索引扫描需要先读索引再回表读数据,对于全表范围查询来说开销更高。
如果担心是统计信息过时导致的判断偏差,可以更新统计信息后再验证:
ANALYZE cdr_2018_05_25;
额外优化建议
- 删除主表上的
ind_cdr_cdr_date索引:主表为空时,这个索引完全没用,还会误导规划器选择扫描主表索引。 - 考虑升级到PostgreSQL 10+原生分区表:原生分区表比继承式分区更高效,规划器对分区的支持更完善,也不需要手动维护触发器和约束排除规则。
内容的提问来源于stack exchange,提问作者N'bia

