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

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

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

最近笔者和同事争辩起来,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

相关推荐

[常用工具] git基础学习笔记_git工具有哪些

添加推送信息,-m=messagegitcommit-m“添加注释”查看状态...

centos7安装部署gitlab_centos7安装git服务器

一、Gitlab介1.1gitlab信息GitLab是利用RubyonRails一个开源的版本管理系统,实现一个自托管的Git项目仓库,可通过Web界面进行访问公开的或者私人项目。...

太高效了!玩了这么久的Linux,居然不知道这7个终端快捷键

作为Linux用户,大家肯定在Linux终端下敲过无数的命令。有的命令很短,比如:ls、cd、pwd之类,这种命令大家毫无压力。但是,有些命令就比较长了,比如:...

提高开发速度还能保证质量的10个小窍门

养成坏习惯真是分分钟的事儿,而养成好习惯却很难。我发现,把那些对我有用的习惯写下来,能让我坚持住已经花心思养成的好习惯。...

版本管理最好用的工具,你懂多少?

版本控制(Revisioncontrol)是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史,方便查看更改历史记录,备份以便恢复以前的版本的软件工程技术。...

Git回退到某个版本_git回退到某个版本详细步骤

在开发过程,有时会遇到合并代码或者合并主分支代码导致自己分支代码冲突等问题,这时我们需要回退到某个commit_id版本1,查看所有历史版本,获取git的某个历史版本id...

Kubernetes + Jenkins + Harbor 全景实战手册

Kubernetes+Jenkins+Harbor全景实战手册在现代企业级DevOps体系中,Kubernetes(K8s)、Jenkins和Harbor组成的CI/CD流水...

git常用命令整理_git常见命令

一、Git仓库完整迁移完整迁移,就是指,不仅将所有代码移植到新的仓库,而且要保留所有的commit记录1.随便找个文件夹,从原地址克隆一份裸版本库...

第三章:Git分支管理(多人协作基础)

3.1分支基本概念分支是Git最强大的功能之一,它允许你在主线之外创建独立的开发线路,互不干扰。理解分支的工作原理是掌握Git的关键。核心概念:HEAD:指向当前分支的指针...

云效Codeup怎么创建分支并进行分支管理

云效Codeup怎么创建分支并进行分支管理,分支是为了将修改记录分叉备份保存,不受其他分支的影响,所以在同一个代码库里可以同时进行多个修改。创建仓库时,会自动创建Master分支作为默认分支,后续...

git 如何删除本地和远程分支?_git怎么删除远程仓库

Git分支对于开发人员来说是一项强大的功能,但要维护干净的存储库,就需要知道如何删除过时的分支。本指南涵盖了您需要了解的有关本地和远程删除Git分支的所有信息。了解Git分支...

git 实现一份代码push到两个git地址上

一直以来想把自己的博客代码托管到github和coding上想一次更改一次push两个地址一起更新今天有空查资料实践了下本博客的github地址coding的git地址如果是Gi...

git操作:cherry-pick和rebase_git cherry-pick bad object

在编码中经常涉及到分支之间的代码同步问题,那就需要cherry-pick和rebase命令问题:如何将某个分支的多个commit合并到另一个分支,并在另一个分支只保留一个commit记录解答:假设有两...

模型文件硬塞进 Git,GitHub 直接打回原形:使用Git-LFS管理大文件

前言最近接手了一个计算机视觉项目代码是屎山就不说了,反正我也不看代码主要就是构建一下docker镜像,测试一下部署的兼容性这本来不难但是,国内服务器的网络环境实在是恶劣,需要配置各种镜像(dock...

防弹少年团田柾国《Euphoria》2周年 获世界实时趋势榜1位 恭喜呀

当天韩国时间凌晨3时左右,该曲在Twitter上以“2YearsWithEuphoria”的HashTag登上了世界趋势1位。在韩国推特实时趋势中,从上午开始到现在“Euphoria2岁”的Has...