事务隔离性由锁和mvcc协同实现:select默认快照读不加锁,但for update/lock in share mode会加行锁;update/delete/insert依条件可能加行锁、间隙锁或表锁;ix/is意向锁保障多粒度锁高效协作。

事务的隔离性靠锁和MVCC共同实现,不是“用了事务就自动上锁”,而是根据操作类型、隔离级别、SQL语句显式/隐式触发锁机制。
哪些SQL会真正加行锁?
很多人以为 SELECT 一定不加锁,其实不然:在 REPEATABLE READ 隔离级别下,普通 SELECT 是快照读(走 MVCC,不加锁),但只要带上 FOR UPDATE 或 LOCK IN SHARE MODE,就会立即申请行级排他锁(X锁)或共享锁(S锁)。
-
UPDATE/DELETE语句,即使没命中索引,也可能升级为表锁或间隙锁(尤其是 WHERE 条件无法使用索引时) -
INSERT会加插入意向锁(Insert Intention Lock),与间隙锁冲突——这是幻读防控的关键一环 - 全表扫描的
SELECT ... FOR UPDATE会为所有扫描过的记录加锁,甚至可能锁住不存在但符合范围的“间隙”(即间隙锁)
为什么 UPDATE 卡住,但 SELECT 不卡?
因为默认隔离级别下,SELECT 走的是 MVCC 快照读,它不争抢锁,只读自己事务启动时刻可见的版本;而 UPDATE 必须获取 X 锁才能修改,一旦目标行已被其他未提交事务锁定,就会进入等待队列(is_waiting=true)。
- 这种“读不阻塞写、写不阻塞读”的分离,是 InnoDB 高并发的基础
- 但注意:如果
SELECT加了FOR UPDATE,它就变成当前读,会和UPDATE互斥 - 用
SHOW ENGINE INNODB STATUS\G可查到锁等待链,比如*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
意向锁(IX/IS)不是摆设,它是表锁和行锁的“通行证”
你执行 SELECT ... FOR UPDATE 时,InnoDB 先申请表级 IX 锁,再尝试对具体行加 X 锁。这个 IX 的作用不是锁数据,而是快速告诉别人:“这张表里有行正被写”。没有它,每次加行锁前都得扫全表检查有没有表锁,性能崩盘。
-
LOCK TABLES t WRITE会阻塞所有 IX/IS,所以它能彻底禁止其他事务访问该表 - 两个事务同时对同一张表的不同行加 X 锁,各自持 IX 锁完全不冲突——这就是多粒度锁的设计精妙处
- 误以为“没显式锁表就不会被阻塞”,结果一个 DDL(如
ALTER TABLE)来了,发现被IX锁住了元数据锁(MDL),整个表卡住
MVCC 和锁不是二选一,而是按需协作
MVCC 解决的是“读-写并发”,但它不解决“写-写并发”——两个事务同时 UPDATE 同一行,必然要靠 X 锁串行化。而 MVCC 的版本链依赖 undo log,一旦事务长时间不提交,undo log 不能回收,会导致历史版本堆积、空间暴涨、甚至阻塞 purge 线程。
- 长事务是锁和 MVCC 的双重敌人:既可能持有锁不放,又让 purge 停摆
-
READ COMMITTED下每次SELECT都生成新 Read View,REPEATABLE READ复用事务首次读的 Read View——这意味着后者能避免不可重复读,但幻读仍需间隙锁补位 - 想彻底规避锁竞争?业务层可考虑乐观锁(如
version字段 +WHERE version = ?),但要注意它不防脏读,也不替代事务边界
真正容易被忽略的,是锁的“隐形成本”:锁结构本身占内存、等待队列调度有开销、死锁检测会暂停事务几毫秒。与其纠结“要不要加锁”,不如先看清楚——你的 SQL 走的是快照读还是当前读,是否命中索引,事务是否真的必要短平快。










