组合索引必须严格匹配查询模式、锁行为与执行路径;where字段顺序不被优化器重排,须对齐索引最左连续等值链,否则索引失效。

高并发读写场景下,组合索引不是“加了就快”,而是必须让索引结构和查询模式、锁行为、执行路径三者咬合严丝合缝;否则轻则性能不升反降,重则放大锁竞争、拖垮QPS。
WHERE条件字段顺序必须严格对齐索引最左连续等值链
MySQL不会重排WHERE子句顺序去匹配索引。哪怕你写 WHERE status = 'paid' AND user_id = 123,而索引是 INDEX(user_id, status, created_at),status 字段也完全走不上索引——因为 user_id 没出现在WHERE中,最左匹配直接中断。
- 高频等值字段(如
user_id、tenant_id)必须放最左,哪怕它的区分度不如status -
IN视为等值条件,可放在等值段末尾,例如(user_id, status, product_type)能支持WHERE user_id = 123 AND status IN ('paid','shipped') - 一旦出现范围查询(
>、BETWEEN、LIKE 'abc%'),它右边所有字段只能用于过滤或排序,不再参与索引查找 - 跳字段(如只查
status和created_at)或反序(如ORDER BY created_at DESC, status ASC对应索引(status, created_at))都会导致无法免排序或索引失效
避免在高并发写入路径上触发间隙锁(Gap Lock)
在 RR 隔离级别下,WHERE user_id = 123 AND status = 'pending' 这类查询如果走的是 (user_id, status) 索引,InnoDB 可能对 status = 'pending' 对应的索引区间加间隙锁——尤其当该值尚未存在时,会锁住整个 user_id = 123 下所有可能插入 status = 'pending' 的位置,引发大量写阻塞。
- 若业务允许,将隔离级别降为
READ COMMITTED,可彻底关闭间隙锁(MySQL 8.0+ 支持) - 写密集场景慎用包含低区分度字段(如
status)的组合索引前缀,它容易扩大间隙锁覆盖范围 - 对写操作频繁的字段(如状态流转),考虑用单独的写优化索引,例如
INDEX(user_id, updated_at)专用于更新时间戳,避免和读场景索引混用
覆盖索引要兼顾读性能与回表成本
高并发读场景下,一次回表就是一次随机I/O,极易成为瓶颈。但盲目堆字段进索引又会拖慢写入、撑大缓冲池。
- 优先覆盖高频只读查询的全部字段,例如日志类表常查
SELECT user_id, action, created_at,就建INDEX(user_id, action, created_at) - 不要把主键显式加入联合索引——InnoDB二级索引叶子节点天然带主键,
INDEX(a, b)已隐含主键,加了反而浪费空间和维护开销 - TEXT/BLOB 类型不能作为索引字段,也不能放在联合索引非前导列参与排序;若查询需按其内容排序,得换方案(如预计算哈希或摘要字段)
- 用
EXPLAIN看Extra:出现Using index才算真正覆盖;Using where; Using index是常见误导项,只说明WHERE用了索引,并不保证不回表
验证索引是否真生效,不能只看key字段
EXPLAIN 输出里 key 不为空 ≠ 索引被有效利用。很多“假阳性”源于没盯住三个关键字段。
-
key_len值要对照字段类型算:比如user_id BIGINT占8字节,status VARCHAR(20)在utf8mb4下最多占80字节(20×4),若key_len = 8,说明只有user_id生效,status没命中 -
Extra中出现Using index condition表示用了ICP(索引条件下推),但仍有回表;Using filesort或Using temporary是严重信号,说明ORDER BY/GROUP BY没对齐索引 - 对线上慢查询,务必用
SELECT ... FOR UPDATE或真实业务SQL复现,避免用SELECT *测试——优化器对*和明确字段列表的决策可能完全不同
最易被忽略的一点:组合索引不是静态配置,而是随数据分布和查询频率动态演化的。上线后必须持续用 performance_schema.table_io_waits_summary_by_index_usage(MySQL 8.0+)或慢日志分析实际命中率,而不是靠经验拍脑袋保留或删除。











