mysql 5.7不支持联合索引中desc排序,优化器忽略方向导致全扫或filesort;8.0+虽支持但要求order by方向与索引定义严格一致,否则触发filesort,扩大间隙锁范围。

为什么ORDER BY DESC会扩大间隙锁范围
MySQL 5.7 默认不支持联合索引中对排序字段使用 DESC,即使你建了 (shop_id, created_at DESC),优化器也会忽略 DESC 含义,退化为全扫描或 filesort;而 MySQL 8.0+ 虽支持方向声明,但若查询的 ORDER BY 方向与索引定义不严格一致(比如索引是 created_at DESC,SQL 写成 ORDER BY created_at ASC),仍会触发 Using filesort,进而导致扫描行数暴增、间隙锁范围意外扩大。
更关键的是:InnoDB 的间隙锁(gap lock)加在「索引扫描路径所触及的所有间隙」上。一旦排序无法走索引,优化器可能从聚簇索引开头/结尾全扫,把本不该锁的区间也纳入锁范围——比如你只查 shop_id = 1001,结果却锁住了 (0, 5), (5, 10), ..., (999, 1001) 这一整段。
用EXPLAIN + optimizer_trace验证是否真走了倒序索引
光看 EXPLAIN 的 key 字段不够,它只告诉你用了哪个索引,不告诉你是否真正利用了 DESC 顺序。必须配合 optimizer_trace 看执行路径:
- 先开启:
SET SESSION optimizer_trace="enabled=on,one_line=off"; - 执行你的
SELECT ... ORDER BY created_at DESC查询 - 查
SELECT * FROM information_schema.OPTIMIZER_TRACE;
重点看 steps 中的 "using_filesort": false 和 "using_index_for_order_by": true 是否同时成立。如果出现 "using_filesort": true,说明倒序索引没生效,锁范围已经失控。
RR隔离级别下倒序索引失效的真实锁表现
在 REPEATABLE READ 下,哪怕你只查一行,只要排序没走索引,InnoDB 就可能对整个扫描路径加 next-key lock(记录锁 + 间隙锁)。典型现象包括:
-
INSERT新记录被莫名阻塞,比如插入id = 12却卡住,而你查的是WHERE shop_id = 1001 ORDER BY created_at DESC -
SHOW ENGINE INNODB STATUS\G中看到大量lock_mode X locks gap before rec,且index名称不是你预期的倒序索引 - 事务长时间持有锁,
INFORMATION_SCHEMA.INNODB_TRX中TRX_ROWS_LOCKED数值远超实际返回行数
这时候别急着调大 innodb_lock_wait_timeout,先确认是不是倒序索引没被用上——因为问题不在等待时间,而在锁本身就不该存在那么大范围。
修复倒序索引锁扩大的三个硬性条件
不是建了带 DESC 的索引就万事大吉。以下三点缺一不可:
- MySQL 版本必须 ≥ 8.0.1(5.7 完全无视
DESC声明) - 索引定义必须与
ORDER BY字段和方向**逐字匹配**:INDEX idx_shop_created (shop_id, created_at DESC)对应WHERE shop_id = ? ORDER BY created_at DESC -
WHERE中的等值条件必须覆盖索引最左前缀,且不能有函数/表达式干扰(如WHERE ABS(shop_id) = 1001会让整个索引失效)
最容易被忽略的是第三点:业务代码里一个 LOWER() 或隐式类型转换,就能让精心设计的倒序索引彻底作废,锁范围瞬间回到“全表扫描级”。











