锁的类型
MySQL中的锁,按照锁的粒度分,分为以下三类:
- 全局锁:锁定数据库中的所有表。
- 表级锁:每次操作锁住整张表。
- 行级锁:每次操作锁住对应的行数据。
全局锁
相关的SQL语句
-
使用全局锁
1
flush tables with read lock
-
释放全局锁
1
unlock tables
使用效果
执行后,整个数据库就处于只读状态了,这时其他线程执行以下操作,都会被阻塞:
- 对数据的增删改操作,比如 insert、delete、update等语句;
- 对表结构的更改操作,比如 alter table、drop table 等语句。
应用场景
全局锁主要应用于做 全库逻辑备份 ,这样在备份数据库期间,不会因为数据或表结构的更新,而出现备份文件的数据与预期的不一样。
缺点和解决方案
缺点 :使用全局锁,意味着整个数据库都是只读状态。那么如果数据库里有很多数据,备份就会花费很多的时间,关键是备份期间,业务只能读数据,而不能更新数据,这样会造成业务停滞。
解决方案 :将事务隔离级别改为可重复读,在备份数据库的同时开启事务。底层原理是开启事务,会先创建 Read View,然后整个事务执行期间都在用这个 Read View,而且由于 MVCC 的支持,备份期间业务依然可以对数据进行更新操作。因为在可重复读的隔离级别下,即使其他事务更新了表的数据,也不会影响备份数据库时的 Read View
表级锁
表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低
表锁
- 读锁:允许多个事务同时读取被锁定的表,但不允许任何事务进行写操作。
- 写锁:允许一个事务对表进行读写操作,其他事务不能对该表进行任何操作(读或写)。
表锁除了会限制别的线程的读写外,也会限制本线程接下来的读写操作。
- 读锁:允许当前会话读取被锁定的表,但阻止其他会话对这些表进行写操作,同时 本线程不能访问其他表
- 写锁:允许当前会话对表进行读写操作,但阻止其他会话对这些表进行任何操作(读或写)。
应用场景
- 在进行大规模的数据导入、导出或删除操作时,为了防止其他事务对数据进行并发操作,可以使用表锁。
- 在进行表结构变更(如添加列、修改列类型)时,为了确保变更期间没有其他事务访问或修改该表,可以使用表锁。
元数据锁(MDL)
不需要显示的使用 MDL,因为当我们对数据库表进行操作时,会自动给这个表加上 MDL
读锁 (MDL_SHARED):当一个事务需要读取表的元数据时(如执行 SELECT 操作),会获取读锁。 多个事务可以同时持有读锁,不会互相阻塞。
写锁 (MDL_EXCLUSIVE):当一个事务需要修改表的元数据时(如执行 ALTER TABLE 操作),会获取写锁。 写锁会阻塞其他任何读锁和写锁,确保独占访问。
MDL 是为了保证当用户对表执行 CRUD 操作时,防止其他线程对这个表结构做了变更。
锁的释放时机:锁在事务提交后才会释放,这意味着 事务执行期间,MDL 是一直持有的
意向锁
假设业务上用到了表锁,那么表锁和行锁之间肯定会冲突,当 InnoDB 加表锁的时候,需要判断表里面是否已经有行锁,而意向锁就是用来协调表锁和行锁的
- 在使用 InnoDB 引擎的表里对某些记录加上共享锁之前,需要先在表级别加上一个意向共享锁
- 在使用 InnoDB 引擎的表里对某些纪录加上独占锁之前,需要先在表级别加上一个意向独占锁
作用
- 协调表锁和行锁:防止同时对表加排他锁和行加行锁造成冲突
- 快速判断表里是否有记录被加锁
- 如果没有意向锁,那么加独占表锁时,就需要遍历表里所有记录,查看是否有记录存在独占锁,这样效率会很慢。
- 那么有了意向锁,由于在对记录加独占锁前,先会加上表级别的意向独占锁,那么在加独占表锁时,直接查该表是否有意向独占锁,如果有就意味着表里已经有记录被加了独占锁,这样就不用去遍历表里的记录。
意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突,而且意向锁之间也不会发生冲突,只会和共享表锁和独占表锁发生冲突。
行级锁
记录锁
相关SQL语句
|
|
记录锁顾名思义就是锁住当前的记录,它是作用到索引上的。所以以 记录锁总是锁定索引记录 。
间隙锁
Gap Lock 称为间隙锁,只存在于 可重复读 隔离级别,目的是为了解决可重复读隔离级别下幻读的现象。间隙锁用于在范围查询时锁定记录之间的“间隙”,防止其他事务在该范围内插入新记录
间隙锁之间是兼容的,即两个事务可以同时持有包含共同间隙范围的间隙锁,并不存在互斥关系,因为间隙锁的目的是防止插入幻影记录而提出的。
临键锁
临键锁是记录锁和间隙锁的组合,锁定一个范围,并且锁定记录本身。临键指的是间隙加上它右边的记录组成的 左开右闭区间
插入意向锁
一个事务在插入一条记录的时候,需要判断插入位置是否已被其他事务加了间隙锁(next-key lock 也包含间隙锁)。
如果有的话,插入操作就会发生 阻塞 ,直到拥有间隙锁的那个事务提交为止(释放间隙锁的时刻),在此期间会生成一个 插入意向锁 ,表明有事务想在某个区间插入新记录,但是现在处于等待状态。
判断到插入的位置已经被事务 A 加了间隙锁,于是事务会生成一个插入意向锁,然后将锁的状态设置为等待状态
行级锁的加锁机制
加锁的SQL语句
- update 和 delete 操作都会加行级锁,且锁的类型都是独占锁(X型锁)。
- 普通的 select 语句是不会对记录加锁的
- in share mode :加读锁
- for update:加写锁
加锁机制
加锁的对象是 索引 ,加锁的基本单位是 临键锁 ,它是由记录锁和间隙锁组合而成的,临键锁是前开后闭区间,而间隙锁是前开后开区间。
唯一索引等值查询
- 当查询的记录是存在的,在索引树上定位到这一条记录后,将该记录的索引中的 next-key lock 会 退化成记录锁。
- 由于主键具有唯一性,所以 其他事务插入的时候,会因为冲突,导致无法插入新记录 。这样事务 A 在多次查询 id = 1 的记录的时候,不会出现前后两次查询的结果集不同,也就避免了幻读的问题。
- 由于对唯一索引加了记录锁,其他事务无法删除该记录 ,这样事务 A 在多次查询 记录的时候,不会出现前后两次查询的结果集不同,也就避免了幻读的问题
- 当查询的记录是不存在的,在索引树找到第一条大于该查询记录的记录后,将该记录的索引中的 next-key lock 会 退化成间隙锁。
唯一索引范围查询
当唯一索引进行范围查询时,会对每一个扫描到的索引加 next-key 锁,然后如果遇到下面这些情况,会退化成记录锁或者间隙锁
- 情况一:针对「大于等于」的范围查询,因为存在等值查询的条件,那么如果等值查询的记录是存在于表中,那么该记录的索引中的 next-key 锁会退化成记录锁。
- 情况二:针对「小于或者小于等于」的范围查询,要看条件值的记录是否存在于表中:
- 当条件值的记录不在表中,那么不管是「小于」还是「小于等于」条件的范围查询,扫描到终止范围查询的记录时,该记录的索引的 next-key 锁会退化成间隙锁,其他扫描到的记录,都是在这些记录的索引上加 next-key 锁。
- 当条件值的记录在表中,如果是「小于」条件的范围查询,扫描到终止范围查询的记录时,该记录的索引的 next-key 锁会退化成间隙锁,其他扫描到的记录,都是在这些记录的索引上加 next-key 锁;如果「小于等于」条件的范围查询,扫描到终止范围查询的记录时,该记录的索引 next-key 锁不会退化成间隙锁。其他扫描到的记录,都是在这些记录的索引上加 next-key 锁。