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),对索引记录及其前后的间隙进行锁定,从而避免其他事务插入或修改数据导致的幻读现象。
具体意义和作用:
-
防止幻读:
- 幻读是指在同一事务中,多次执行相同查询时,由于其他事务插入了新记录,导致查询结果集发生变化。
- Next-key lock 锁定一个索引记录及其前后的间隙,防止其他事务插入新记录到这个范围内,从而保证查询结果的稳定性。
-
结合记录锁和间隙锁:
- 记录锁:锁定具体的索引记录,防止其他事务修改或删除该记录。
- 间隙锁:锁定索引记录之间的“间隙”,防止其他事务在该间隙内插入新记录。
- Next-key lock 是两者的结合,锁定一个记录及其左侧或右侧的间隙。例如,对于索引值 10,Next-key lock 可能锁定 (5, 10] 范围(假设 5 是前一个索引值)。
-
提高并发控制的精度:
- Next-key lock 是一种范围锁,比表级锁更精细,能够在保证数据一致性的同时,尽量减少锁的粒度,提高并发性能。
-
支持可重复读隔离级别:
- 在 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] 的范围,防止其他事务插入值在该范围内的记录。
注意事项:
- 性能影响:Next-key lock 锁定范围较大,可能导致锁冲突,降低并发性能。
- 死锁风险:多个事务竞争相同的间隙锁可能导致死锁,需要合理设计事务逻辑。
- 依赖索引:Next-key lock 依赖于索引。如果查询没有使用索引,可能会退化为表级锁,影响性能。
总结来说,Next-key lock 的核心意义在于通过锁定记录和间隙,防止幻读,维护事务隔离级别的一致性,同时在高并发场景下提供较好的数据保护机制。
插入意向锁
一个事务在插入一条记录的时候,需要判断插入位置是否已被其他事务加了间隙锁(next-key lock 也包含间隙锁)。
如果有的话,插入操作就会发生阻塞,直到拥有间隙锁的那个事务提交为止(释放间隙锁的时刻),在此期间会生成一个插入意向锁,表明有事务想在某个区间插入新记录,但是现在处于等待状态。