MySQL的锁机制

锁的类型

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语句

1
2
3
4
5
//对读取的记录加共享锁
select ... lock in share mode;

//对读取的记录加独占锁
select ... for update;

记录锁顾名思义就是锁住当前的记录,它是作用到索引上的。所以以 记录锁总是锁定索引记录

间隙锁

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 锁。
最后更新于 2025-04-16 14:45 UTC
그 경기 끝나고 좀 멍하기 있었는데 여러분 이제 살면서 여러가
使用 Hugo 构建
主题 StackJimmy 设计