where a = ? and b = ? 用不上 (a, b) 索引是因为未满足最左前缀匹配原则:必须从索引最左列开始连续使用,且首列需为确定等值条件;若跳过a直接查b,或a为范围/函数/null,则索引失效。

为什么 WHERE a = ? AND b = ? 用不上 (a, b) 索引?
不是索引建错了,是查询条件没触发最左前缀匹配。MySQL 多列索引生效前提是:从左到右连续使用索引列,中间不能跳过。比如索引是 (status, created_at, user_id),但查询只写了 WHERE user_id = 123,那整个索引就失效。
常见错误现象:EXPLAIN 显示 type: ALL 或 key: NULL,哪怕字段上明明建了索引。
- 必须保证 WHERE 条件中,最左边的列(如
status)有确定值(非IS NULL、非范围、非函数包裹),后续列才能被索引下推 - 范围查询(
>、BETWEEN、LIKE 'abc%')会截断索引,WHERE status = 'active' AND created_at > '2024-01-01' AND user_id = 123实际只用到前两列 -
OR条件大概率让整条索引失效,别指望优化器能聪明地拆解
区分度低的字段(如 gender、is_deleted)该不该放索引最左?
不该——除非它能显著过滤数据。区分度(cardinality)低于 5% 的字段,单独建索引几乎没意义;放在联合索引最左,反而拖慢所有依赖该索引的等值查询。
使用场景:你有一张订单表,order_status 只有 4 个值(pending / paid / shipped / cancelled),但 user_id 有百万级唯一值。此时 (order_status, user_id) 不如 (user_id, order_status)。
- 用
SHOW INDEX FROM table_name查看Cardinality列,对比字段实际唯一值数量 - 执行
SELECT COUNT(DISTINCT col) / COUNT(*) FROM table_name,结果低于 0.03 就算低区分度 - 如果必须查低区分度字段,优先考虑加在索引右侧,作为“筛选后排序/分组”的辅助列
如何验证新索引是否真被用了?别只信 EXPLAIN
EXPLAIN 显示 key 非空,不代表查询真的快——可能走了索引但回表太多,或者扫描了大量无效索引页。
实操建议:用 SELECT ... INTO DUMPFILE 或慢日志 + pt-query-digest 观察真实执行时间与扫描行数(rows_examined)。
- 开
performance_schema,查events_statements_summary_by_digest表,比对avg_timer_wait和avg_rows_affected - 用
SELECT * FROM table WHERE ... FOR UPDATE测试写锁竞争,低区分度索引容易导致锁范围过大 - 注意
Handler_read_next和Handler_read_rnd_next的差值:后者飙升说明回表严重,索引覆盖不足
什么时候该放弃多列索引,改用覆盖索引或冗余字段?
当高频查询固定返回几个字段,且其中包含低区分度列时,硬凑多列索引不如直接建覆盖索引,甚至加计算字段。
例如:频繁执行 SELECT id, title, status FROM posts WHERE status = 'published' ORDER BY created_at DESC LIMIT 20,而 status 区分度极低。
- 建覆盖索引
(status, created_at, id, title),避免回表(注意字段顺序:等值 → 排序 → 查询列) - 如果
created_at经常被格式化(如DATE(created_at)),考虑新增生成列created_date DATE AS (DATE(created_at)) STORED并对其建索引 - 不要为每个查询组合都建索引——3 列组合最多 6 种顺序,但真正高频路径通常不超过 2 种,先抓
slow_log里 top 3 查询
最易被忽略的是索引统计信息陈旧。执行 ANALYZE TABLE table_name 后再测,否则优化器可能基于错误基数选错索引。尤其是大批量 INSERT/DELETE 后,这个动作不是可选项。











