2707 字
14 分钟
MySQL全局锁,表级锁,行级锁

MySQL的锁#

全局锁#

如果要使用全局锁 要执行下面的命令:

flush table with read lock

执行全局锁以后,数据库就变成只读状态了,插入和更新操作都会被阻塞

这个全局锁一般是用于数据库全局备份的。在备份数据库期间,不会因为数据和表结构的更新,出现备份文件的数据和预期的不一样。

可以看到会卡主

解锁以后就可以插入了

备份数据库的时候又不想停机,可以在用 mysqldump的时候加上 —single-transaction参数,就会在备份数据之前先开启事务。这种方法只适用于支持可重复读隔离级别的事务的存储引擎。

表级锁#

MySQL中的表级锁有哪些?

  • 表锁
  • 元数据锁
  • 意向锁
  • AUTO-INC锁

表锁#

如果我们相对student表加上表锁

-- 允许当前会话读取被锁定的表,但是会组织其他会话对这些表进行写操作
lock table student_t read;
-- 表级别的独占锁,也就是写锁
-- 允许当前会话对表进行读写操作,但会阻止其他会话对这些表进行任何操作
lock table student_t write;

需要注意的是,表锁除了会限制别的线程的读写外,也会限制本线程接下来的读写操作。

元数据锁#

元数据锁不需要显示调用,因为当我们对数据库表进行操作的时候,会自动给这个表加上MDL

当我们对一张表进行CRUD操作的时候,加的是MDL读锁 当我们对一张表做结构变更操作的时候,加的是MDL写锁

MDL是为了保证当用户对表执行CRUD操作的时候,防止其他线程对这个表结构做变更。

比如说,一个线程正在执行查询操作(加了MDL读锁),如果有其他线程来修改表结构,就会被阻塞,直到查询结束。 同理,一个线程在修改表结构的时候(申请了MDL写锁),其他线程的查询操作就会被阻塞,直到说表结构变更完成

意向锁#

  • 在使用InnoDB引擎的表里对某些记录加上共享锁之前,需要先在表级别上加一个意向共享锁。
  • 在使用InnoDB引擎的表里对某些记录加上独占锁之前,需要先在表级别加上一个意向独占锁。

普通的select是不会加行级锁的,因为它是用MVCC(多版本并发控制)实现的,是无锁的。

不过select也是可以对记录加共享锁和独占锁的。

//先在表上加上意向共享锁,然后对读取的记录加共享锁
select ... lock in share mode;
//先表上加上意向独占锁,然后对读取的记录加独占锁
select ... for update;

意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突,而且意向锁之间也不会发生冲突,只会和共享表锁和独占锁发生冲突。

意向锁的目的是为了快速判断表里是否有记录被加锁。 比如说,当一个事务想要对某个记录加锁时,可以先检查表级的意向锁,如果表级的意向锁是共享锁,就说明有其他事务正在读取这个表中的记录;如果是独占锁,就说明有其他事务正在修改这个表中的记录。

如果表级的意向锁是共享锁,那么其他事务可以对表上共享锁,但是不能加独占锁。如果是表级意向锁是独占锁,其他事务就不能对表上加任何锁。

AUTO-INC锁#

表里的主键通常会设置成自增的,这是通过主键字段声明 AUTO_INCREMENT 属性实现的。

之后可以在插入数据的时候,可以不指定主键的值,数据库会自动给主键赋值递增的值,这主要是通过 AUTO-INC锁实现的。

AUTO-INC锁是特殊的表锁机制,锁不是在一个事务提交后才释放,而是在执行完插入语句后就会立刻释放。

在插入数据的时候,会加一个表级别的AUTO-INC锁,然后为被 AUTO_INCREMENT 修饰的字段赋值递增的值,等插入语句执行完成后,才会把AUTO-INC锁释放掉。

那么在一个事务持有AUTO-INC锁的过程中,其他事务如果要向该表插入语句都会被阻塞,从而保证了插入数据的时候,被AUTO_INCREMENT修饰的字段的值是连续递增的。

因此,在MySQL5.1.22开始,InnoDB存储引擎提供了一种轻量级的锁来实现自增。

一样也是在插入数据的时候,会为被auto_increment修饰的字段加上轻量级锁,然后给该字段赋值一个自增的值,然后就把这个轻量级锁释放了,不需要等待整个插入语句执行完成后才释放锁。

行级锁#

InnoDB引擎是支持行级锁的,而MyISAM不支持行级锁

可以使用下面这两个方式,这种查询会加锁的语句称为锁定读。

//对读取的记录加共享锁
select ... lock in share mode;
//对读取的记录加独占锁
select ... for update;

行级锁类型#

有三类:

  • Record Lock,记录锁,也就是仅仅把一条记录锁上
  • Gap Lock 间隙锁,锁定一个范围,但是不包含记录本身
  • Next-Key Lock: Record Lock + Gap Lock的组合,锁定一个范围,并且锁定记录本身

Record Lock 记录锁#

Record Lock被称为记录锁,锁住的锁一条记录,而且记录锁是有S锁和X锁之分的。

  • 当一个事务对一条记录加了S型记录锁后,其他事务也可以继续对该记录加S型记录锁,但是不可以对该记录加X型记录锁
  • 当一个事务对一条记录加了X型记录锁后,其他事务不可以对该记录加S型记录锁,也不可对该记录加X型记录锁

Gap Lock 间隙锁#

Gap Lock被称为间隙锁,存在于可重复读隔离级别和串行化隔离级别,目的是为了解决可重复读隔离级别下幻读的现象

假设表中有一个范围id为(3,5)的间隙锁,那么其他事务就无法插入id = 4这条记录了,这样就有效地防止了幻读现象的发生。

间隙锁虽然也存在X型和S型间隙锁,但是没什么区别,间隙锁之间是兼容的,两个事务可以同时持有并包含共同间隙范围的间隙锁,并不存在互斥关系,因为间隙锁的目的是防止插入幻影记录而提出的。

Next-Key Lock 临键锁#

Next-Key-Lock称为临键锁,是Record Lock和Gap Lock的组合。锁定一个范围,并且锁定记录本身。 假设表中有个范围id为(3,5]的next-key-lock,那么其它事务既不能插入id = 4的记录,也不能修改id = 5这条记录。

所以next-key lock既能保护该记录,又能阻止其它事务将新记录插入到被保护记录前面的间隙中。

Next-key lock 是数据库中 InnoDB 存储引擎(常见于 MySQL)使用的一种锁机制,主要用于防止 幻读(Phantom Read) 问题,确保事务在可重复读(Repeatable Read)隔离级别下的一致性。它的意义在于通过结合 记录锁(Record Lock)间隙锁(Gap Lock),对索引记录及其前后的间隙进行锁定,从而避免其他事务插入或修改数据导致的幻读现象。

具体意义和作用:#

  1. 防止幻读

    • 幻读是指在同一事务中,多次执行相同查询时,由于其他事务插入了新记录,导致查询结果集发生变化。
    • Next-key lock 锁定一个索引记录及其前后的间隙,防止其他事务插入新记录到这个范围内,从而保证查询结果的稳定性。
  2. 结合记录锁和间隙锁

    • 记录锁:锁定具体的索引记录,防止其他事务修改或删除该记录。
    • 间隙锁:锁定索引记录之间的“间隙”,防止其他事务在该间隙内插入新记录。
    • Next-key lock 是两者的结合,锁定一个记录及其左侧或右侧的间隙。例如,对于索引值 10,Next-key lock 可能锁定 (5, 10] 范围(假设 5 是前一个索引值)。
  3. 提高并发控制的精度

    • Next-key lock 是一种范围锁,比表级锁更精细,能够在保证数据一致性的同时,尽量减少锁的粒度,提高并发性能。
  4. 支持可重复读隔离级别

    • 在 MySQL 的可重复读(Repeatable Read)隔离级别下,Next-key lock 是默认的锁机制,用于确保事务在多次读取时看到一致的数据快照。

工作原理:#

  • 当事务对某一行记录进行操作(例如 SELECT … FOR UPDATE 或 UPDATE),InnoDB 会锁定该记录以及其前后的间隙。
  • 例如,假设表中有一个索引列 id 包含值 10、20、30。如果事务 A 对 id = 20 加锁,Next-key lock 可能会锁定 (10, 20] 或 (20, 30] 的范围,防止其他事务插入值在该范围内的记录。

注意事项:#

  1. 性能影响:Next-key lock 锁定范围较大,可能导致锁冲突,降低并发性能。
  2. 死锁风险:多个事务竞争相同的间隙锁可能导致死锁,需要合理设计事务逻辑。
  3. 依赖索引:Next-key lock 依赖于索引。如果查询没有使用索引,可能会退化为表级锁,影响性能。

总结来说,Next-key lock 的核心意义在于通过锁定记录和间隙,防止幻读,维护事务隔离级别的一致性,同时在高并发场景下提供较好的数据保护机制。

插入意向锁#

一个事务在插入一条记录的时候,需要判断插入位置是否已被其他事务加了间隙锁(next-key lock 也包含间隙锁)。

如果有的话,插入操作就会发生阻塞,直到拥有间隙锁的那个事务提交为止(释放间隙锁的时刻),在此期间会生成一个插入意向锁,表明有事务想在某个区间插入新记录,但是现在处于等待状态。