在MySQL中,有多种锁类型,我们先了解三种概念的锁,以便对接下来的内容有更好理解。
- 表级锁(Table Lock):对整个表加锁,其他事务无法修改或读取该表的数据,但可以对其他表进行操作。
- 页级锁(Page Lock):对数据页(通常是连续的几个行)加锁,控制并发事务对该页的访问。适用于数据较大且并发量较高的场景。
- 行级锁(Row Lock):对单个行加锁,只锁定需要修改的数据行,其他行可以被同时修改或读取。并发性高,但锁管理较复杂。
一般来说,锁的粒度越大,性能就越差。因为粒度一大,那么发生竞争的可能性就越大,进入锁等待的概率也越大。
例如MyISAM引擎主要使用表锁设计,因此它的并发插入性能显然会很慢,因为每次插入都要加表锁,其他对此表进行操作的事务都会受到影响。而SQL Server起初是侧重于页锁,不过后续开始支持行级锁,但因为设计的缘故导致锁的资源很稀有,锁越多开销越大,最后促成锁升级变为表锁。
而InnoDB主要采用行级锁,且行级锁没有相关额外的开销,能够获得很好的并发性。
innoDB的三种行级锁就是本文的重点:行锁(Record Lock)、间隙锁(Gap Lock)、临键锁(Next-Key Lock)。
记录锁(Record Lock)
InnoDB的存储上是一颗聚簇索引树,因此,innoDB的所谓行锁,实际上就是在索引的节点上加锁。记录锁可以防止多个会话同时修改同一条记录,从而保证数据的一致性。
间隙锁(Gap Lock)
用来锁住一个范围内的间隙(即两个索引值之间的空隙),但不包括索引记录本身。
一般情况下,间隙锁是在使用范围查询或者使用唯一索引进行插入时自动创建的。在可重复读(REPEATABLE READ)或者串行化(SERIALIZABLE)的隔离级别下,MySQL会自动创建间隙锁。
间隙锁的存在可以防止幻读问题的产生。由于间隙锁会限制一定范围内的插入操作,避免了其他事务在该范围内插入新的记录,从而保证了一致性。
需要注意的是,间隙锁只锁住范围,并不锁记录本身。
像上图,(E-F)表示锁住E-F之间的记录,但E、F本身并不加锁,如果此时有另外一个事务操作E、F记录是可以成功的,但是如果是在E和F之间插入数据,则会失败。
比较特殊的,由于此时E是最小值,G是最大值,当想锁住比E小的范围时,用(-∞,E)表示;当想锁住比G大的范围时,用(G,+∞)表示。
临键锁
MySQL的临键锁(Next-Key Locks)是一种用于保护事务并发操作的锁机制。它结合了记录锁和间隙锁,能够实现在并发环境下防止幻读的效果。
临键锁的工作原理如下:
- 当事务对一个记录进行读取或写入操作时,会对该记录加上记录锁(行锁)。
- 若事务需要对一个范围进行操作(比如读取一段记录或者插入新记录),会对这个范围加上间隙锁(区间锁)。
- 锁的释放顺序与加锁顺序相反,即先释放间隙锁再释放记录锁。
大白话速记就是:临键锁=记录锁+间隙锁,左开右闭(不锁左边,锁右边)。
临键锁的作用如下:
- 防止幻读:在RR(可重复读)隔离级别下,临键锁会对查询范围的间隙进行加锁,以防止其他事务在范围内插入新记录,从而导致幻读现象。
- 提高并发度:临键锁的可精确加锁范围带来了更高的并发度,避免了使用传统锁(如表锁、页锁)时的大范围加锁。
不得不多嘴一句,这个临键锁的命名真是太抽象了,刚开始知道的时候,无论如何也不能通过这个名称知道,这是个什么实现。
看到这,可能有人比较疑惑,前面提到的ReadView的存在看起来已经解决了事务隔离,怎么还会有锁的存在?
事实上,ReadView可以理解为第一道关卡,类似于布隆过滤器,如果满足不了ReadView规则的,一定满足不了锁规则;如果满足了ReadView,不一定满足锁。ReadView更多只是从「读」的角度触发,提供一种快速判读的机制,但是解决不了互相写冲突的问题!
因此,锁是一种更为底层同时逻辑偏重的保证机制。
总结:
- InnoDB有三种锁:记录锁、间隙锁、临键锁。
- 记录锁只锁住某个具体节点;间隙锁锁住区间但不包含记录本身;临键锁=记录锁+间隙锁,主要用来解决不可重复读的问题
上一篇:诚意满满之MySQL实现事务隔离的秘诀:锁与MVCC
刚好讲到了Record Lock、Gap Lock和Next-Key Lock,那下一篇便讲一讲在实际工作中容易遇到的,如何排查和解决死锁的问题。
欢迎关注~