MySQL中的锁

文章详细阐述了数据库事务并发存在的脏读、不可重复读和幻读问题,并介绍了MySQL的四种隔离级别,如读未提交、读已提交、可重复读和串行化。接着,文章深入解析了MySQL中的锁机制,包括意向锁、共享/排他锁、间隙锁和记录锁,以及临键锁在解决幻读问题中的作用。这些锁机制用于控制并发访问时的数据一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先说一下事务并发存在的问题

  • 脏读:事务A在执行期间读到了事务B还未提交的数据,就是脏读
  • 不可重复读:事务A在执行期间,两条同样的SQL,第一次查询id为1的记录age为18,第二次查询id为1的记录age变成了20。同样的查询,返回了不同的数据就是不可重复读
  • 幻读:事务A执行两次同样的SQL,第一次返回了三条记录,第二次返回了四条或者两条记录,结果集不一样。也就是事务A查询一个范围的结果集,另一个并发事务B在这个范围内插入 / 删除了数据并且提交了,然后事务A再次查询,两个得到的结果集不一样了,就是幻读

顺便再说一下MySQL的四大隔离级别

  • 读未提交:事务A能读到别的事务没有提交的数据,就是读未提交,解决了脏读,但是没有解决不可重复读、幻读
  • 读已提交:在读未提交的基础上解决了脏读,但还是有不可重复读、幻读
  • 可重复读:解决了脏读、不可重复读(只解决了快照读情况下),但是没有解决幻读
  • 串行化:解决了所有并发问题,因为同一时间只有一个线程可以执行

下面画一个经典表格

脏读不可重复读幻读
读未提交×××
读已提交××
可重复读×
串行化

再分类一下MySQL中的几种锁

  • 意向锁
  • 共享 / 排他 锁
  • 间隙锁
  • 记录锁
  • 临键锁

意向锁

意向锁是一种不与行级锁冲突的表级锁。未来的某个时刻,事务可能要加共享或者排它锁时,先提前声明一个意向。注意一下,意向锁,是一个表级别的锁。

因为InnoDB是支持表锁和行锁共存的,如果一个事务A获取到某一行的排他锁,并未提交,这时候事务B请求获取同一个表的表共享锁。因为共享锁和排他锁是互斥的,因此事务B想对这个表加共享锁时,需要保证没有其他事务持有这个表的表排他锁,同时还要保证没有其他事务持有表中任意一行的排他锁。

然后问题来了,你要保证没有其他事务持有表中任意一行的排他锁的话,去遍历每一行?这样显然是一个效率很差的做法。为了解决这个问题,InnoDB的设计大叔提出了意向锁。

意向锁是如何解决这个问题的呢? 我们来看下

意向锁分为两类:

  • 意向共享锁:简称IS锁,当事务准备在某些记录上加S锁时,需要现在表级别加一个IS锁。

  • 意向排他锁:简称IX锁,当事务准备在某条记录上加上X锁时,需要现在表级别加一个IX锁。
    比如:

  • select … lock in share mode,要给表设置IS锁;

  • select … for update,要给表设置IX锁;

如果一个事务A获取到某一行的排他锁,并未提交,这时候表上就有意向排他锁和这一行的排他锁。这时候事务B想要获取这个表的共享锁,此时因为检测到事务A持有了表的意向排他锁,因此事务A必然持有某些行的排他锁,也就是说事务B对表的加锁请求需要阻塞等待,不再需要去检测表的每一行数据是否存在排他锁啦。这样效率就高很多啦。

意向锁仅仅表明意向的锁,意向锁之间并不会互斥,是可以并行的,整体兼容性如下图所示:

兼容性ISIXSX
IS兼容兼容兼容不兼容
IX兼容兼容不兼容不兼容
S兼容不兼容兼容不兼容
X不兼容不兼容不兼容不兼容

共享 排他 锁

InnoDB呢实现了两种标准的行级锁:共享锁(简称S锁)、排他锁(简称X锁)。

  • 共享锁:简称为S锁,在事务要读取一条记录时,需要先获取该记录的S锁。
  • 排他锁:简称X锁,在事务需要改动一条记录时,需要先获取该记录的X锁。

如果事务T1持有行R的S锁,那么另一个事务T2请求访问这条记录时,会做如下处理:

  • T2 请求S锁立即被允许,结果 T1和T2都持有R行的S锁
  • T2 请求X锁不能被立即允许,此操作会阻塞
  • 如果T1持有行R的X锁,那么T2请求R的X、S锁都不能被立即允许,T2 必须等待T1释放X锁才可以,因为X锁与任何的锁都不兼容。

S锁和X锁的兼容关系如下图表格:

兼容性SX
S兼容不兼容
X不兼容不兼容

X锁和S锁是对于行记录来说的话,因此可以称它们为行级锁或者行锁。我们认为行锁的粒度就比较细,其实一个事务也可以在表级别下加锁,对应的,我们称之为表锁。给表加的锁,也是可以分为X锁和S锁的哈。

如果一个事务给表已经加了S锁,则:

  • 别的事务可以继续获得该表的S锁,也可以获得该表中某些记录的S锁。
  • 别的事务不可以继续获得该表的X锁,也不可以获得该表中某些记录的X锁。

如果一个事务给表加了X锁,那么

  • 别的事务不可以获得该表的S锁,也不可以获得该表某些记录的S锁。
  • 别的事务不可以获得该表的X锁,也不可以继续获得该表某些记录的X锁。

记录锁(Record Lock)

顾名思义,就是针对于记录的锁,不过MySQL的记录锁是针对于聚簇索引的,也就是主键索引。当加锁条件能匹配到索引就是行锁,否则就是表锁。

  • 如果查询条件用了索引/主键,那么select … for update就会进行 行锁
  • 如果是普通字段(没有索引/主键),那么select … for update就会进行 锁表

间隙锁(Gap Lock)

为了解决幻读问题,InnoDB引入了间隙锁(Gap Lock)。间隙锁是一种加在两个索引之间的锁,或者加在第一个索引之前,或最后一个索引之后的间隙。它锁住的是一个区间,而不仅仅是这个区间中的每一条数据。

临键锁(Next-Key Lock)

Next-key锁是记录锁和间隙锁的组合,它指的是加在某条记录以及这条记录前面间隙上的锁。说得更具体一点就是:临键锁会封锁索引记录本身,以及索引记录之前的区间,即它的锁区间是前开后闭,比如(5,10]。

如果一个会话占有了索引记录R的共享/排他锁,其他会话不能立刻在R之前的区间插入新的索引记录。

所谓的next key lock就是一个行锁(record lock)+范围锁(gap lock),比如某一个辅助索引,如果它有1,3,5这几个值,那么当我们使用next key lock的锁住class_id=1的时候,实际上锁住了(-无穷,1],或者锁住class_id=3的时候,实际上锁住的是(1,3],也就是一个左开右闭的区间。如果此时别的事务要在这个区间内插入数据,就会被阻塞住。这个锁一直到事务提交才会释放。因此,即使出现了特殊情况,也可以保证前后两次去读的内容一致,因为对这个辅助索引上的锁是:“next key lock”,他会锁住一个区间。

但是注意,对于可重复读默认使用的就是next key lock,但是对于“唯一索引”,比如主键的索引,next key lock会降级成行锁,而不会锁住一个区间。因此,如果上面的事务1的update使用的是主键,事务2也使用主键进行插入,那么实际上事务2根本不会被阻塞,可以立即插入并返回。而对于非唯一索引,next key lock则不会降级。

参考文章:https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247499275&idx=1&sn=ca72f48a290e4fd2a2ded6ef6fd045be&chksm=cf222122f855a8347b911352cebdd722b17ea45733b91ff169353c0805d9f31cea5261ef01b9&token=554600777&lang=zh_CN#rd

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值