InnoDB 行级锁与意向锁
InnoDB 行级锁与意向锁
一、锁(整体概念)
1. 概念(Concept)
- 定义:数据库管理系统提供的并发控制机制,用于协调多个事务对共享数据资源的访问顺序。
- 分类:
- 按粒度:表级锁、行级锁、页级锁
-按功能:共享锁(S)、排他锁(X)、意向锁(IS/IX) - 按策略:悲观锁(数据库锁)、乐观锁(版本号/CAS)
- 按粒度:表级锁、行级锁、页级锁
- 关系:InnoDB 以行级锁为核心,结合MVCC(多版本并发控制)实现非锁定读(快照读),兼顾并发性能与一致性。
2. 作用 / 目的(Purpose)
- 保证事务 ACID 中的隔离性(Isolation)与一致性(Consistency)。
- 防止并发访问导致的脏读、不可重复读、幻读、丢失更新。
- 不用的后果:多事务同时读写同一数据时,结果相互覆盖或依赖混乱,数据失去可信度。
3. 原理 / 机制(Mechanism)
- 锁管理器(Lock Manager):InnoDB 在内存中维护锁表和锁等待队列,通过锁兼容性矩阵判断请求是否冲突。
- 自动加锁/释放:事务执行 DML 时自动申请锁,事务提交(COMMIT)或回滚(ROLLBACK)时自动释放。
- 两阶段锁协议(2PL):加锁和解锁分为两个阶段,保证事务隔离。
4. 使用方式(Usage)
- 隐式加锁:
UPDATE / DELETE / INSERT等 DML 语句自动加相应锁。 - 显式加锁:
SELECT...FORUPDATE;-- 显式加 X 锁SELECT...LOCKINSHAREMODE;-- 显式加 S 锁LOCKTABLEStWRITE;-- 显式表锁(InnoDB 不推荐)
5. 注意事项 / 坑点(Caveats)
- 锁粒度过大(如退化为表锁)会严重降低并发性能。
- 死锁:多个事务循环等待对方持有的锁,InnoDB 会自动检测并回滚代价最小的事务。
- 锁等待超时:由参数
innodb_lock_wait_timeout控制(默认 50 秒)。
6. 对比 / 演进(Comparison)
| 维度 | MyISAM | InnoDB |
|---|---|---|
| 锁粒度 | 仅表锁 | 行锁 + 表锁 |
| 并发读 | 读锁会阻塞写 | MVCC 快照读不阻塞 |
| 适用场景 | 读多写少、非事务 | 高并发、事务强一致 |
二、共享锁(S 锁 / Shared Lock)
1. 概念(Concept)
- 定义:行级读锁。事务对数据行加 S 锁后,允许其他事务继续加 S 锁读取,但禁止加 X 锁修改。
- 分类:行级锁、悲观锁。
- 关系:与 X 锁互斥,与其他 S 锁兼容;与意向共享锁(IS)兼容。
2. 作用 / 目的(Purpose)
- 保证读一致性:读取期间阻止其他事务修改,防止脏读。
- 提高读并发:多事务可同时持有同一行的 S 锁,实现并发读。
- 使用场景:
- 读取数据后需基于该值做业务判断(如读取余额后决定是否转账)。
- 父子表一致性读取(先对父表加 S 锁,防止父记录被删)。
- 报表统计,需确保查询期间数据不被修改。
3. 原理 / 机制(Mechanism)
- 事务对某行加 S 锁时,InnoDB 执行两步:
- 对表加IS 锁(意向共享锁)
- 对目标行加S 锁
- 锁兼容性:S-S 兼容,S-X 互斥。
- 底层通过
lock_t结构体记录锁信息,并维护锁等待队列。
4. 使用方式(Usage)
-- 显式加共享锁BEGIN;SELECT*FROMusersWHEREid=1LOCKINSHAREMODE;-- 其他事务可继续 LOCK IN SHARE MODE,但不能 FOR UPDATE 或 UPDATECOMMIT;-- 隐式加锁(SERIALIZABLE 隔离级别)SETSESSIONTRANSACTIONISOLATIONLEVELSERIALIZABLE;BEGIN;SELECT*FROMusersWHEREid=1;-- 普通 SELECT 也加 S 锁COMMIT;5. 注意事项 / 坑点(Caveats)
- 死锁风险:事务 A 持 S 锁等 B 释放 X 锁,事务 B 持 S 锁等 A 释放 X 锁,形成循环等待。
- 阻塞写操作:长时间持有 S 锁会导致写事务持续等待,影响系统吞吐量。
- 与 MVCC 快照读的区别:
LOCK IN SHARE MODE是当前读(Current Read),会加真实锁;普通SELECT在 RC/RR 下是快照读,不加锁。
6. 对比 / 演进(Comparison)
- vs X 锁:S 锁共享、只读;X 锁独占、可写。
- vs 意向锁:S 锁是真实行锁,IS 锁是表级辅助标记。
- 版本演进:MySQL 8.0 支持
SELECT ... FOR SHARE(LOCK IN SHARE MODE的新语法别名)。
三、排他锁(X 锁 / Exclusive Lock)
1. 概念(Concept)
- 定义:行级写锁。事务对数据行加 X 锁后,禁止其他事务加任何锁(S 或 X)访问该行。
- 分类:行级锁、悲观锁。
- 关系:与 S 锁、X 锁均互斥;与意向排他锁(IX)在表级兼容。
2. 作用 / 目的(Purpose)
- 保证写操作独占性:确保修改过程不被其他事务干扰,防止丢失更新和脏写。
- 保证原子性:事务内的修改要么全部成功,要么通过回滚撤销。
- 使用场景:
- 库存扣减、账户转账、订单状态变更。
- 悲观锁并发控制:先锁定资源,再执行业务逻辑。
- 避免"读取-修改-写入"过程中的竞争条件。
3. 原理 / 机制(Mechanism)
- 事务对某行加 X 锁时,InnoDB 执行两步:
- 对表加IX 锁(意向排他锁)
- 对目标行加X 锁(记录锁 Record Lock)
- 锁兼容性:X 与任何锁(S、X)都互斥。
- 对于范围查询,可能升级为间隙锁(Gap Lock)或临键锁(Next-Key Lock)。
4. 使用方式(Usage)
-- 显式加排他锁BEGIN;SELECT*FROMusersWHEREid=1FORUPDATE;UPDATEusersSETbalance=balance-100WHEREid=1;COMMIT;-- 隐式加锁(DML 自动)BEGIN;UPDATEusersSETage=20WHEREid=1;-- 自动加 X 锁DELETEFROMusersWHEREid=2;-- 自动加 X 锁INSERTINTOusers(name)VALUES('Tom');-- 对新插入行自动加 X 锁COMMIT;5. 注意事项 / 坑点(Caveats)
- 死锁高发区:多个事务以不同顺序对多行加 X 锁,极易形成循环等待。
- 锁未命中索引:若
WHERE条件无索引,可能退化为表锁或锁定大量行。 - 幻读问题:仅靠 X 锁无法完全解决幻读,需配合间隙锁 / 临键锁(RR 隔离级别)。
- 超时控制:锁等待超过
innodb_lock_wait_timeout会报错:Lock wait timeout exceeded。
6. 对比 / 演进(Comparison)
- vs S 锁:X 锁独占资源,S 锁共享资源。
- vs 乐观锁:X 锁是"先锁后改"的悲观策略;乐观锁是"先改后校验"(如版本号),无真实数据库锁。
- 版本演进:MySQL 8.0 支持
SELECT ... FOR UPDATE NOWAIT(立即报错不等待)和SKIP LOCKED(跳过已锁定行)。
四、意向锁(IS / IX 锁 / Intention Lock)
1. 概念(Concept)
- 定义:表级锁,用于声明事务即将对表中的某些行加行级锁(S 或 X)。
- IS(意向共享锁):事务即将对某些行加 S 锁。
- IX(意向排他锁):事务即将对某些行加 X 锁。
- 分类:表级锁、辅助锁。
- 关系:是行锁的前置"信号锁",协调表级锁与行级锁的冲突判断。
2. 作用 / 目的(Purpose)
- 核心作用:解决"加表锁时是否需要逐行扫描检查行锁"的效率问题。
- 无意向锁:加表锁前需遍历全表,检查每一行是否被锁定 →O(n) 复杂度。
- 有意向锁:只需检查表级是否有 IS/IX →O(1) 复杂度。
- 使用场景:
ALTER TABLE(需表级锁)与正在进行的 DML(持有 IX 锁)之间的协调。LOCK TABLES显式表锁与行锁共存时的快速判断。- 外键约束检查时,父表与子表之间的锁协调。
3. 原理 / 机制(Mechanism)
- 自动维护:事务对行加 S 锁前,先对表加 IS;对行加 X 锁前,先对表加 IX。
- 兼容性:
- IS 与 IX互相兼容(多个事务可同时持有 IX)。
- 但 IS/IX 与表级 S/X 锁存在互斥:
- IX 与 表 S 锁互斥
- IX 与 表 X 锁互斥
- IS 与 表 X 锁互斥
- 表锁请求时,只需检查表级意向锁状态,无需遍历行记录。
4. 使用方式(Usage)
- 完全自动,用户不可手动干预。
-- 以下操作自动在表上加 IS 锁,在行上加 S 锁SELECT*FROMusersWHEREid=1LOCKINSHAREMODE;-- 以下操作自动在表上加 IX 锁,在行上加 X 锁SELECT*FROMusersWHEREid=1FORUPDATE;UPDATEusersSETage=20WHEREid=1;INSERTINTOusers(name)VALUES('Tom');5. 注意事项 / 坑点(Caveats)
- 不可见但存在:用户无法直接操作,但可通过
SHOW ENGINE INNODB STATUS查看锁状态。 - 不是真正的"阻塞锁":意向锁本身不会阻塞其他事务对行的访问,只阻塞表级锁请求。
- 常见误区:误以为意向锁会阻塞行级操作。实际上,IX 与 IX 兼容,多个事务可同时持有 IX 并操作不同行。
6. 对比 / 演进(Comparison)
- vs 行锁:意向锁是"表级信号",行锁是"真实数据保护";两者协作,意向锁不替代行锁。
- vs 表锁:表锁直接阻塞整表所有访问;意向锁仅影响后续表锁请求,不影响行级 DML。
- 版本演进:从 InnoDB 诞生之初即存在,机制稳定,无重大版本差异。
五、锁兼容性总表
| 请求锁 \ 已有锁 | IS | IX | S(表) | X(表) |
|---|---|---|---|---|
| IS | ✓ | ✓ | ✓ | ✗ |
| IX | ✓ | ✓ | ✗ | ✗ |
| S(表) | ✓ | ✗ | ✓ | ✗ |
| X(表) | ✗ | ✗ | ✗ | ✗ |
✓ 兼容(可同时持有),✗ 互斥(需等待)
六、问题
什么是锁?InnoDB有哪些行级锁类型(共享锁、排他锁)?意向锁的作用是什么?
第一,什么是锁。 锁是数据库的并发控制工具。多事务同时读写同一份数据,容易出现脏读、数据覆盖。锁就是用来规定访问顺序,保障隔离性和一致性。InnoDB 搭配 MVCC 把读分成两种:普通 SELECT 是快照读,不加锁;FOR UPDATE、LOCK IN SHARE MODE 是当前读,会加锁。InnoDB 主打行锁,粒度小,并发高。
第二,行级锁。 行锁分两种基础锁,配合 RR 隔离还有三种细分。
共享锁 S 锁,也叫读锁。多个事务可以同时加 S 锁读同一行,彼此兼容;但只要某一行上有 S 锁,其他事务就无法对同一行加 X 锁去修改。适用场景:读完数据要依据结果做判断,必须保证读的过程中数据不会变。加锁方式:SELECT … LOCK IN SHARE MODE,MySQL 8.0 也可以用 FOR SHARE。
排他锁 X 锁,也叫写锁。增删改自动加 X 锁,也能用 FOR UPDATE 手动加。一旦某行被 X 锁锁住,其他事务不能修改,也不能加锁读;但普通 SELECT 走 MVCC 快照读,不加锁也能读到数据。适合转账、库存扣减这类必须独占修改的场景。
补充一句:RR 隔离下,行锁细分为记录锁、间隙锁、临键锁。记录锁锁单行;间隙锁锁住索引空隙,防止插入新数据;临键锁是前两者的组合,RR 范围查询默认用它,从根源避免幻读。RC 下只有记录锁,没有间隙锁,所以幻读没法靠锁来解决。
第三,意向锁。 意向锁是表级锁,分 IS 和 IX,不能手动加,全程自动维护。事务要给行加 S 锁,必先给表加 IS;要给行加 X 锁,必先给表加 IX。核心目的只有一个:优化性能。没有意向锁的话,执行 ALTER 这类要加表锁的操作时,数据库得逐行遍历全表检查有没有行锁,效率极低。有意向锁只需要瞄一眼表上有没有 IS/IX,立刻就能判断能不能加表锁。
这里有个关键误区:意向锁不会阻塞普通行操作。IS 和 IX、IX 和 IX 全都互相兼容,多个事务改不同行互不影响;意向锁只拦后续的表锁请求。
最后记住这个分工:行锁管数据,意向锁管效率。 行锁保护真实读写,意向锁是表上的预告牌,让表锁判断少走弯路。二者配合,InnoDB 才能在保证一致性的同时支撑高并发。
