MySQL 锁机制
参考:
MySQL锁机制详解
快照读与锁定读
MySQL中读可以分作两类
- 快照读: 也就是普通的
select,读-读场景不需要额外的机制保证并发安全, 而读-写场景通过MVCC来实现隔离级别 - 锁定读: 会加锁的读, 是
select ... for update或者select ... lock in share mode, 在MySQL中update, delete操作会分成两部分, 读取阶段和写入(删除)阶段, 前一阶段就属于锁定读
共享锁(S)和互斥锁(X)
共享锁: 如果一个事务给一个表(记录)加了S锁, 其他事务能再获取这个S锁, 但是该事务和其他事务都不能再获取这个表(记录)的X锁
互斥锁: 独占锁, 如果有一个事务给一个表(记录)加了X锁, 其他事务不能再从这个表(记录)上获取锁了(无论XS), 同时也不能给已经上锁了的表(记录)加上X锁
| 兼容性 | 共享锁S | 互斥锁X |
|---|---|---|
| 共享锁S | 兼容 | 不兼容 |
| 互斥锁X | 不兼容 | 不兼容 |
类似于读-读并发安全, 所以不需要额外处理, 也就是S锁能兼容, 其他的情况都存在并发安全问题, 所以不能兼容
表锁
表级锁
锁上整张表的锁, 分有X和S两种
什么时候会加上表级锁: MySQL InnoDB引擎中因为有更细粒度的行级锁, 所以其实表级锁应用场景极其有限(没啥用)
但是现在有个问题就是, 如果我们要对某个表加X锁或者S锁, 有个问题就是, 我们需要确保现在这个表是没有不兼容的行锁的
- 我们要加上表级S锁的时候, 就需要保证表内没有X锁
- 我们要加上表级X锁的时候, 就需要保证表内没有锁
这种时候很明显我们不能遍历每行来看是不是有加锁, Innodb引入了意向锁的机制
意向锁
IS锁: 共享意向锁, IX锁: 互斥意向锁
什么时候会加上意向锁: 现在在加锁前, 我们会现在表级上加上一个意向锁, 比如我们要加上一个互斥X锁, 我们就会先在表上加一把IX, IS同理
在有了意向锁以后, 我们就能通过判断表上有没有持有IS和IX锁来快速判断现在我们能不能加表锁
IS, IX是表级锁, 它们的出现仅仅是为了在之后加表级锁的时候快速判断表中是不是存在加锁的记录, IS锁和IS锁之间是兼容的, IX和IX之间是兼容的
| 兼容性 | X |
IX |
S |
IS |
|---|---|---|---|---|
X |
不兼容 | 不兼容 | 不兼容 | 不兼容 |
IX |
不兼容 | 兼容 兼容的 | 不兼容 | 兼容 兼容的 |
S |
不兼容 | 不兼容 | 兼容 兼容的 | 兼容 兼容的 |
IS |
不兼容 | 兼容 兼容的 | 兼容 兼容的 | 兼容 兼容的 |
MDL锁
Meta Data Lock: 元数据锁, 是针对DDL操作的锁, 防止在存在事务还在执行的时候变更表结构
什么时候会加上MDL:
- 对一张表CRUD的时候, 加上MDL读锁
- 修改表结构的时候, 加上MDL写锁
MDL写锁的获取会阻塞MDL读锁的获取, 也就是如果有一个事务在修改表结构获取MDL写锁的时候阻塞了, 后续的CRUD操作都会被阻塞住
MDL在事务提交后才会释放
AUTO-INC锁
是用于处理AUTO_INCREAMENT自增字段的自增的锁, 如果并发地向一张表中插入记录, 就可能会导致自增字段值重复
有两种锁来解决并发自增问题
- AUTO-INC锁: 在执行插入语句的时候, 会加上一个表级的AUTO_INC锁, 然后分配自增属性的值, 在插入语句执行结束后将锁释放掉, 以此将插入时生成自增值串行化
- 轻量级锁: 在执行插入语句的时候, 获取这个轻量级锁, 在生成自增属性的值以后就将锁释放掉
如果我们的插入语句在执行前就知道要插入多少条数据, 就会采用轻量级锁
TIP:设计InnoDB的大佬提供了一个称之为innodb_autoinc_lock_mode的系统变量来控制到底使用上述两种方式中的哪种来为AUTO_INCREMENT修饰的列进行赋值,当innodb_autoinc_lock_mode值为0时,一律采用AUTO-INC锁;当innodb_autoinc_lock_mode值为2时,一律采用轻量级锁;当innodb_autoinc_lock_mode值为1时,两种方式混着来(也就是在插入记录数量确定时采用轻量级锁,不确定时使用AUTO-INC锁)。不过当innodb_autoinc_lock_mode值为2时,可能会造成不同事务中的插入语句为AUTO_INCREMENT修饰的列生成的值是交叉的,在有主从复制的场景中是不安全的。
行锁
行锁在MySQL中是InnoDB独有的更加细粒度的锁
记录锁
Record Lock: 记录锁, 有S锁和X锁之分, 是针对一条记录上加的锁, 也就是对某一行加上的锁
什么时候加锁: 在执行锁定读的时候会对遍历到的行加上, 往往是以next-key lock的组成部分的形式被加上
间隙锁
Gap Loc: 间隙锁, 有S锁和X锁, 但是没有分别, 实际上是都是S锁的行为, 多个事务可同时获取一个间隙的Gap Lock, 是对一个开区间的锁, 用于解决可重复读隔离级别下的幻读现象的

在id范围(3, 5)的间隙锁以后, 其他事务就无法再插入id =4的记录了
什么时候加锁: 和记录锁是一样的, 执行锁定读的时候会对遍历到的行加上, 往往是以next-key lock的组成部分的形式被加上
Next-Key Lock
Next-Key Lock = Record Lock + Gap Lock, 锁定一个范围, 并且锁定记录本身
因为Next-Key Lock是包含Record Lock的, 所以是分X锁和S锁的
插入意向锁
是在insert前会对某个记录加上锁, 用于提高插入的并发效率, 只要插入位置不同, 想插入的事务间不会相互阻塞, 只有当多个事务尝试插入相同位置时才会产生冲突
什么时候加锁: 在执行插入操作的时候需要判断这个位置上有没有间隙锁, 如果有, 插入操作就会被阻塞, 然后生成一个插入意向锁(is_wait = true), 在这个间隙锁被释放掉以后, 插入意向锁就会被真正获取到, 执行插入操作
有插入意向锁(当前机制):
1 | css复制索引:[1] [4] [7] [10] |
没有插入意向锁(假设情况):
1 | css复制索引:[1] [4] [7] [10] |
