如何识别正在发生的锁等待
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并不能避免锁等待,它只影响读视图,不影响写锁获取
