index scan 退化为 seq scan 是性能下降的直接原因,源于优化器基于过期统计、隐式类型转换或选择率变化等判断索引代价更高;需用 explain analyze 验证实际执行行为并检查过滤行数与缓冲区读取。

执行计划里 Index Scan 变成了 Seq Scan,就是性能掉下去的直接原因
PostgreSQL(或 PostgreSQL 兼容引擎)中,Index Scan 突然退化为 Seq Scan,几乎总是查询变慢的元凶。这不是偶然,而是优化器基于统计信息和代价估算后“主动放弃”索引的结果。关键不是“索引没了”,而是“优化器觉得走索引更贵”。常见诱因包括:表数据量突增、WHERE 条件选择率变化、统计信息过期、隐式类型转换导致索引无法下推。
EXPLAIN (ANALYZE, BUFFERS) 必须带 ANALYZE 才能看到真实行为
只用 EXPLAIN 看的是预估计划,而 EXPLAIN ANALYZE 会真正执行语句并返回实际耗时、扫描行数、缓冲区命中情况。尤其要注意两处输出:Actual Rows 和 Rows Removed by Filter。如果后者数值巨大(比如扫描了 100 万行,过滤掉了 99.9 万),说明谓词没有下推到索引层,很可能用了全表扫描或索引只用于定位但过滤靠 CPU 完成。
实操建议:
- 在业务低峰期运行
EXPLAIN (ANALYZE, BUFFERS) SELECT ...,避免干扰线上 - 对比前后两次执行计划,重点看
Plan Node类型、Startup Cost和Total Cost是否跳升 - 检查
Buffers: shared hit=xxx read=yyy——read高说明磁盘 IO 暴增,常伴随Seq Scan
为什么 WHERE col = '123' 没走索引?先查隐式转换
最隐蔽也最高发的问题:字段是 integer 类型,但查询写成 WHERE id = '123'(字符串字面量)。PostgreSQL 会尝试把字符串转成整数,但这个转换发生在运行时,导致索引无法使用 —— 因为索引是按整数建的,而查询条件被包装成了 CAST('123' AS integer),优化器无法匹配索引表达式。
验证方式:
- 用
\d table_name查列类型 - 在
EXPLAIN输出里找Filter:行,如果看到类似(col = ('123')::integer)就是隐式转换证据 - 修复只需统一类型:
WHERE id = 123(去掉引号)
同理适用于 timestamp 字段用字符串比较、jsonb 字段用 -> 而非 ->> 导致类型不匹配等场景。
VACUUM ANALYZE 不是“重启大法”,但往往是最快见效的干预
当表刚经历大批量 INSERT/UPDATE/DELETE,统计信息滞后会导致优化器误判索引价值。此时 ANALYZE 单独执行就能刷新列分布直方图;如果还伴有大量死元组(如未清理的更新旧版本),则需 VACUUM ANALYZE 一起跑。
注意点:
-
ANALYZE不锁表,但会短暂持有ShareUpdateExclusiveLock,一般不影响读写 - 不要对大表盲目
ANALYZE,可指定列:ANALYZE table_name (col1, col2)加速 - 若频繁出现统计失真,考虑调高
default_statistics_target(默认 100,可设为 500),再配合定期ANALYZE
真正难处理的是多列组合条件下的选择率误估,这时可能需要创建扩展统计(CREATE STATISTICS)或函数索引,但那是另一层复杂度了。










