加ORDER BY易引发锁等待,根本原因在于未走索引导致filesort、扫描行数激增、事务持锁时间延长及分页时锁范围扩大;应建匹配WHERE和ORDER BY最左前缀的联合索引,并优先采用游标分页。

ORDER BY 本身不加锁,但排序过程可能放大锁竞争——问题不在排序,而在它迫使 MySQL 扫描更多行、拖长事务、加剧行锁/间隙锁等待。
为什么加了 ORDER BY 就出现锁等待?
常见现象是:没 ORDER BY 时查询秒出,加上后不仅变慢,还频繁触发 Lock wait timeout exceeded 或被 SHOW PROCESSLIST 看到卡在 Updating/Sending data 状态。根本原因不是排序操作本身持锁,而是:
- 未走索引的
ORDER BY触发Using filesort,MySQL 需先读取大量数据到 sort_buffer 或磁盘临时文件,再排序——这期间持有读锁(如 RR 隔离级下的 consistent read snapshot)时间显著拉长 - 若查询带
FOR UPDATE或LOCK IN SHARE MODE,且WHERE + ORDER BY无法用联合索引覆盖,扫描范围扩大,锁住的行数或间隙数成倍增加 - 分页场景下
LIMIT 10000, 20配合ORDER BY created_at,即使有索引,MySQL 仍需定位前 10000 行并逐个加锁,锁等待自然堆积
如何验证是不是排序引发的锁扩散?
执行 EXPLAIN 后重点盯三处:
-
Extra出现Using filesort→ 排序没走索引,大概率已成锁瓶颈源头 -
key为NULL或非预期索引名 → 实际没用上你认为该生效的索引 -
rows值远大于实际返回行数(比如查 20 条却显示扫描 50000 行)→ 扫描膨胀,锁范围失控
再配合 SELECT * FROM performance_schema.data_locks 查当前锁持有情况,常能看到大量 RECORD 类型锁集中在排序字段值密集的区间。
建索引时必须匹配 WHERE 和 ORDER BY 的“连续最左前缀”
错误做法是单独给排序字段建索引,或把 ORDER BY 字段放在联合索引中间。正确顺序只有一条铁律:等值条件字段在前,范围条件字段紧接,排序字段必须紧跟其后且连续。
- 查询是
WHERE status = 1 AND deleted = 0 ORDER BY updated_at DESC→ 索引应为(status, deleted, updated_at),不是(updated_at, status, deleted) - 查询是
WHERE user_id = 123 ORDER BY score DESC, id ASC→ MySQL 8.0+ 可建(user_id, score DESC, id ASC);5.7 及以前只能建(user_id, score, id)并统一用ASC,否则score DESC会失效 - 若还需
SELECT name, avatar,直接扩展为覆盖索引:(user_id, score DESC, id, name, avatar),彻底避免回表带来的额外锁和 I/O
游标分页替代 OFFSET 是降低锁压力最有效的手段
当业务允许(如信息流、订单列表),用上一页最后一条记录的排序字段值做下一页条件,能直接砍掉扫描和锁住前 N 行的成本。
- 原写法:
SELECT * FROM orders WHERE status = 1 ORDER BY created_at DESC LIMIT 10000, 20→ 锁前 10020 行 - 改写为:
SELECT * FROM orders WHERE status = 1 AND created_at < '2026-05-10 12:34:56' ORDER BY created_at DESC LIMIT 20→ 只锁实际返回的 20 行 - 关键点:WHERE 中的
created_at < ?必须能命中索引最左前缀,且该值来自上一页结果集的最小created_at,不能错位
这个改动对锁等待的改善是数量级的,但需要前端配合传递游标值,且无法跳转任意页——这点常被忽略,上线前务必确认产品是否接受。


















