mysql系列9—mysql的MVCC机制

embedded/2025/2/24 21:58:07/

背景

mysql提供了读未提交、读已提交、可重复读、串行化四种隔离级别,默认的隔离界别为可重复读。其中,不可重复度场景下,每次直接读取最新记录(即使事务未提交);串行化对于所有的读写都加锁,因此,对二者不做过多介绍,MVCC只适用于可重复度和读已提交。
mysql的并发事务中存在三种冲突:读-读、读-写、写-写。其中读-读没有冲突,可以同时进行;写-写通过数据库锁实现冲突分离;MVCC解决的是读-写冲突,并尽可能提高数据库性能。

MVCC的实现由UNDO日志、版本链、READVIEW三部分组成,因此本文先对这三部分进行介绍,然后再结合案例介绍MVCC机制;最后介绍MVCC遗留的幻读问题。
本文中较大篇幅围绕案例进行,案例使用的表如下:

CREATE TABLE `t_student` (`id` INT(10) NOT NULL COMMENT '学号,唯一ID',`name` VARCHAR(50) NOT NULL COMMENT '姓名',PRIMARY KEY (`id`) USING BTREE
)
ENGINE=InnoDB
;

1.当前读和快照读

当前读也叫做锁定读,数据读取时需要加锁(行锁和间隙锁),以保证读取的数据不会被其他事务修改,从而读取的是当前最新的数据。
mysql中以下两种SELECT查询语句会触发当前读:

select ... for updateselect ... lock in share mode

当前读会阻塞其他事务对行记录的写操作,一般用于金融交易等对一致性要求较高的场景。
除了上述两种SELECT查询之外的其他查询SQL都是快照读。
mysql的默认隔离级别为可重复读,在这种隔离界别下,第一次查询时生成一个数据库快照,后续查询时读取的是同一份快照。

2.版本链

创建三个事务,分别对t_student表按如下时间线顺序和操作进行修改:

time事务100事务200事务300
1START TRANSACTION;
2INSERT INTO t_student (id, name) VALUES (1, ‘sy’);
3COMMIT;
4START TRANSACTION;
5START TRANSACTION;
6UPDATE t_student SET NAME = ‘sy201’ WHERE id = 1;
7UPDATE t_student SET NAME = ‘sy202’ WHERE id = 1;
8COMMIT;
9UPDATE t_student SET NAME = ‘sy301’ WHERE id = 1;
10UPDATE t_student SET NAME = ‘sy302’ WHERE id = 1;
11COMMIT;

在UNDO文章中已经介绍过版本链的概念,案例对应的版本链结构(近似表示)如下图所示:
请添加图片描述

mysql可以根据undo日志计算出上一步的记录值,因此上图可以形象地表示为:
请添加图片描述
简单回顾一下图中涉及两个重要的隐藏列: db_trx_id表示当前记录由哪个事务创建或者最后改动; db_trx_ptx指向undo日志。实际上,每条被修改的记录都通过db_trx_ptx指向上一步的Undo日志,undo日志中又包含了上上步Undo日志指针…… 由此形成了该记录对应的版本链。

3.ReadView和可见性原则

ReadView结合可见性原则可以实现读已提交,解决并发脏读问题。

ReadView:

ReadView由四个属性组成:
[1] m_ids: 当前活跃的事务ID;
[2] min_trx_id: m_ids中的最小值;
[3] max_trx_id: 下一个待分配的事务ID;
[4] creator_trx_id:当前事务ID;

可见性原则:

由于事务ID是依次递增的,即越早提交的事务,事务ID越小。在前文提到过,事务ID只有在执行修改时才会分配,而不是事务启动时分配。如一个事务全局只会读数据,则该事物不会分配事务ID。
基于上述两点,以下可见性原则保证了读取的数据均为已提交数据。
请添加图片描述
Note: 版本链上的事务ID使用trx_id表示。
[1] trx_id 等于creator_trx_id,表示当前事务在访问自己修改过的记录,可见;
[2] trx_id 小于min_trx_id, 表示记录对应的事务已经被提交,可见;
[3] trx_id 大于max_trx_id, 表示记录对应的事务未被提交,不可见;
[4] 其他情况(处于min_tx_id和max_trx_id之间),如果事务已提交(trx_id 不在m_ids集合中)——可见,事务活跃(trx_id 在m_ids集合中),则不可见。

读已提交和可重复度通过创建ReadView的策略不同进行区分: ReadView本身保证了读取的记录已经被提交,因此读已提交隔离级别下,每次读取都重新创建一个ReadView; 可重复读只在第一次查询时建立ReadView, 后续查询都基于同一个ReadView进行,保证了每次读取的数据相同。

4.MVCC

查询时事务将根据ReadView信息沿着版本链的顺序进行,直到查询满足可见性原则的记录或者返回空。
结合案例进行理解。

time事务100事务200事务300
1START TRANSACTION;
2INSERT INTO t_student (id, name) VALUES (1, ‘sy’);
3COMMIT;
4START TRANSACTION;
5START TRANSACTION;
6UPDATE t_student SET NAME = ‘sy201’ WHERE id = 1;… update other line …
7UPDATE t_student SET NAME = ‘sy202’ WHERE id = 1;
8SELECT NAME FROM t_student WHERE id = 1;
9SELECT NAME FROM t_student WHERE id = 1;
10COMMIT;
11COMMIT;

在time=NO.8和NO.9时刻,id=1记录的版本链如下所示:
请添加图片描述
case 1: 站在事务200的角度,time=NO.8时刻第一次执行版本查询,创建ReadView(用ReadView200表示):
[1] m_ids: 200;
[2] min_trx_id: 200;
[3] max_trx_id: 301;
[4] creator_trx_id: 200;
使用ReadView200与第一条记录按照可见性原则进行匹配:
记录的db_trx_id等于creator_trx_id,表明该记录是当前事务创建的,因此可见。
返回的结果是sy202.

case 2: 站在事务300的角度,time=NO.9时刻第一次执行版本查询,创建ReadView(用ReadView300表示):
[1] m_ids: {200,300};
[2] min_trx_id: 200;
[3] max_trx_id: 301;
[4] creator_trx_id: 300;

使用ReadView300与第一条记录按照可见性原则进行匹配:
记录的db_trx_id(200)不等于creator_trx_id(300), 且db_trx_id(200)事务还处于活跃状态,不可见;
沿着版本链读取下一条记录: 记录的db_trx_id(200),仍不可见;
沿着版本链继续读取下一条记录: db_trx_id(100)小于min_trx_id,表示事务已提交,可见,返回sy.

特别注意:ReadView是在第一次查询时创建, 而不是开启事务时创建

对上述案例进行简单变更(事务3的查询时机修改一下):

time事务100事务200事务300
1START TRANSACTION;
2INSERT INTO t_student (id, name) VALUES (1, ‘sy’);
3COMMIT;
4START TRANSACTION;
5START TRANSACTION;
6UPDATE t_student SET NAME = ‘sy201’ WHERE id = 1;… update other line …
7UPDATE t_student SET NAME = ‘sy202’ WHERE id = 1;
8SELECT NAME FROM t_student WHERE id = 1;
9COMMIT;
10SELECT NAME FROM t_student WHERE id = 1;
11COMMIT;

case 2: 站在事务300的角度,time=NO.10时刻第一次执行版本查询,创建ReadView(用ReadView300表示):
[1] m_ids: {300};
[2] min_trx_id: 300;
[3] max_trx_id: 301;
[4] creator_trx_id: 300;
此时,根据可见性规则,事务200的修改已对事务300可见,即查询的结果为sy202.

5.幻读问题

幻读的概念: 一个事务在读取某个范围内的记录后,另一个并发事务插入了新的记录到这个范围内,导致第一个事务再次读取该范围时,出现了之前未读到的记录,就像产生了“幻影”一样。

事务中完全使用当前读可以避免幻读问题,当前读通过加锁实现: 行锁保证了当前记录不会被修改,间隙锁保证了范围查询时,范围内不会插入新记录。
mysql虽然通过MVCC解决了大部分幻读问题,但特殊场景下仍存在幻读问题:一个事务A读取某个范围内的记录后,另一个并发事务B插入了新的记录到这个范围内并提交; 事务A修改这条记录,事务A再次读取相同范围时,会多查出这条记录。以下通过案例进行细致介绍。

t_student表中初始状态只有1条记录:

mysql> select * from t_student;
+----+-------+
| id | name  |
+----+-------+
|  1 | sy    |
+----+-------+

创建两个事务,分别对t_student表按如下时间线顺序和操作进行修改:

time事务100事务200
1START TRANSACTION;
2SELECT * FROM t_student WHERE id < 10;
3START TRANSACTION;
4INSERT INTO t_student (id, name) VALUES (2, ‘mf’);
5INSERT INTO t_student (id, name) VALUES (3, ‘lj’);
6COMMIT;
7SELECT * FROM t_student WHERE id < 10;
8UPDATE t_student SET NAME = ‘mf100’ WHERE id = 2;
9SELECT * FROM t_student WHERE id < 10;

对应的执行结果如下所示:
请添加图片描述
NO.7范围查询与NO.2的查询结果一致,此时没有幻读问题;NO.9的查询与NO.1的不一致,多查出了其他事务插入的数据,出现幻读。

原因分析:
mysql本质上不能保证两个事务完全彻底的独立,仅通过MVCC机制实现了读的隔离, 在一定程度上保证了读写分离和并发安全性; 上图NO.8的修改操作显示有一条记录被改动,对比NO.7的查询可知:事务100无法读取,但可以修改已被提交的记录。
此时,id=2的记录对应的db_trx_id记录的是最后修改的事务ID,即100;事务100再次查询该范围时,根据ReadView和可见性原则,就可以查询到这条幻影记录了。


http://www.ppmy.cn/embedded/164902.html

相关文章

数据中心储能蓄电池状态监测管理系统 组成架构介绍

安科瑞刘鸿鹏 摘要 随着数据中心对供电可靠性要求的提高&#xff0c;蓄电池储能系统成为关键的后备电源。本文探讨了蓄电池监测系统在数据中心储能系统中的重要性&#xff0c;分析了ABAT系列蓄电池在线监测系统的功能、技术特点及其应用优势。通过蓄电池监测系统的实施&#…

LangChain大模型应用开发:多模态输入与自定义输出

介绍 大家好&#xff0c;博主又来给大家分享知识了。今天给大家分享的内容是使用LangChain进行大模型应用开发中的多模态输入与自定义输出。 LangChain中的多模态数据输入是指将多种不同形式的数据作为输入提供给基于语言模型的应用程序或系统&#xff0c;以丰富交互内容和提…

C++栈与队列:数据结构的“单行道”与“流水线

C栈与队列&#xff1a;数据结构的“单行道”与“流水线” 开篇小故事&#xff1a;火车站的两条轨道 想象一个火车站有两条特殊轨道&#xff1a; 轨道A&#xff08;栈&#xff09;&#xff1a;火车只能从同一端进出&#xff0c;最后进入的车厢必须先离开。轨道B&#xff08;队…

Win11更新系统c盘爆满处理

1.打开磁盘管理 2.右击c盘选择属性&#xff0c;进行磁盘管理&#xff0c;选择详细信息。 3.选择以前安装的文件删除即可释放c盘空间。

Java 集合:单列集合和双列集合的深度剖析

引言 在 Java 编程中&#xff0c;集合是一个非常重要的概念。它就像是一个容器&#xff0c;能够存储多个数据元素&#xff0c;帮助我们更方便地管理和操作数据。Java 集合框架主要分为单列集合和双列集合两大类&#xff0c;它们各自有着独特的特点和适用场景。接下来&#xff0…

全面汇总windows进程通信(二)

在Windows操作系统下,实现进程间通信(IPC, Inter-Process Communication)有几种常见的方法,包括使用管道(Pipe)、共享内存(Shared Memory)、消息队列(Message Queue)、命名管道(Named Pipe)、套接字(Socket)等。本文介绍如下几种: 信号量(Semaphore)和互斥量(…

ip归属地和手机卡有关系吗?详细探析

在数字化浪潮席卷全球的今天&#xff0c;互联网已成为连接世界的桥梁。IP地址&#xff0c;作为网络世界中每个设备的“身份证”&#xff0c;承载着设备的位置信息和通信功能。而手机卡&#xff0c;则是我们移动设备接入互联网的钥匙&#xff0c;它让随时随地的在线交流成为可能…

【mysql】核心参数,SHOW VARIABLES

核心参数分类解析 SET GLOBAL slow_query_log = ON; 修改参数设置 一、核心参数分类解析 1. 连接和线程配置 max_connections (200):允许的最大并发连接数,超过会拒绝新连接。 wait_timeout (28800秒):非交互式连接的空闲超时时间(默认8小时)。 interactive_timeout (…