Mysql系列(四)彻底理解MVCC+行锁+表锁+间隙锁

文章目录

  • 一. 什么是MVCC
  • 二.什么是行锁、表锁、间隙锁
  • 三. MVCC与各种锁的关系
  • 四. MVCC的实现原理
    • 4.1 多版本
    • 4.2 undo log
    • 4.2 readview

一. 什么是MVCC

MVCC(Multi-Version Concurrency Control),即多版本并发控制。不使用锁,主要是用来提高数据库的并发性能;算是一种概念,不同的数据库有不同的实现方式,本文主要介绍mysql的innodb引擎中的实现方式。

在mysql的innodb中,前面我们有篇文章《Mysql系列(二)Mysql事务四大隔离级别详解&演示》分析了4种隔离级别,以及每种隔离级别下导致的问题,脏读、不可重复读、幻读。其中不可重复读、幻读就是使用MVCC来解决的。

二.什么是行锁、表锁、间隙锁

首先锁的存在,目的是为了在并发场景下,保持数据的安全、一致。
并发场景有:

  • 读-读 :此并发场景不需要进行并发控制,也就是不需要加锁。
  • 读-写 :此并发场景需要并发控制,不然就会出现脏读,幻读,不可重复读的问题。
  • 写-写 :此并发场景需要并发控制,不然就会出现更新丢失的问题。

进行并发控制,常规手段就是加锁,不管是咋java业务代码中,还是mysql数据库本身,都有实现自己的锁,其中mysql的锁有以下几种:

  • 行锁:锁住表中的一行;比如 update user set name=‘张三’ where id=1;会锁住id=1的那一行数据,其他事务再想更新,就只能等前一个事务释放锁。
  • 表锁:锁住整个表,比如update user set name=‘张三’;由于没有加where条件,此更新sql会对整个表进行更新,也就是会锁住整个表。
  • 间隙锁:比如事务A执行update user set name=‘张三’ where id >1 and id<4; 假如表中只有id=1、2 两条数据,A事务还没提交,那么此时事务B再次插入一条id=3的数据,理论上是允许的,但是实际上是B只能等A提交,因为事务A执行的是id>1and id<4,范围涵盖了id=3的,也即是把id=3的这个间隙也给锁了,叫做间隙锁

除了这3种锁,还有乐观锁、悲观锁、记录锁、自增锁、意向锁;

三. MVCC与各种锁的关系

既然可以使用行锁、表锁、间隙锁来保证数据操作的安全性,那么还要MVCC的出现是为何呢? 实际是因为在性能方面还有优化的空间

虽然使用锁可以保证数据安全,但是毕竟加了锁就意味着并发性能的降低,因此,能不使用锁就尽量不使用锁。在某些场景下,MVCC可以在比使用锁更快。

在读-读、读-写、写-写这3种并发场景中,读-写 可以不使用锁,而是使用MVCC来实现数据的并发操作以及安全一致性

因此,mysql是同时使用了MVCC+行锁、表锁、间隙锁来保证了数据安全,又尽可能大的实现了性能最优化。

四. MVCC的实现原理

4.1 多版本

那么不使用锁,MVCC是如何更高效的解决读-写这种并发场景下的数据安全呢?
我们知道,事务在执行失败时,会将数据回滚为上个版本,而MVCC叫做多版本并发控制,核心概念就在版本上,也就是说数据库存储了多个版本的数据。

多个版本整体上分为两类:最新版本历史版本
这也牵扯出来另外两个概念:

  • 快照读:读取的是数据库种历史版本的数据;
    常规的select * from user ;属于是快照读;
  • 当前读:读取的是数据库种最新版本的数据;当前读的操作有:
    select * from user in share mode(共享锁),;
    select * from user for update;
    update, insert ,delete(排他锁)

那么多个版本,mysql是如何存储的呢?

innodb存储引擎中,我们存在表中的数据,除了我们设置的业务字段,另外还有3个默认字段,如下图末尾3个:
Mysql系列(四)彻底理解MVCC+行锁+表锁+间隙锁_第1张图片

  • DB_TRX_ID: 事务id,存的是创建事务的id,或者最后一次更新事务的id;
  • DB_ROW_ID: 主键id,建表时如果没设置主键,则此字段会成为主键发挥作用;
  • DB_ROLL_PTR: 回滚指针,指向上一个版本的数据,如果没有上个版本,则为null。

其中,DB_ROLL_PTR结合undo log实现。

4.2 undo log

undo log称为回滚日志,是InnoDB MVCC事务特性的重要组成部分,存在形式就是一种日志文件。

undo log的数据结构,非常复杂,可以简单理解为链表,链表头部存储最新版本,尾部存储最早版本。通过遍历链表,就可以找到对应版本的数据。
Mysql系列(四)彻底理解MVCC+行锁+表锁+间隙锁_第2张图片
大多数对数据的变更操作包括INSERT/DELETE/UPDATE,其中,

  • INSERT操作使用insert_undo,因为新插入就只有一个版本,因此产生的Undo日志可以在事务提交后直接删除;
  • 而对于UPDATE/DELETE则需要维护多版本信息,在InnoDB里,UPDATE和DELETE操作产生的Undo日志被归成一类,即update_undo;mvcc使用的undolog,就是update_undo。

只有undolog,还不足以实现mvcc,因为既然undolog维护了那么多版本,遍历的时候,应该去找哪一个版本呢?这里必然牵扯到一种规则,这种规则在不同的数据库隔离级别下是不一样的。

innodb具体实现的时候,使用了一种叫做readview的定西。

4.2 readview

readview称为读视图,是事务在进行快照读的时候产生的,算是一种数据结构。

readview中包含3个参数:

  • trx_list :系统活跃的(事务在执行,还没有提交)事务id列表;
  • up_limit_id :trx_list列表中最小的事务id;
  • low_limit_id :ReadView生成时刻系统尚未分配的下一个事务ID,也就是目前已出现过的事务ID的最大值+1;比如当前事务id已经分配到了4,那么low_limit_id的值就是5;

每个事务在进行自己的快照读时,都会产生自己事务对应的readview。

例如下图中:
Mysql系列(四)彻底理解MVCC+行锁+表锁+间隙锁_第3张图片
有4个事务同时在执行,事务id分别为1,2,3,4;其中,1,2,3还没有提交,4已经提交,那么当事务2在进行快照读时,产生一个readview,其trx_list存的就是活跃的1,2,3,如下图:
Mysql系列(四)彻底理解MVCC+行锁+表锁+间隙锁_第4张图片
此时,最小的事务id是1,最大的事务id是5 (尚未分配的下一个id),当前最新的事务id是4 (事务4的id)。

可见性算法
上图中右侧黄色部分就是本次查询到底该取哪个版本数据的规则,也叫可见性算法;

查找过程:
遍历undolog的最新数据到最老数据,逐个判断每条log的事务id,当作DB_TRX_ID,然后使用上图中黄色可见性算法比对,最终确定当前遍历的数据是不是目标数据。

读视图的产生时机
在不同的隔离级别下,readview产生的时机是不同的:

  • RC :每次进行快照读的时候,都会产生新的readview;
  • RR :只有在第一次进行快照读的时候,才会产生readview,之后的操作都会使用第一次生成的readview。如果此事务中间发生了update等当前读操作,也会生成新的readview。

Mysql系列(四)彻底理解MVCC+行锁+表锁+间隙锁_第5张图片

你可能感兴趣的:(mysql,mysql)