MySQL 锁
锁结构
锁结构中两个比较重要的字段
trx信息:表明该锁与哪个事务相联系;
is_waiting:表明当前事务是否在等待;
再谈锁定读
在并发事务的情况下,读 - 读 不会引起问题,而 读 - 写,写 - 读,写 - 写 会发生问题,需要使用 MVCC 或 加锁的方式来解决问题
共享锁和独占锁
共享锁:又称读锁,S锁,MySQL 在读取一条数据时,首先先获取该记录的 S 锁
独占锁:又称写锁,X锁,MySQL 在更改一条数据时,首先先获取该记录的 X 锁
互斥情况
| 兼容性 | X 锁 | S 锁 |
|---|---|---|
| X 锁 | 不兼容 | 不兼容 |
| S 锁 | 不兼容 | 兼容 |
- 一个事务获取了该记录的 S 锁,其他事务可以继续获取该记录的 S 锁,但不能获取该记录 X 锁,会陷入阻塞状
- 一个事务获取了该记录的 X 锁,其他事务不可以获取该记录的 S 锁和 X 锁
锁定读的语句
对读取记录加 S 锁:select ... lock in share mode
对读取记录加 X 锁:select ... for update
写语句
update:给记录加 X 锁,然后再执行 delete mark 操作
delete:给记录加 X 锁
insert:加 "隐式锁",其实是根据事务 ID 进行判断
多粒度锁(表锁)
前面提到的锁是针对记录的,可以称为行锁,行锁的粒度较细,其实一个事务也可以在表的层面进行加锁,称为表锁。表锁也分为共享锁(S 锁)和独占锁(X 锁)
给表加 S 锁:
- 可以给表中的记录加 S 锁
- 可以给表加 S 锁;
- 不可以给表中的记录加 X 锁;
- 不可以给表加 X 锁;
给表加 X 锁,表明该事务要独占这个表
- 不可以给表中的记录加 S 锁
- 不可以给表加 S 锁;
- 不可以给表中的记录加 X 锁;
- 不可以给表加 X 锁;
在给表加 S 锁时,要确保记录中没有加 X 锁;
在给表加 X 锁时,要确保记录中没有加 S 锁 和 X 锁;
意向共享锁:当给表中的记录加 S 锁时,会给表加上 IS 意向共享锁;
意向独占锁:当给表中的记录加 X 锁时,会给表加上 IX 意向独占锁;
- 在加 S 锁(表锁)时,若发现含有 IX 锁,则阻塞等待,否则加上 S 锁即可;
- 在加 X 锁(表锁)是,若发现含有 IS 或者 IX 锁,则阻塞等待,否则加上 X 锁即可;
保证了加表锁时不用去遍历整个表,检查表中行锁的情况,大大提高了性能,IX 锁 和 IS 锁两者并不互斥,只是一种标记手段而已
表锁的兼容性
| 兼容性 | X | IX | S | IS |
|---|---|---|---|---|
| X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
| IX | 不兼容 | 兼容 | 不兼容 | 兼容 |
| S | 不兼容 | 不兼容 | 兼容 | 兼容 |
| IS | 不兼容 | 兼容 | 兼容 | 兼容 |
MySQL 中的行锁和表锁
在 InnoDB 事务中,对记录加锁带基本单位是 next-key 锁,但是会因为一些条件会退化成间隙锁,或者记录锁。加锁的位置准确的说,锁是加在索引上的而非行上。
其他存储引擎中的锁
在MyISAM、Memory 存储引擎中,只支持表锁,当我们为使用这些存储引擎的表加锁时,一般都是针对当前会话进行的加锁;
InnoDB 存储引擎中的锁
InnoDB 中的表锁
普通的 select、update、delete、insert 不会给表加表锁。
LOCK TABLES t READ:InnoDB 存储引擎会给表 t 加 S 锁;
LOCK TABLES t WRITE:InnoDB 存储引擎会给表 t 加 X 锁;
InnoDB 中的行锁
Record Lock - 记录锁
- 给某个记录直接上锁,官方的名称为 LOCK_RES_NOT_GAP
- 该锁也有 S 锁和 X 锁之分的
Gap Lock - 间隙锁
MySQL 在 Repeatable Read 隔离级别下利用 MVCC 和 加锁 的方法是可以很大程度解决幻读的。对于加锁的方式存在一个问题,刚开始时并没有 幻影记录,无法对这些幻影记录上锁,这时就要用到 Gap Lock 了;
Gap Lock 的作用:给一个记录加 Gap Lock 表示不能在该记录的前面的间隙插入新的记录;
例如给 number = 8 的记录加上Gap Lock,表明(3,8)之间的间隙不能插入新的记录,注意:number 为 8 的记录不会被锁,间隙锁的作用仅仅是锁住前面的间隙而已
若要插入 number = 4 的记录,先会定位到该新记录的下一条记录,即 number = 8 的记录,发现 number = 8 的记录中加上了 Gap Lock ,表明(3,8)之间的间隙不能插入新的数据,该插入语句会被阻塞
若给 (20,∞) 间隙锁,则要给页面中的最大值 Supremum 加上间隙锁
Supremum 记录:页面中的最大值Infimum 记录:页面中的最小值
Next-Key Lock - 临键锁
临键锁:即锁住当前的记录,又锁住该记录前面的间隙;
临键锁 等于 记录锁 + 间隙锁,保护当前的记录,又阻止其他事务向该记录前面的间隙中插入新的数据;
Insert Intention Lock - 插入意向锁
当一个事务插入到加了间隙锁的间隙中时,该事务在内存中会生成一个锁结构,即插入意向锁。等到 gap 锁释放后,插入意向锁会把 is_waiting 字段改成 false,然后执行插入操作;
隐式锁
减少在内存中生成锁结构,从而提出了 隐式锁 的概念
如果一个事务插入了一条记录,另一个事务使用 select ... lock in share mode 来获取 S 锁或者 使用 select ... for update 来获取 X 锁时,若获取成功则会造成 脏读 / 脏写的问题;
而隐式锁就正好处理这个问题:insert 的记录会把当前事务的 ID,赋值为 trx_id 的字段,另一个事务获取时,若该记录的 trx_id 位于活跃列表 m_ids 中,则会阻塞当前的 select 语句,为自身事务生成锁结构,is_waiting 字段设置为 true,而当前事务生成锁结构,is_waiting 字段设置为 false;