复合索引字段过多且顺序不当会导致锁范围意外扩大。例如索引(status, user_id, created_at, amount)仅用WHERE status = 'pending'时,会扫描所有匹配行;应将高选择性等值字段置左、范围字段置右,并通过EXPLAIN的key_len和rows验证是否有效截断。

复合索引列太多会导致锁范围意外扩大
复合索引字段堆砌过多,尤其是把非过滤型字段(如 created_at、updated_at)或低选择性字段(如 status)放在前面,会让 MySQL 无法高效截断扫描,最终锁住远超预期的行数。比如 INDEX idx_all (status, user_id, created_at, amount),当只用 WHERE status = 'pending' 查询时,InnoDB 会扫描所有 status = 'pending' 的记录——哪怕实际只需其中最新一条。
如何判断复合索引是否“过宽”
关键看 EXPLAIN 输出中 rows 和 key_len 是否明显偏大,以及是否出现 Using where; Using index 但依然锁大量行:
-
key_len显著大于必要字节数(例如user_id是BIGINT,仅需 8 字节,却显示 24+)→ 索引被“全量使用”,未有效截断 -
rows值远高于 WHERE 条件实际匹配行数 → 扫描范围失控 -
Extra出现Using index condition但锁等待频繁 → ICP 生效了,但扫描起点太宽,仍加了大量锁
精简复合索引的实操原则
目标不是“删字段”,而是让索引结构严格匹配高频查询的 **最左前缀 + 过滤优先级**:
- 把高选择性、高频等值过滤的字段放最左:比如
order_no(唯一)、user_id(区分度高) - 范围查询字段(如
created_at > '2024-01-01')必须放在等值字段之后,且只放一个;放两个就会让后续字段失效 - 避免把
status这类低选择性字段放最左,除非你总配合user_id一起查;否则单独查status就等于扫半张表 - 覆盖查询字段(
SELECT列)可以加在末尾,但别为了“覆盖”而牺牲最左过滤能力
容易被忽略的细节:ORDER BY 和 LIMIT 依赖索引顺序
如果业务常用 SELECT ... FOR UPDATE ORDER BY created_at DESC LIMIT 1,那 created_at 必须出现在索引中,且位置要能让排序走索引——不能只靠 WHERE user_id = ? 就建 (user_id),得建 (user_id, created_at)。否则 MySQL 可能先按 user_id 找出几百行,再内存排序取第一条,过程中对这几百行都加了锁。
真正难处理的,是那些看似“合理”的复合索引,因为字段顺序错了一位,就把锁范围从 1 行拉到上千行。验证时别只看执行计划是否走索引,得结合 SHOW ENGINE INNODB STATUS 看实际锁了哪些记录。


















