为何PostgreSQL在WHERE含主键条件时不优先通过主键查询?
PostgreSQL查询计划:为何优先使用is_deleted索引而非主键索引?
问题场景
我在PostgreSQL数据库中执行了以下查询(courses表的id列为主键,is_deleted列(布尔类型)也建有索引):
SET enable_seqscan = OFF; EXPLAIN ANALYZE SELECT c.title FROM courses AS c WHERE c.id = 1 AND NOT (c.is_deleted) LIMIT 1
得到的查询计划如下:
Limit (cost=4.15..6.17 rows=1 width=318) (actual time=0.035..0.036 rows=1 loops=1) -> Bitmap Heap Scan on courses c (cost=4.15..6.17 rows=1 width=318) (actual time=0.033..0.034 rows=1 loops=1) Filter: ((NOT is_deleted) AND (id = 1)) Rows Removed by Filter: 2 Heap Blocks: exact=2 -> Bitmap Index Scan on ix_courses_is_deleted (cost=0.00..4.14 rows=2 width=0) (actual time=0.017..0.017 rows=6 loops=1) Index Cond: (is_deleted = false) Planning Time: 0.179 ms Execution Time: 0.068 ms
作为新手,我觉得这个计划不太合理——它优先通过is_deleted过滤,而非直接使用主键索引查询。直接执行Index Scan using pk_courses on courses难道不是更高效吗?
原因分析与解答
为什么优化器选了is_deleted索引?
- 统计信息过时:PostgreSQL的查询优化器依赖表的统计数据估算执行成本。从计划能看到,优化器预估
is_deleted = false的行数只有2行,但实际扫描出了6行,说明统计信息不准确。它误以为通过is_deleted索引缩小范围后再过滤id更划算,但实际并非如此。 - enable_seqscan的强制限制:你手动设置了
SET enable_seqscan = OFF,这会让优化器彻底排除全表扫描的选项,可能导致它退而求其次选择了并非最优的索引路径,毕竟成本估算的可选范围被人为缩小了。
主键索引确实更高效的原因
主键索引是唯一索引,通过id=1可以直接定位到唯一一行数据,之后只需要检查这行的is_deleted值是否符合条件,全程仅需一次索引查找+一次堆表访问,成本极低。而当前计划先扫描is_deleted索引得到6行,再回表过滤出符合id=1的行,还额外过滤掉2行,做了不少无用功。
如何让优化器选择主键索引?
- 更新表统计信息:执行
ANALYZE courses;,让PostgreSQL重新收集表的最新统计数据,优化器就能基于准确的信息做出更合理的选择。 - 取消enable_seqscan的强制设置:日常不要随意修改
enable_seqscan这类参数,让优化器根据表的实际数据分布自由选择最优执行计划。 - 强制指定索引(不推荐):如果万不得已,可以用索引提示强制优化器使用主键索引,示例如下:
EXPLAIN ANALYZE SELECT c.title FROM courses AS c WHERE c.id = 1 AND NOT (c.is_deleted) LIMIT 1 INDEX (pk_courses);
不过这种方法不建议常用,因为优化器在统计信息准确的前提下,通常能自动选出最优方案。
内容的提问来源于stack exchange,提问作者Arad
相关产品推荐
相关产品推荐

