降序索引本身不直接导致死锁,但会破坏加锁顺序一致性;当多个事务用不同ORDER BY方向访问同一主键范围时,InnoDB按B-Tree物理遍历顺序加锁,ASC从左到右、DESC从右到左,易形成环形等待。

降序索引本身不直接导致死锁,但会破坏加锁顺序一致性
MySQL 8.0 支持 INDEX (a DESC, b ASC) 这类显式降序索引,但它在并发 DML 中的危险点不在“索引结构”,而在于:当多个事务用不同排序方向访问同一组主键范围时,InnoDB 内部加锁顺序可能不一致。比如事务 A 执行 SELECT * FROM t WHERE a > 10 ORDER BY a ASC FOR UPDATE,事务 B 执行 SELECT * FROM t WHERE a > 10 ORDER BY a DESC FOR UPDATE —— 即使都走同一个降序索引,扫描方向相反,加锁行顺序就可能倒过来,形成环形等待。
关键不是「用了 DESC」,而是「ORDER BY 方向与索引定义方向混用 + 多事务交叉访问」。InnoDB 按索引 B-Tree 的物理遍历顺序加锁,ASC 遍历从左到右,DESC 遍历从右到左,若两个事务扫描同一区间但方向相反,极易让锁顺序错位。
ORDER BY 与索引定义方向不匹配时,优化器可能放弃使用索引排序
即使建了 INDEX (status DESC),如果写的是 ORDER BY status ASC,MySQL 通常仍能利用该索引(因 B-Tree 可双向遍历),但代价是额外排序开销;而更危险的是:当 WHERE 条件配合 ORDER BY 出现方向冲突,优化器可能退化为 filesort,进而导致全表扫描或非确定性加锁顺序。
-
EXPLAIN中type为index或ALL,说明没走索引定位,而是靠扫描+排序 → 加锁范围不可控 - 隐式转换(如
WHERE status = 'active'但 status 是 ENUM)会让索引失效,ORDER BY 再怎么写也白搭 - 联合索引中部分字段方向不一致(如
INDEX (x ASC, y DESC)),但查询只用WHERE x = ? ORDER BY y ASC,此时 y 的 DESC 定义无法支持 ASC 排序,优化器大概率放弃索引排序能力
并发更新多行时,ORDER BY 缺失或方向混乱是死锁高发场景
批量更新必须显式加 ORDER BY id ASC,这是避免死锁最廉价、最有效的手段。降序索引常让人误以为“既然索引是 DESC,那 ORDER BY DESC 更自然”,结果两个事务一个用 ORDER BY id ASC,一个用 ORDER BY id DESC,哪怕数据集完全一样,加锁顺序也相反。
MySQL 数据库管理技能,能够通过自然语言查询和管理数据库,支持 SELECT/INSERT/UPDATE/DELETE、表管理、备份恢复等操作。当用户提及 MySQL、数据库查询、建表或数据备份时使用此技能。
- 所有涉及
UPDATE ... WHERE ... IN (...)或子查询更新的语句,只要影响多行,就必须补ORDER BY - 不要依赖 InnoDB 对
IN列表的内部排序:它只按主键升序加锁,但如果你的 WHERE 条件没走主键索引,这个保证就失效 - 应用层分页更新(如先查 ID 列表再循环更新)必须确保两次查询结果顺序绝对一致 —— 不加
ORDER BY就是埋雷
唯一索引 + 降序定义 + 并发 INSERT ON DUPLICATE KEY UPDATE 易触发间隙锁竞争
当唯一索引定义为 UNIQUE (tenant_id DESC),而并发执行 INSERT INTO t VALUES (...) ON DUPLICATE KEY UPDATE ... 时,InnoDB 在查找冲突记录阶段,会基于索引顺序扫描可能插入的位置。DESC 索引的间隙划分方式与 ASC 不同,多个事务同时尝试插入相邻值(如 tenant_id=1002 和 1001),可能在不同间隙上加插入意向锁,又互相等待对方释放临键锁,最终形成死锁。
这类问题在日志里常表现为:*** (1) WAITING FOR THIS LOCK TO BE GRANTED: x-lock gap before rec insert intention 和 *** (2) HOLDS THE LOCK(S): s-lock next-key 交替出现。根本解法不是删掉 DESC,而是统一用 ASC 定义唯一索引,并在业务逻辑中控制插入顺序。










