# 系列目录
# 参考
# InnoDB 锁
数据库遵循的是两段锁协议,将事务分成两个阶段,加锁阶段和解锁阶段(所以叫两段锁)
- 加锁阶段:在该阶段可以进行加锁操作。加锁不成功,则事务进入等待状态,直到加锁成功才继续执行。
- 解锁阶段:当事务释放了一个封锁以后,事务进入解锁阶段,在该阶段只能进行解锁操作不能再进行加锁操作。
这种方式虽然无法避免死锁,但是两段锁协议可以保证事务的并发调度是串行化(串行化很重要,尤其是在数据恢复和备份的时候)的。
# 行锁
InnoDB 实现了两种类型的行级锁:
- 共享锁 (S):在对任何数据进行读操作之前要申请并获得 S 锁。其它事务可以继续加共享锁,但不能加排它锁;
- 独占锁 (X):在进行写操作之前要申请并获得 X 锁。其它事务不能再获得任何锁;
# 表锁
InnoDB 支持多粒度锁,因此 S 和 X 锁还可以锁表(如使用 ALTER TABLE
等语句会给表上 X 锁)。
另外还设计了两个意向锁,注意意向锁都是表级的:
- 意向共享锁 (IS):表明事务即将给表中的行设置 S 锁。事务给行加 S 锁前必须获得该表的 IS 锁。
- 意向排它锁 (IX):表明事务即将给表中的行设置 X 锁。事务给行加 X 锁前必须获得该表的 IX 锁。
综上,MySQL 支持两种行锁和四种表锁。
四种表锁的兼容表如下:
锁类型 | X | IX | S | IS |
---|---|---|---|---|
X | 冲突 | 冲突 | 冲突 | 冲突 |
IX | 冲突 | 兼容 | 冲突 | 兼容 |
S | 冲突 | 冲突 | 兼容 | 兼容 |
IS | 冲突 | 兼容 | 兼容 | 兼容 |
总结一下就是:
意向锁
之间相互不冲突互斥锁
和所有锁都冲突共享锁
和互斥意向锁
冲突
# 行锁的算法
MySQL :: MySQL 8.0 Reference Manual :: 15.7.1 InnoDB Locking (opens new window)
- Record Locks:锁(单条)记录
- Gap Locks:间隙锁,锁定一个开区间范围,但不包括记录本身。间隙指的是索引键值之间的间隙,如下面例子的
(-∞, 0)
、(10, 15)
。 - Next-Key Locks:锁定一个非开区间范围,包括记录本身
假设表中 id 为主键,且有值:0, 5, 10, 15, 20, 25
主键等值查询且数据存在时,会添加 Record Locks,锁住 10
这个记录:
SELECT * FROM t WHERE id = 10 FOR UPDATE;
主键等值查询且数据不存在时,会添加 Gap Locks,锁住 (10, 15)
的范围,防止幻读:
SELECT * FROM t WHERE id = 11 FOR UPDATE;
主键范围查询时,会添加 Next-Key Locks,锁住 (10, 15]
的范围,防止幻读:
SELECT * FROM t WHERE id >= 10 AND id < 11 FOR UPDATE;
MySQL 官网上有提到锁的是前开后闭区间,但是网上测试不同版本的 MySQL 的表现还不一样,所以就记非开区间吧。
# 实际演示
# 两个事务先后 update 但不提交 -> 后更新的被卡住
说明可重复读时进行更新也会加锁。
# 两个事务都 lock in share mode,然后一个事务 update -> 该事务被卡住
也是说明可重复读时进行更新也会加锁,但是这次加独占锁失败了。
# InnoDB 通过加锁回避幻读
如果在可重复读下进行了 SELECT ... FOR UPDATE
或 UPDATE
或 DELETE
,InnoDB 会锁住当前最新已提交的数据(而不是当前事务开始前已提交的数据),并在查询、更新、删除过程中使用最新已提交的数据。
代价是,加锁后读的值和加锁前读的值不同,即不可重复读。
-- locking - How MVCC works with Lock in MySql? - Stack Overflow (opens new window)