innodb事务中dml语句默认立即加锁而非提交时加锁,update、delete及select for update/lock in share mode会根据索引类型施加record lock、gap lock或next-key lock,锁持续至事务结束;无索引条件触发表锁,autocommit=1时锁短暂存在但依然生效。

事务中DML语句加锁是InnoDB的默认行为
MySQL的InnoDB引擎在事务中执行UPDATE、DELETE或带FOR UPDATE/LOCK IN SHARE MODE的SELECT时,会立即对涉及的行加锁(通常是排他锁X或共享锁S),而不是等到COMMIT才加。这是因为锁的作用是保证隔离性——防止其他事务同时修改同一行导致数据错乱。
常见错误现象:
• 事务A执行UPDATE t SET x=1 WHERE id=100后未提交,事务B再执行相同UPDATE会被阻塞,而非报错或跳过
• SELECT ... FOR UPDATE在读取时就加X锁,后续UPDATE不会重复加锁,但其他事务的读写都会被拦住
- 锁的类型取决于语句和索引:主键/唯一索引上的等值查询 → 行锁(Record Lock);范围查询 → 可能触发间隙锁(Gap Lock)或临键锁(Next-Key Lock)
- 没有索引的WHERE条件会导致全表扫描,行锁升级为表锁,极大降低并发度
- 锁持续到事务结束(
COMMIT或ROLLBACK),不是语句结束就释放
autocommit=1时DML也隐式加锁
即使没显式BEGIN,只要autocommit=1(默认),每条DML都自动开启并提交独立事务,锁在语句执行完立刻释放。但这不等于“不加锁”——它只是生命周期极短。一旦你手动SET autocommit=0,后续DML就进入长事务模式,锁会一直挂着。
使用场景判断:
• 简单更新配置项、计数器等,autocommit=1够用,锁影响小
• 转账类业务必须显式事务+锁,否则SELECT balance和UPDATE balance之间可能被并发修改
-
SELECT ... FOR UPDATE和SELECT ... LOCK IN SHARE MODE只在事务内有效;单独执行时,若autocommit=1,锁会在语句结束后立即释放 - 注意
INSERT也会加锁:对插入位置加插入意向锁(Insert Intention Lock),与间隙锁冲突时会阻塞 - 死锁检测只发生在行级锁之间,表锁和MDL锁不参与InnoDB死锁检测
为什么不能只在COMMIT时加锁?
如果等到COMMIT才加锁,中间窗口期其他事务就能读到“将要被改但还没改”的脏数据,或者覆盖写入,彻底破坏原子性和隔离性。例如转账:事务A读出余额100,事务B也读出100,各自+100后都写回200——最终结果不是300而是200。
性能影响很实际:
• 锁持有时间越长,阻塞越多,超时(Lock wait timeout exceeded)概率越高
• 长事务还拖住MDL锁(比如事务里执行了SELECT,后面有人想ALTER TABLE就会卡住整个库)
- 避免长事务:业务逻辑尽量轻量,不要在事务里做RPC调用、文件读写等耗时操作
- 查锁状态用
SELECT * FROM information_schema.INNODB_TRX和INNODB_LOCK_WAITS - 监控锁等待:关注
Innodb_row_lock_waits和Innodb_row_lock_time_avg这两个状态变量
锁本身不是开销来源,锁等待才是瓶颈。真正容易被忽略的是:事务边界不清晰 + 无索引DML + 长事务共存时,行锁会悄无声息地变成表级阻塞,而你只看到“慢”,却找不到锁在哪。











