MySQL行锁在事务提交或回滚时立即释放,而非语句执行完或连接关闭时;其本质是事务粒度的锁,自动提交模式下每条DML为独立事务,关闭自动提交后未COMMIT会导致锁长期持有。

事务提交后行锁什么时候释放
MySQL 的行级锁(如 SELECT ... FOR UPDATE 或 UPDATE 持有的锁)在事务提交(COMMIT)或回滚(ROLLBACK)时**立即释放**,不是在语句执行完就释放,也不是等到连接关闭。
关键点在于:InnoDB 的锁是**事务粒度**的,不是语句粒度。即使你只更新一行、很快执行完,只要事务没结束,锁就一直挂着。
- 显式开启事务(
BEGIN或START TRANSACTION)后,所有 DML 操作的锁都会延续到COMMIT - 自动提交模式(
autocommit=1)下,每条 DML 本身就是一个独立事务,锁在语句返回后立刻释放 - 若用
SET autocommit = 0关闭自动提交,但忘了COMMIT,锁会一直持有,可能造成其他事务长时间阻塞甚至超时(Lock wait timeout exceeded)
为什么有时候 COMMIT 后还看到锁没释放?
这不是锁没释放,而是你观察的时机或方式有问题。常见误判场景:
- 用
SELECT * FROM performance_schema.data_locks查锁时,结果反映的是「当前活跃事务持有的锁」,如果事务已提交,该视图里不会出现对应记录 - 用
SHOW ENGINE INNODB STATUS看到的TRANSACTIONS部分若仍有事务 ID,说明那个事务还没结束(可能卡在应用层没发COMMIT) - 客户端连接未断开、事务处于空闲状态(
TRX_STATE = "RUNNING"但没执行语句),锁依然有效
MySQL 语句执行顺序和锁获取时机
InnoDB 中,锁是在**语句实际访问到某行数据时才加上的**,不是解析 SQL 就加锁,也不是按 SQL 文本顺序逐字执行。执行流程大致如下:
- SQL 解析 → 生成执行计划 → 开始执行
- 执行器逐行读取存储引擎返回的数据;每读到一行满足条件的记录,InnoDB 就对其加锁(比如
UPDATE t SET x=1 WHERE id=5,只会对 id=5 这行加 X 锁) - 如果是范围条件(
WHERE id > 10),可能加间隙锁(Gap Lock)或临键锁(Next-Key Lock),影响的是索引区间,不单是命中行 - 全表扫描 + 条件过滤的写法(如没走索引的
WHERE status='draft')会导致扫描全表并为每行加锁,极易引发锁冲突
典型反例:UPDATE orders SET paid=1 WHERE user_id=123 AND status='unpaid' —— 如果 user_id 没索引,InnoDB 可能扫全表,锁住成百上千行,哪怕最终只改 1 行。
如何验证锁是否已释放
最直接的方式是用另一个会话尝试获取相同资源的锁,看是否被阻塞:
- 会话 A:
BEGIN; UPDATE t SET v=1 WHERE id=1;(不提交) - 会话 B:
SELECT * FROM t WHERE id=1 FOR UPDATE;→ 会卡住 - 会话 A:
COMMIT;→ 会话 B 立即返回,说明锁已释放
注意:不要依赖 information_schema.INNODB_TRX 中的 TRX_STARTED 时间判断“是否刚提交”,它只反映事务开始时间;真正要看的是 TRX_STATE 是否为 "COMMITTED IN MEMORY"(极短暂)或已不在结果集中。
锁释放这件事本身没有延迟、不依赖后台线程清理,但如果你在高并发下观察到“提交后别人还是等了一两秒”,大概率是对方事务在等别的锁,或者网络/应用层延迟掩盖了真实释放时刻。










