READ COMMITTED下锁持有时间更短,因仅对当前语句行加record lock且语句执行完即释放;而REPEATABLE READ默认用next-key lock并持锁至事务结束,且间隙锁易导致INSERT阻塞。

READ COMMITTED 下锁持有时间明显更短
在 READ COMMITTED 级别下,InnoDB 只对当前语句涉及的行加 record lock,且事务提交后立刻释放所有锁。这意味着 UPDATE 或 DELETE 语句执行完、没显式 COMMIT 前,锁只持有一条语句的时间;而 REPEATABLE READ 默认用 next-key lock(行锁 + 间隙锁),锁会一直保持到整个事务结束。
常见错误现象:UPDATE t SET status='done' WHERE id = 123 在 RR 下执行完仍卡着其他事务插入相邻 id,而在 RC 下几乎立刻放行。
- RC 下:
SELECT ... FOR UPDATE和UPDATE的锁生命周期 = 语句执行时长 + 网络/应用层延迟 - RR 下:同一事务内后续任意语句(哪怕只是
SELECT)都可能复用已有锁,锁持续时间 = 事务从BEGIN到COMMIT的全部耗时 - 特别注意:RR 下长事务(>30s)极易导致锁堆积,
SHOW ENGINE INNODB STATUS中---TRANSACTION段能看到大量lock struct(s)未释放
间隙锁(Gap Lock)在 RR 下是默认开启的
间隙锁不是锁某一行,而是锁住索引中“不存在的值之间的空隙”。比如 WHERE status = 'pending' 没走索引,InnoDB 可能锁住整个 status 索引的全部间隙——新插入任何 status 值都会被阻塞。这就是为什么你没改数据、没写大 SQL,却看到 INSERT 被卡住。
READ COMMITTED 禁用间隙锁(仅保留 record lock),但有两个例外:
- MySQL 8.0+ 中,**非唯一二级索引的范围查询**(如
WHERE idx_col BETWEEN 10 AND 20)仍可能加 gap lock,只为避免回表时幻读 - 显式使用
SELECT ... LOCK IN SHARE MODE或FOR UPDATE时,gap lock 行为与 RR 一致 - 用
SELECT * FROM performance_schema.data_locks查看实际锁类型:LOCK_MODE字段含GAP即表示命中了间隙锁
锁等待时间暴涨往往不是 SQL 慢,而是隔离级别放大了冲突
压测时如果发现 Innodb_row_lock_waits 每秒飙升、Threads_running 长期 >32,但单条 SQL 的 EXPLAIN 显示扫描行数极少,大概率是 RR 级别下间隙锁互相卡死。
实操建议:
- 用
SELECT @@transaction_isolation确认真实生效级别,别只信my.cnf里的transaction_isolation - 临时降级:在应用里对非强一致场景的事务开头加
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED - 避免在 RR 下执行无索引的
UPDATE/DELETE,哪怕只改一行,也可能锁全表(因全表扫描触发的 next-key lock 覆盖所有间隙) - 监控
History list length:超过 10000 就说明 purge 跟不上,RR 下版本链遍历开销剧增,间接拉长锁持有感知时间
binlog_format = STATEMENT 会强制降级为 READ COMMITTED
如果你开了 binlog_format = STATEMENT,MySQL 会在事务启动时自动把隔离级别降为 READ COMMITTED,否则报错 ERROR 1665 (HY000): Cannot execute statement: binlogging impossible since the statement is non-deterministic。
这意味着你配置了 transaction_isolation = REPEATABLE-READ,但实际运行中很多事务根本没走 RR —— 尤其在老系统或兼容性优先的部署里。这点常被忽略,导致调优方向完全跑偏。
验证方式:
- 在事务内执行
SELECT @@transaction_isolation,看返回值是不是READ-COMMITTED - 检查
SHOW VARIABLES LIKE 'binlog_format'是否为STATEMENT - 如果必须用 STATEMENT 格式且需要 RR 语义,只能改 SQL 让其 deterministic(比如所有 UPDATE 加上主键 WHERE 条件)
锁持有时间不是孤立指标,它和历史版本堆积、purge 进度、索引覆盖程度咬合在一起。改隔离级别前,先查 History list length 和 data_locks,否则可能把问题从“锁太久”变成“版本链太深”。



















