百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

打脸实录:MySQL插入是并发还是串行?

wptr33 2024-11-08 15:03 30 浏览

最近笔者和同事争辩起来,MySQL插入是并发还是串行,我记得明明是串行插入,同事非要和我杠,说MySQL可以并发插入。


我要亲自试验一下,打他的脸!


MySQL实验版本:8.0。


一、定义表结构


首先定义 用户信息表userInfo,其中id为自增,name具有唯一索引。


CREATE TABLE `userInfo` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `password` varchar(50) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4

二、验证流程


默认情况下,在命令行中 MySQL会自动提交,每个SQL执行会非常快,无法验证同时执行的两个事务之间是否存在阻塞情况,所以需要显示开启事务和提交事务。


1.验证


首先,我们开启两个事务。在事务1中,首先插入一条记录,暂时不提交。然后,在事务2中开启一个新的事务,并插入一条自增记录。


如果MySQL的innodb插入是串行的,那么此时事务2的插入记录将会被阻塞。如果没有被阻塞,那就说明MySQL的innodb插入是并发执行的。



2.实验验证


事务2 的执行记录:



如上图所示,在事务1还未提交,事务2在事务1的间隙中插入一条记录,插入操作立即成功,并且事务2的自增主键ID为2。这说明在MySQL中,当一个事务正在插入记录时,并不会阻塞其他事务的插入。


在MySQL中,多个事务之间的插入操作是并发进行的,而不是串行进行的。


我感觉自己的脸热热的,小丑竟是我自己,赶紧给同事认了错……


我的认知一直是错误的。


但是在底层存储层面,MySQL会对数据页加锁。如果两条记录在同一个数据页,实际写入是串行的,但是事务层面是并发的。


想象一下,库存扣减和新增库存流水在同一个事务中,如果新增库存流水是串行的,那将极大的降低库存事务的并发度啊。


本以为验证结束,打卡下班,结果发现 MySQL插入似乎存在幻读问题!


从下图中可以观察到,事务1在插入时似乎确实出现了幻读问题!


事务 1 的执行记录显示,事务1先于事务2开启,但是事务1期间可以查询到事务2提交的记录。这说明有幻读问题!



三、为什么出现幻读?


所谓幻读,是指在一个事务读取记录时,另一个事务在此时插入或删除了一条记录,导致第一个事务再次读取时发现记录的数量发生了变化。


要想理解出现幻读的原因,需要先了解MySQL是如何解决幻读问题的。


为了解决幻读问题,MySQL采用了间隙锁和多版本并发控制(MVCC)的方法。间隙锁会锁定一段记录的范围,其他事务无法对这些记录进行更新或删除操作。这样,当当前事务再次进行查询时,就不会出现记录数量的新增或减少的情况了。


1.MySQL 插入时加了什么锁?


MySQL 插入时存在幻读问题,说明MySQL 并没有加间隙锁,主要考虑也是为了提高插入时并发度,如果添加间隙锁,势必导致插入并发度降低!MySQL 在插入之前会申请 插入意向锁,而记录本身不冲突(无唯一键冲突)插入意向锁就不会冲突。


MySQL 文档中记录了 插入意向锁


插入意向锁(insert intention lock)是一种由插入操作在插入行之前设置的锁定类型。这种锁定方式表示插入的意图,使得在相同索引间隙上进行插入的多个事务在插入位置不冲突的情况下不需要互相等待。假设索引记录中存在值为4和7的记录。分别尝试插入值为5和6的两个不同事务,在获得插入行的排他锁之前,它们会先使用插入意向锁锁定位于4和7之间的间隙,并且由于插入行不冲突,它们不会互相阻塞。


2.插入场景 MVCC 不生效?


除更新场景外,查询场景也有幻读的困恼。如果第一次查询时只有3条记录,再次查询则变为4条,实在过于奇幻。


如果给普通的查询语句添加间隙锁,势必极大的降低MySQL 的并发度,如果不能使用间隙锁,还有哪些办法解决幻读呢?


MySQL 通过引入MVCC解决查询场景的幻读问题。MVCC是多版本并发控制(Multiversion Concurrency Control)的缩写,在MVCC中,每个事务可以看到数据库的一个稳定的快照,而不会被其他并发事务的修改所干扰。当一个事务修改数据库时,它会创建一个新的数据版本,而不是直接在原始数据上进行修改。而其他事务仍然可以读取原始数据的旧版本或者已经提交的新版本,这样就避免了读取到未提交的数据或者被其他事务的写操作所阻塞。


MVCC的实现通常涉及对每个数据行或数据块分配一个唯一的标识符,称为"事务ID"。每个事务也有自己的唯一ID。当一个事务读取数据时,系统会检查该数据的事务ID与事务的ID是否兼容,以确定是否允许读取。如果事务的ID大于数据的事务ID,那么说明数据是过期的,事务将无法读取。这种机制保证了事务在读取数据时的隔离性和一致性。


3.转机出现了


当我在苦苦思考,为什么MVCC 没有生效时,我随手重新测试发现,如果在 insert 语句之前,使用 select 查询一下,就不会出现幻读问题。


操作顺序如下:



我在事务1,开启事务以后,新增了 select 语句查询,而后第六步,就不会再有幻读问题……


这实在太奇幻了,一波三折……


由此可见 MySQL 插入并没有幻读问题,只是我的打开方式不对。我应该先 select一下 ……,终究还是我错了,但是我想问为什么?我为什么错了?


4.ReadView 是关键!


除MVCC 外,MySQL InnoDB 引擎设计了 ReadView(可读视图) 的概念。


ReadView 判断记录的可见性,ReadView 实际上是当前系统中所有活跃事务的列表,主要包含以下组成部分:


  • m_ids:在生成 ReadView 时当前系统中活跃的事务 ID 列表;
  • min_trx_id:在生成 ReadView 时当前系统中活跃的事务中最小的事务 ID,也就是 m_ids 中的最小值;
  • max_trx_id:在生成 ReadView 时系统中应该分配给下一个事务的 ID 值;
  • creator_trx_id:生成 ReadView 的事务对应的事务 ID,也就是当前事务 ID。


有了这个 ReadView 之后,在访问某条记录时,只需要按照下边的步骤判断该记录的某个版本是否可见:


1)如果被访问版本的 trx_id 属性值与 ReadView 中的 creator_trx_id 值相同,意味着当前事务在访问它自己修改过的记录,所以该版本记录可以被当前事务访问。


2)如果被访问版本的 trx_id 属性值小于 ReadView 中的 min_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本记录可以被当前事务访问。


3)如果被访问版本的 trx_id 属性值大于或等于 ReadView 中的 max_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本记录不可以被当前事务访问。


4)如果被访问版本的 trx_id 属性值在 ReadView 的 min_trx_id 和 max_trx_id 之间,那就需要判断一下 trx_id 属性值是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本记录可以被访问。如果某个版本的记录对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。


总结一下就是:如果当前事务id的生成时间发生在 记录的更新之后,那么当前事务就可以看见这个记录,否则看不见!避免幻读问题。


那 ReadView 又是何时生成的呢?


在 Read committed RC 隔离级别下,每个事务执行第一个 SELECT 语句时,会将当前系统中的所有的活跃事务拷贝到一个列表生成 ReadView,后续所有的 SELECT 都是复用这个 ReadView。


REPEATABLE READ RR 隔离级别下,只有第一次 SELECT 才会生成 ReadView,后续 SELECT 都会复用这个 ReadView,也就不存在新提交事务对这个 ReadView 的影响了。


所以 当我在 事务 1 新增select语句,会生成一个ReadView,这个ReadView 生成时间要早于 事务2的时间,所以事务1 的后续所有查询都不会看到事务2的记录,从而避免幻读问题发生。


总结


  • MySQL innodb 插入记录是并发的。
  • MySQL innodb 插入记录不存在幻读问题,MySQL 通过 mvcc+ ReadView解决幻读问题。


作者丨五阳神功

来源丨稀土掘金:juejin.cn/post/7297608058476249124

dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn

相关推荐

Linux文件系统操作常用命令(linux文件内容操作命令)

在Linux系统中,有一些常用的文件系统操作命令,以下是这些命令的介绍和作用:#切换目录,其中./代表当前目录,../代表上一级目录cd#查看当前目录里的文件和文件夹ls#...

别小看tail 命令,它难倒了技术总监

我把自己以往的文章汇总成为了Github,欢迎各位大佬star...

lnav:基于 Linux 的高级控制台日志文件查看器

lnav是一款开源的控制台日志文件查看器,专为Linux和Unix-like系统设计。它通过自动检测日志文件的格式,提取时间戳、日志级别等关键信息,并将多个日志文件的内容按时间顺序合并显示,...

声明式与命令式代码(声明模式和命令模式)

编程范式中的术语和差异信不信由你,你可能已经以开发人员的身份使用了多种编程范例。因为没有什么比用编程理论招待朋友更有趣的了,所以这篇文章可以帮助您认识代码中的流行范例。命令式编程命令式编程是我们从As...

linux中的常用命令(linux常用命令和作用)

linux中的常用命令linux中的命令统称shell命令shell是一个命令行解释器,将用户命令解析为操作系统所能理解的指令,实现用户与操作系统的交互shell终端:我们平时输入命令,执行程序的那个...

提高工作效率的--Linux常用命令,能够决解95%以上的问题

点击上方关注,第一时间接受干货转发,点赞,收藏,不如一次关注评论区第一条注意查看回复:Linux命令获取linux常用命令大全pdf+Linux命令行大全pdf...

如何限制他人操作自己的电脑?(如何控制别人的电脑不让发现)

这段时间,小猪罗志祥正处于风口浪尖,具体是为啥?还不知道的小伙伴赶紧去补一下最近的娱乐圈八卦~简单来说,就是我们的小罗同事,以自己超强的体力,以及超强的时间管理能力,重新定义了「多人运动」的含义,重新...

最通俗易懂的命令模式讲解(命令模式百科)

我们先不讲什么是命令模式,先通过一个场景来引出命令模式,看看命令模式能解决什么样的问题。现在有一个渣男张三,他有还几个女朋友,你现在是不是还是单身狗,你就说你气不气?然后他需要每天分别叫几个女朋友起床...

互联网大厂后端必看!Spring Boot 中Runtime执行与停止命令?

你是否曾在使用SpringBoot开发项目时,遇到需要执行系统命令的场景?比如调用脚本进行文件处理,又或是启动外部程序?很多后端开发人员会使用Processexec=Runtime.get...

Linux 常用命令(linux常用的20个命令面试)

日志排查类操作命令...

Java字节码指令:if_icmpgt(0xA3)(java字节码使用的汇编语言)

if_icmpgt是Java字节码中的一条条件跳转指令,其全称是"IfIntegerCompareGreaterThan"。它用于比较两个整数值的大小。如果栈顶的第一个...

外贸干货|如何增加领英的曝光量和询盘

#跨境电商#...

golang执行linux命令(golang调用shell脚本)

需求需要通过openssl生成rsa秘钥,然后保存该秘钥。代码实例packagemainimport("io/ioutil""bytes"&...

LINUX磁盘挂载(linux磁盘挂载到windows)

1、使用root用户查看磁盘挂载情况:fdisk-l2、使用df查看当前磁盘挂载情况,根据和fdisk-l的结果进行对比,查看还有那些磁盘未使用3、挂载:mount磁盘挂载路径...

Linux命令学习——nl命令(linux ln命令的使用)

nl命令主要功能为每一个文件添加行号,每一个输入的文件添加行号后发送到标准输出。当没有文件或文件为-时,读取标准输入...