MySQL中识别锁等待应查INNODB_LOCK_WAITS(8.0.1+)或解析SHOW ENGINE INNODB STATUS(5.7-),注意RUNNING状态不等于无锁、NULL waiting_query表示非SQL层等待,innodb_lock_wait_timeout仅控制超时而非优先级,InnoDB无事务抢占机制,死锁时回滚undo量更小的事务。

如何识别正在发生的锁等待
MySQL 中出现性能抖动或查询卡住,大概率是事务在等锁。最直接的判断方式是查 information_schema.INNODB_TRX 和 information_schema.INNODB_LOCK_WAITS:
SELECT r.trx_id waiting_trx_id, r.trx_mysql_thread_id waiting_thread, r.trx_query waiting_query, b.trx_id blocking_trx_id, b.trx_mysql_thread_id blocking_thread, b.trx_query blocking_query FROM information_schema.INNODB_LOCK_WAITS w JOIN information_schema.INNODB_TRX b ON b.trx_id = w.BLOCKING_TRX_ID JOIN information_schema.INNODB_TRX r ON r.trx_id = w.REQUESTING_TRX_ID;
注意:INNODB_LOCK_WAITS 在 MySQL 8.0.1 之后才稳定可用;5.7 及更早版本需依赖 SHOW ENGINE INNODB STATUS 解析 LOCK WAIT 行,但输出不易解析且无实时性。
常见误判点:
- 看到
trx_state = RUNNING不代表没锁——它只表示事务没提交,实际可能正持锁阻塞他人 -
trx_started时间远早于当前时间,说明该事务长期未提交,极可能是“长事务”,应优先 kill - 若
waiting_query是NULL,说明等待发生在非 SQL 层(如主键查找、唯一约束校验),需结合trx_operation_state判断
innodb_lock_wait_timeout 能否控制事务优先级
不能。innodb_lock_wait_timeout 只是设置单个语句等待锁的**超时秒数**,超时后报错 ERROR 1205 (40001): Deadlock found when trying to get lock... 或 ERROR 1205 (40001): Lock wait timeout exceeded...,它不参与锁争抢时的调度决策。
MySQL InnoDB **没有事务优先级抢占机制**:不会因为某个事务启动早、线程 ID 小、或用了 SET SESSION TRANSACTION ISOLATION LEVEL 就优先获得锁。锁分配完全基于“先到先得 + 死锁检测后回滚”原则。
真正影响“谁被杀”的只有死锁检测器的判定逻辑:
- InnoDB 总是回滚 undo log 量更小 的那个事务(即修改行数更少、产生的回滚段更少)
- 这个选择不可配置,也与事务执行时间、客户端 IP、SQL 类型无关
- 想降低被回滚概率?尽量让写操作批量、紧凑,避免在事务中穿插大量 SELECT 或网络 I/O
用 low_priority_updates 和 innodb_thread_concurrency 控制并发行为
这两个参数常被误认为能“提升事务优先级”,实际作用非常有限,且多数场景已不推荐使用:
-
low_priority_updates=ON:仅对INSERT/UPDATE/DELETE生效,使其让位于SELECT—— 但这会加剧写入延迟,且在读多写少系统中反而放大锁等待 -
innodb_thread_concurrency:设为非 0 值(如 16)会启用 InnoDB 内部线程调度器,但该机制在 MySQL 5.6.2+ 后默认禁用(值为 0),因实测效果不佳且增加调度开销 - 真正可控的并发调节应落在应用层:例如用连接池限制最大活跃写事务数,或对高冲突业务加应用级分布式锁
替代方案更有效:
- 用
SELECT ... FOR UPDATE SKIP LOCKED避免无谓等待(适用于队列类消费场景) - 将大事务拆成小事务,减少单次持锁时间
- 确保 WHERE 条件命中索引,避免锁升级为表级锁(如全表扫描触发
gap lock)
为什么 SET TRANSACTION ISOLATION LEVEL 不影响锁等待顺序
隔离级别决定的是“能看到什么数据”,不是“谁能先拿到锁”。READ COMMITTED 和 REPEATABLE READ 对锁的行为差异集中在:
-
READ COMMITTED:只对当前读(SELECT ... FOR UPDATE、UPDATE)加行锁,不加 gap lock(除非唯一索引等特殊情况) -
REPEATABLE READ:默认加 next-key lock(行锁 + gap 锁),范围更广,更容易造成锁冲突
但无论哪种级别,两个事务同时更新同一行时,InnoDB 仍按内部等待队列顺序处理,不会因为 A 是 RC 级别、B 是 RR 级别就让 A 先获得锁。
容易忽略的关键点:
- 显式开启事务(
BEGIN)后第一个 SELECT 才触发一致性读快照,此前的 UPDATE 已经持锁——隔离级别生效时机比直觉中晚 -
autocommit=0下忘记COMMIT,会导致锁长期持有,这是线上锁等待头号原因 - 用
START TRANSACTION WITH CONSISTENT SNAPSHOT并不能避免锁等待,它只影响读视图,不影响写锁获取










