根本原因是REPEATABLE-READ隔离级别下SELECT FOR UPDATE默认加next-key lock(行锁+间隙锁),若WHERE未走索引或索引不精准,会锁住大片间隙,引发严重锁等待;应确保命中唯一索引、降级隔离级别、验证覆盖索引并排查连接池与MDL锁。

SELECT FOR UPDATE 为什么一用就卡
根本原因不是语句写错了,而是它在 REPEATABLE-READ 隔离级别下默认加的是 next-key lock(行锁 + 间隙锁),哪怕你只查一个主键,只要 WHERE 条件没走索引、或走了普通索引但没覆盖全字段,InnoDB 就可能锁住一大片间隙。
常见错误现象:
-
SHOW ENGINE INNODB STATUS\G里看到大量*** (1) WAITING FOR THIS LOCK TO BE GRANTED:,且lock_mode是X, GAP或X, REC_NOT_GAP - 其他事务执行
INSERT INTO t VALUES (...)被阻塞,即使新记录的字段值完全不和当前查询条件重叠 - EXPLAIN 显示走了索引,但查询延迟仍高,
SHOW PROFILE卡在locking阶段
实操建议:
- 确认是否真需要
FOR UPDATE:只是读校验就改用SELECT LOCK IN SHARE MODE,或干脆去掉锁 - 确保 WHERE 条件命中唯一索引或主键——普通索引查单值也可能锁多个间隙,例如
WHERE status = 2没索引时会锁整个status索引区间 - 若只能用普通索引,强制缩小范围:
WHERE status = 2 AND id = 123,比单status = 2锁得更精准
隔离级别怎么选才不拖慢查询
REPEATABLE-READ 不是万能默认值,它是锁竞争的放大器。压测数据明确显示:从 READ-COMMITTED 升到 REPEATABLE-READ,innodb_row_lock_waits 每秒增长可能翻倍,而平均 RT 反而变化不大——掩盖了真实瓶颈。
关键判断点:
- 用
SELECT @@transaction_isolation查实际生效级别,别信配置文件里的transaction_isolation - 如果业务允许“可重复读”以外的一致性(比如后台统计、报表导出),直接在会话层降级:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED - 开了
binlog_format = STATEMENT?MySQL 会强制把隔离级别降到READ-COMMITTED,否则报错ERROR 1665,这时硬配 RR 没用
注意:RR 级别下长事务还会拖住 purge 线程,导致 History list length 持续上涨,最终让所有 DML 变慢——这不是锁问题,但和隔离级别强相关。
覆盖索引真能减少锁等待吗
能,但前提是真正“覆盖”。很多人建了 INDEX(status, created_at) 就以为万事大吉,结果 SELECT id, name FROM t WHERE status = 2 还是回表,照样触发间隙锁。
必须满足:
- 索引包含
WHERE所有字段(含等值、范围条件) - 包含
SELECT所有字段(不能漏id、name这类非索引列) - 包含
ORDER BY和GROUP BY字段(否则排序/分组仍需临时表)
验证方式只有两个:
-
EXPLAIN输出中Extra列必须是Using index(不是Using index condition) - 执行
SELECT * FROM t WHERE ... FOR UPDATE后,再查performance_schema.data_locks,确认锁类型是RECORD而非GAP或NEXT-KEY
别把 TEXT 或超长 VARCHAR 加进索引——B+ 树层级变深,锁获取本身变慢,得不偿失。
锁等待优化不能只盯 SQL
很多团队花几天调索引、改隔离级别,最后发现 Threads_running 持续 > 100,而 innodb_buffer_pool_read_requests 每秒几万次,innodb_buffer_pool_reads 却只有个位数——说明根本不是锁的问题,是连接池没管好,空闲连接占着资源不放。
必须同步检查:
-
wait_timeout是否还设为默认 28800 秒?应降到 60–300 秒,配合应用层连接池的idleTimeout -
max_connections是否被耗尽?用SHOW STATUS LIKE 'Threads_connected'对比峰值 QPS × 平均耗时 × 1.5 - 有没有 DDL 在跑?
ALTER TABLE会持MDL锁,连SELECT都会被卡住,错误信息是Waiting for table metadata lock
最常被忽略的一点:SHOW ENGINE INNODB STATUS 的 TRANSACTIONS 部分才是锁问题的源头证据,不是慢日志,也不是 EXPLAIN——它告诉你谁拿了锁、谁在等、等了多久、为什么等不到。


















