【MySQL】MVCC(多版本并发控制)详解

MVCC

MVCC概述

MVCC,全称 Multi-Version Concurrency Control ,即多版本并发控制。MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。

MVCC就是在ReadCommitted(读已提交)、RepeatableRead(可重复读) 隔离级别,不加锁的情况下,解决并发事务的脏读、幻读和不可重复读问题。

MVCC 在 MySQL InnoDB 中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。

什么是当前读、快照读?

当前读: 读到的数据都是最新的数据,像排他锁(又称写锁updateinsertdeleteselect for update共享锁(又称读锁select lock in share mode,这些操作都是当前读。

快照读: 可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。像不加锁的select操作就是快照读,如果隔离级别是串行化,快照读会退化成当前读。

数据库并发场景

  • 读-读: 不存在任何问题。
  • 读-写: 有线程安全问题,可能出现脏读、幻读、不可重复读。
  • 写-写: 有线程安全问题,可能存在更新丢失等。

MVCC(多版本并发控制)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。

MVCC实现原理

MVCC 的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的 3个隐式字段,undo日志版本链 , Read View 来实现的。

三个隐式字段

数据库中的每行记录都有DB_TRX_ID(事务id)DB_ROLL_PTR(回滚指针)DB_ROW_ID(隐藏主键)这三个字段。

DB_TRX_ID(事务id)DB_ROLL_PTR(回滚指针)DB_ROW_ID(隐藏主键)
记录创建这条记录,最后一次修改该记录事务的ID。每处理一个事务,其值自动 +1回滚指针,指向这条记录的上一个版本(存储于 rollback segment 里)隐含的自增 ID(隐藏主键),如果数据表没有主键,InnoDB 会自动以DB_ROW_ID产生一个聚簇索引

undo日志版本链

undo日志版本链是指一行数据被多个事务依次修改过后,在每个事务修改完后,Mysql会保留修改前的数据undo回滚日志,并且用两个隐藏字段DB_TRX_ID(事务id)DB_ROLL_PTR(回滚指针)把这些undo日志串联起来形成一个历史记录版本链。

undo日志分类:

undo log 主要分为两种:

  • insert undo log 代表事务在 insert 新记录时产生的 undo log,只在事务回滚时需要,并且在事务提交后可以被立即丢弃
  • update undo log 事务在进行 updatedelete时产生的 undo log ;不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被 purge 线程统一清除。
undo日志版本链流程图

1)事务A,在employee表插入了一条数据,name为zhangsan、age为22、主键id为1、事务id为100、回滚指针和隐式主键为null。这条日志会在insert undo log中。

在这里插入图片描述
2)事务B对employee表主键id为1的记录做了修改,修改了name字段为lisi。(在事务B之前都没有事务修改过这条数据)

  • 拷贝改行数据到undo log中,作为旧纪录,既在 undo log 中有当前行的拷贝副本。
  • 修改name字段为lisi,假设事务id自增到101,回滚指针指向拷贝的副本。

在这里插入图片描述

3)事务C对employee表主键id为1的记录做了修改,修改了age字段为18。

  • 在拷贝数据到undo log中,发现这行数据已经有undo日志版本链了。修改后的数据,假设事务id为102。回滚指针就会指向undo日志版本链最后修改的那一条记录。

在这里插入图片描述

Read View 读视图

在可重复读隔离级别,当事务开启,执行任何查询sql时会生成当前事务的一致性视图read-view该视图在事务结束之前永远都不会变化(如果是读已提交隔离级别在每次执行查询sql时都会重新生成read-view)这个视图由执行查询时所有未提交事务id数组(数组里最小的id为min_id)和已创建的最大事务id+1(max_id)组成,事务里的任何sql查询结果需要从对应版本链里的最新数据开始逐条跟read-view做比对从而得到最终的快照结果。

Read View 主要是用来做可见性判断的,可见性算法如下 (trx_id事务id)

绿色部分( trx_id<min_id ):已提交的事务
红色部分( trx_id>max_id ):未开始的事务
黄色部分(min_id <=trx_id<= max_id):活跃的事务。

  1. 如果 row trx_id 落在绿色部分( trx_id<min_id ),表示这个版本是已提交的事务生成的,这个数据是可见的;
  2. 如果 rowtrx_id等于当前事务的trx_id,这个数据是可见的;
  3. 如果 rowtrx_id 落在红色部分( trx_id>=max_id ),表示这个版本是由将来启动的事务生成的,是不可见的;
  4. 如果 rowtrx_id 落在黄色部分(min_id <=trx_id<max_id),那就包括两种情况
    a. 若 rowtrx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见
    b. 若 rowtrx_id 不在视图数组中,表示这个版本是已经提交了的事务生成的,可见

对于删除的情况可以认为是update的特殊情况,会将版本链上最新的数据复制一份,然后将trx_id修改成删除操作的trx_id,同时在该条记录的头信息(record header)里的(deleted_flag)标记位写上true,来表示当前记录已经被删除,在查询时按照上面的规则查到对应的记录如果delete_flag标记位为true,意味着记录已被删除,则不返回数据。

关于readview和可见性算法的原理解释

readview和可见性算法其实就是记录了sql查询那个时刻数据库里提交和未提交所有事务的状态。

要实现RR隔离级别,事务里每次执行查询操作readview都是使用第一次查询时生成的readview,也就是都是以第一次查询时当时数据库里所有事务提交状态来比对数据是否可见,当然可以实现每次查询的可重复读的效果了。

要实现RC隔离级别,事务里每次执行查询操作readview都会按照数据库当前状态重新生成readview,也就是每次查询都是跟数据库里当前所有事务提交状态来比对数据是否可见,当然实现的就是每次都能查到已提交的最新数据效果了。

RepeatableRead(可重复读)MVCC可见性算法示例

假设有一张account表,account表有主键idnamebalance三个字段。最开始这张表里面的数据是:
在这里插入图片描述

1)事务trx_id = 100,修改了balance字段为200,此时事务100还未提交
2)事务trx_id = 200,修改了balance字段为300,此时事务200还未提交
3)事务trx_id = 300,修改了balance字段为500,此时事务300已经提交

此时的undo log日志版本链如下图:

在这里插入图片描述
4)事务trx_id = 400,执行select语句查询id=1,balance字段的数据,数据库为该行数据生成一个Read View读视图,假设数据看没有其它事务了,此时的readview数组:[100,200],min_id:100,max_id:400+1 = 401

此时的row最新的trx_id为300,因为事务400未作修改操作。从undo log日志版本链最后修改的,一直往上查询。根据上面的可见性算法分析:

  1. 先判断trx_id<min_id(300<100)不满足条件
  2. 再判断trx_id=create_trx_id(当前事务id),(300=400)不满足条件
  3. 再判断trx_id>=max_id(300>=401)不满足条件
  4. 再判断min_id <=trx_id<max_id(100<=300<401)满足条件
  5. trx_id=300,很显然不在readview数组[100,200]中,可见

得出结论:事务400查询的数据balance字段为500

5)事务trx_id = 200,又修改了balance字段为800,此时事务200已提交

此时的undo log日志版本链如下图:

在这里插入图片描述

6)事务trx_id = 400,又执行select语句查询id=1,balance字段的数据。在可重复读隔离级别下,不会重新生成Read View读视图,还是用的之前的Read View读视图。此时的readview数组:[100,200],min_id:100,max_id:400+1 = 401

此时的row最新的trx_id为200,同步骤4分析:

  1. 先判断trx_id<min_id(200<100)不满足条件
  2. 再判断trx_id=create_trx_id(当前事务id)(200=400)不满足条件
  3. 再判断trx_id>=max_id(200>=401)不满足条件
  4. 再判断min_id <=trx_id<max_id(100<=200<401)满足条件
  5. trx_id=200,很显然在readview数组[100,200]中,不可见

上面判断出最新修改的undo log日志不满足,继续往上查询,同理可以推断出,事务400查询的数据balance字段为500

ReadCommitted(读已提交)MVCC可见性算法示例

读已提交和可重复读是类似的,读已提交就是每次执行select,就会重新生成Read View读视图。

上面的我就不复制了,唯一不同的就是步骤6,

6)事务trx_id = 400,又执行select语句查询id=1,balance字段的数据。当前的活跃事务就只剩下事务100了,此时的readview数组:[100],min_id:100,max_id:400+1 = 401

此时的row最新的trx_id为200:

  1. 先判断trx_id<min_id(200<100)不满足条件
  2. 再判断trx_id=create_trx_id(当前事务id),(200=400)不满足条件
  3. 再判断trx_id>=max_id(200>=401)不满足条件
  4. 再判断min_id <=trx_id<max_id(100<=200<401)满足条件
  5. trx_id=200,很显然在readview数组[100,200]中,可见

得出结论:事务400查询的数据balance字段为800

参考文章:
【MySQL笔记】正确的理解MySQL的MVCC及实现原理

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: MySQLMVCC(Multi-Version Concurrency Control)机制是通过为每个读操作创建一个版本(Version)并保留旧版本来实现的。这个机制允许多个事务同时访问同一数据行,同时确保它们不会互相干扰或产生冲突。 MVCC在MySQL中的实现方式是,对于每一行数据,在表中存储一个隐藏的系统版本号(system versioning),并将每个操作(包括SELECT查询)的时间戳与该行的版本号进行比较。当读取一行数据时,MySQL会根据当前的事务时间戳和行的版本号来决定该行是否可见。如果行的版本号早于当前事务的时间戳,则说明该行是旧版本,不可见;如果行的版本号晚于当前事务的时间戳,则说明该行是新版本,可见。 在MVCC机制下,读操作不会阻塞写操作,写操作也不会阻塞读操作。因此,MVCC机制可以提高并发性能和可伸缩性,使得多个事务可以同时访问同一数据库而不会产生锁定和阻塞问题。 但是,MVCC机制也有一些限制。例如,如果事务A在读取某个数据行的同时,事务B修改了该行的值,那么事务A在提交时就会检测到该数据行已经被修改,从而回滚该操作。此外,MVCC机制也会占用更多的存储空间来存储旧版本的数据行。 ### 回答2: MySQLMVCC(多版本并发控制)是一种用于处理并发访问的机制。MVCC是通过在数据库的各种操作(如事务的开启、读取和写入)中使用隐藏的时间戳来实现的。 MVCC的主要目标是避免读取和写入操作之间的冲突,从而提高数据库的并发性能和资源利用率。它通过在内部为每个事务提供一个唯一的时间戳来实现。每个事务在开始时都会获得一个时间戳,并且事务中的每个操作都使用这个时间戳。 当一个事务读取数据时,它只能读取它开始时间之前的数据版本。这样可以避免读取到其他事务正在写入或修改的数据,从而保证读取操作的一致性和隔离性。 当一个事务写入数据时,它会创建一个新的数据版本,并将其与事务的时间戳关联。这个新版本的数据不会立即覆盖旧的数据,而是以一种类似于快照的方式存在。其他事务在读取数据时仍然可以访问旧版本的数据。 MVCC还使用了回滚段(undo log)来处理事务的回滚操作。当一个事务被回滚时,数据库会使用回滚段将所有该事务做出的修改逆转回去,从而恢复到事务开始之前的状态。 需要注意的是,MVCC机制对于并发性能和资源利用率的提升是有限的。在高并发的情况下,数据库可能会出现锁等待和资源竞争的问题。为了进一步优化并发性能,可以考虑使用其他技术,如乐观并发控制(Optimistic Concurrency Control)和分布式数据库。 ### 回答3: MySQLMVCC(Multi-Version Concurrency Control)机制是一种并发控制技术,用于处理数据库中的读写冲突。它允许多个事务同时读取数据库,同时也使得读写冲突被有效地解决。 MVCC机制基于以下两个重要的概念:版本号和快照。 首先,每个表中的每个行都有一个版本号。当一个事务对某行进行修改时,会为该事务创建一个新的版本,并将旧版本标记为过期。这样,读取该行的事务会读取到未过期的版本,而不会受到写用户的影响。同时,这也避免了仅读用户被阻塞的情况。 其次,为了实现读取未过期版本的行,MVCC机制通过创建快照来实现。快照是数据库在某个时间点的一个镜像,其中包含了未过期的行版本。当一个读取事务开始时,会生成一个当前的数据库快照,并基于这个快照来读取数据行。这样,读取事务不会看到在其开始时(即快照生成时)已提交的写入事务,从而实现了读写并发。 MVCC机制对于提高数据库的并发性能非常重要。它允许多个事务同时进行读操作,提高了数据库的并发处理能力。此外,它也避免了读写冲突和阻塞的情况,提高了数据库的效率和稳定性。 总之,MySQLMVCC机制通过使用版本号和快照来实现读写并发控制和冲突的解决。它是提高数据库并发性能和减少阻塞的关键技术之一,并且在实际的数据库应用中扮演着非常重要的角色。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值