一、MySQL锁分类
- 锁粒度维度划分
- ①表锁
- 全局锁:加锁之后,整个数据库只允许读,不允许任何写操作
- 元数据锁/MDL锁:基于表的元数据加锁,用于在修改表结构时,禁止其他事务做任何操作
- 意向锁:InnoDB为了支持多粒度锁,为兼容表锁和行锁而设计
- 自增锁:为了提升自增ID并发插入性能而设计的
- ②页面锁:Berkeley DB存储引擎支持的一种锁粒度
- ③行锁
- 记录锁:行锁
- 间隙锁:InnoDB解决幻读问题的一种锁机制,锁的是一段范围内的行数据
- 临键锁:间隙锁升级版,同时具备记录锁+间隙锁功能
- ①表锁
- 互斥性维度划分
- 共享锁
- 排他锁
- 共享排它锁
- 操作类型维度划分
- 读锁
- 写锁
- 加锁方式
- 显示锁:编写SQL时,手动指定加锁粒度
- 隐式锁:执行SQL时,根据隔离级别自动加锁
- 思想维度
- 乐观锁
- 悲观锁
二、MySQL表锁
2.1 元数据锁
Meta Data Lock元数据所,其针对于.frm(存储表结构的文件)文件中的元数据进行加锁,主要用于在更改表结构时,阻断其他事务的CRUD操作
2.2 意向锁
事务A在对行数据进行加锁时,如果事务B需要对表进行加锁,但是由于事务A已经对行数据加了写锁,那么此时事务B对表添加写锁的操作显然是不允许的,但是事务B如何得知事务A加锁操作呢,如果按照传统的遍历方式,在数据量过大的情况下显然是不现实的,那么自然需要有个地方记录了事务A对表加了哪些锁,此时意向锁便诞生了,事务A对行数据添加写锁的同时还需要对表添加插入意向锁,它不会阻断其他事务对表添加行锁,但是在添加表级锁时需要判断是否存在意向锁再决定能否加锁成功
2.3 自增锁
自增锁,专门为提升自增ID的并发插入性能而设计的,仅为设置了自增ID的表服务,同时其也有不同级别
- innodb_autoinc_lock_mode=0:传统模式,事务A、B想要获取自增锁插入数据,事务B只能阻塞等待
- innodb_autoinc_lcok_mode=1:连续模式(8.0之前默认模式),对于能够提前确定数量的插入语句,不会获取自增锁,改为采用轻量级锁Mutex-Lock来防止自增至重复分配
- innodb_autoinc_lock_mode=2:交错模式(8.0之后默认模式),不同事务之间插入数据时,自增列的值时交错插入,即如果存在事务A、B需要插入数据,先预分配A:2n的ID,B为2n-1的ID
全局锁
基于整个数据库加锁,加锁之后整个数据库只允许读,不允许任何写操作,一般在数据库数据备份时使用
-- 获取全局锁命令
FLUSH TABLES WITH READ LOCK;
-- 释放全局锁命令
UNLOCK TABLES;
三、MySQL行锁
3.1 InnoDB如何实现行锁
由于InnoDB支持聚簇索引,将数据存储在树种的叶子节点,因此行数据对应叶子节点,因此可以对每一条数据进行加锁,其他存储引擎由于不支持聚簇索引,表数据存储在一起,只能对表进行加锁,也因此InnoDB只有在索引命中的时候才会添加行锁
3.2 记录锁
-- 获取行级别的共享锁
select * from `account` where id=1 lock in share mode;
-- 获取行级别的排它锁
select * from `account` where id=1 for update;
3.3 间隙锁
间隙锁是对行锁的一种补充,主要用于解决幻读问题,幻读问题主要是因为在查询一段范围内的数据时,由于其他事务在这个范围内插入了新的数据导致了读取数据前后数目不一致,使用间隙锁锁住了这段范围,那么其他事务就无法插入新的数据,从而解决幻读问题
3.4 临键锁
加锁后,即锁定左闭右开的区间,是对于间隙锁和记录锁的综合,在InnoDB中,除开一些特殊情况,当尝试对一条数据加锁时,默认使用的是临键锁
3.5 插入意向锁
一种间隙锁,当多个事务在同一区间插入位置不同的多条数据时,事务之间不需要互相等待,在RR级别下,使用插入意向锁来控制和解决并发插入
3.6 行锁粒度变化
在某些特殊情况下行锁是会粗化的
- 内存中专门分配存储锁对象的空间满之后,行锁会粗化成表锁
- 做范围写操作,加锁行数较多,此时行锁开销大,会粗化成表锁
四、MySQL5.7-共享排它锁
4.1 SMO问题
在SQL执行期间,一旦更新操作触发B+Tree叶子节点分裂,就会给整棵树B+Tree加排它锁,阻塞更新和读操作
4.2 MySQL5.7中读操作执行流程
- ①读取数据之前对B+Tree加共享锁
- ②基于树检索数据过程中,对所有走过节点加共享锁
- ③找到需要读取的目标叶子节点后,加共享锁,释放②中的所有共享锁
- ④读取最终的目标叶子节点中数据,完成后释放共享锁
4.3 5.7乐观写入流程
- ①对B+Tree加共享锁
- ②检索数据,走过子节点加共享锁
- ③目标叶子节点加排它锁,释放②中共享锁
- ④修改完成后,释放排它锁
4.4 5.7悲观写入流程
- ①对B+Tree加共享排他锁
- ②当前事务阻塞其他尝试更改树结构的事务
- ③找到目标叶子节点,加排它锁,释放①中SX锁
- ④执行SMO操作,完成后释放③中分时上的排它锁 由于上述操作SX锁在找到需要修改的节点后会释放,后续事务执行SMO操作不会阻塞,解决了5.6版本发生的SMO操作时阻塞一切读写操作的问题
参考资料
- 《MySQL实战45讲》
- 《MySQL锁机制:高并发场景下该如何保证数据读写的安全性》