MySQL中的锁
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
首先说一下事务并发存在的问题
- 脏读事务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对表的加锁请求需要阻塞等待不再需要去检测表的每一行数据是否存在排他锁啦。这样效率就高很多啦。
意向锁仅仅表明意向的锁意向锁之间并不会互斥是可以并行的整体兼容性如下图所示
兼容性 | IS | IX | S | X |
---|---|---|---|---|
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锁的兼容关系如下图表格
兼容性 | S | X |
---|---|---|
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则不会降级。