索引选择性低、复合索引顺序不当、null值处理不当及统计信息过期均会导致优化器弃用索引;应计算选择性(

索引选择性低会导致全表扫描
当 WHERE 条件列的重复值太多(比如 status 只有 'active'/'inactive' 两种),MySQL 或 PostgreSQL 可能直接放弃走索引,改用全表扫描——因为读索引再回表的成本比直接扫一遍还高。
实操建议:
- 用
SELECT COUNT(DISTINCT col) / COUNT(*) FROM table算选择性,结果低于 0.01(即 1%)就危险 - 避免给
gender、is_deleted这类低基数字段单独建索引 - 如果必须查这类字段,优先考虑组合索引,把低选择性列放在后面,比如
(created_at, status)而不是(status, created_at)
复合索引顺序决定能否命中
数据库只能按索引定义的**最左前缀**匹配条件。写成 INDEX (a, b, c),那么 WHERE a = ? AND b = ? 能用上,但 WHERE b = ? AND c = ? 就完全失效。
实操建议:
- 高频等值查询字段放最左,范围查询(
BETWEEN、>)字段放中间,排序/分组字段放最后 - 如果有
WHERE user_id = ? AND created_at > ? ORDER BY updated_at DESC,索引应为(user_id, created_at, updated_at) - 不要为了“看起来全面”堆砌字段,每多一个字段都增加索引体积和写入开销
NULL 值让索引失效的隐蔽情况
某些场景下,IS NULL 或 IS NOT NULL 不走索引,尤其在 MySQL 5.7 之前;PostgreSQL 对 IS NULL 支持较好,但若字段允许 NULL 且实际 NULL 值很多,选择性也会被拉低。
实操建议:
- 建表时尽量用
NOT NULL+ 默认值(如0、''),除非业务真需要区分“未设置”和“空” - 查
IS NULL前先确认执行计划:EXPLAIN SELECT ...,看key列是否为空 - 如果必须支持 NULL 查询且性能差,可加生成列(MySQL 5.7+ 的
STORED列)或函数索引(PostgreSQL 的CREATE INDEX ON t ((col IS NULL)))
统计信息过期会让优化器选错索引
PostgreSQL 的 ANALYZE、MySQL 的索引统计(innodb_stats_persistent 相关)如果不更新,优化器可能基于错误的选择性估算,跳过本该用的索引,甚至选错连接顺序。
实操建议:
- 大表批量导入后立刻执行
ANALYZE table_name(PostgreSQL)或ANALYZE TABLE table_name(MySQL) - MySQL 中检查
information_schema.STATISTICS表的SEQ_IN_INDEX和CARDINALITY,确认数值是否合理 - 不要依赖自动统计——某些 OLAP 场景下,自动采样率太低,
innodb_stats_sample_pages可调高,但别盲目设成 2000+
真正卡住性能的,往往不是没建索引,而是索引建了但优化器不敢用、或者用了却绕远路。验证方式永远只有两个:看 EXPLAIN,再看真实 EXPLAIN ANALYZE(PostgreSQL)或 EXPLAIN FORMAT=JSON(MySQL)里的实际行数和时间分布。











