并发场景

最近做了一些分布式事务的项目,对事务的隔离性有了更深的认识,后续写文章聊分布式事务。今天就复盘一下单机事务的隔离性是如何实现的?

隔离的本质就是控制并发,如果SQL语句就是串行执行的。那么数据库的四大特性中就不会有隔离性这个概念了,也就不会有脏读,不可重复读,幻读等各种问题了

对数据库的各种并发操作,只有如下四种,写写,读读,读写和写读

写-写

事务A更新一条记录的时候,事务B能同时更新同一条记录吗?

答案肯定是不能的,不然就会造成脏写问题,那如何避免脏写呢?答案就是加锁

读-读

MySQL读操作默认情况下不会加锁,所以可以并行的读

读-写 和 写-读

基于各种场景对并发操作容忍程度不同,MySQL就搞了个隔离性的概念。你自己根据业务场景选择隔离级别。

√ 为会发生,×为不会发生

隔离级别 脏读 不可重复读 幻读
read uncommitted(未提交读)
read committed(提交读) ×
repeatable read(可重复读) × ×
serializable (可串行化) × × ×

所以你看,MySQL是通过锁和隔离级别对MySQL进行并发控制的

MySQL中的锁

行级锁

InnoDB存储引擎中有如下两种类型的行级锁

  1. 共享锁(Shared Lock,简称S锁),在事务需要读取一条记录时,需要先获取改记录的S锁
  2. 排他锁(Exclusive Lock,简称X锁),在事务要改动一条记录时,需要先获取该记录的X锁

如果事务T1获取了一条记录的S锁之后,事务T2也要访问这条记录。如果事务T2想再获取这个记录的S锁,可以成功,这种情况称为锁兼容,如果事务T2想再获取这个记录的X锁,那么此操作会被阻塞,直到事务T1提交之后将S锁释放掉

如果事务T1获取了一条记录的X锁之后,那么不管事务T2接着想获取该记录的S锁还是X锁都会被阻塞,直到事务1提交,这种情况称为锁不兼容。

多个事务可以同时读取记录,即共享锁之间不互斥,但共享锁会阻塞排他锁。排他锁之间互斥

S锁和X锁之间的兼容关系如下

兼容性 X锁 S锁
X锁 互斥 互斥
S锁 互斥 兼容

update,delete,insert 都会自动给涉及到的数据加上排他锁,select 语句默认不会加任何锁

那什么情况下会对读操作加锁呢?

  1. select … lock in share mode,对读取的记录加S锁
  2. select … for update ,对读取的记录加X锁
  3. 在事务中读取记录,对读取的记录加S锁
  4. 事务隔离级别在 SERIALIZABLE 下,对读取的记录加S锁

InnoDB中有如下三种锁

  1. Record Lock:对单个记录加锁
  2. Gap Lock:间隙锁,锁住记录前面的间隙,不允许插入记录
  3. Next-key Lock:同时锁住数据和数据前面的间隙,即数据和数据前面的间隙都不允许插入记录

写个Demo演示一下

CREATE TABLE `girl` (`id` int(11) NOT NULL,`name` varchar(255),`age` int(11),PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into girl values
(1, '西施', 20),
(5, '王昭君', 23),
(8, '貂蝉', 25),
(10, '杨玉环', 26),
(12, '陈圆圆', 20);

Record Lock

对单个记录加锁

如把id值为8的数据加一个Record Lock,示意图如下

Record Lock也是有S锁和X锁之分的,兼容性和之前描述的一样。

SQL执行加什么样的锁受很多条件的制约,比如事务的隔离级别,执行时使用的索引(如,聚集索引,非聚集索引等),因此就不详细分析了,举几个简单的例子。

-- READ UNCOMMITTED/READ COMMITTED/REPEATABLE READ 利用主键进行等值查询
-- 对id=8的记录加S型Record Lock
select * from girl where id = 8 lock in share mode;-- READ UNCOMMITTED/READ COMMITTED/REPEATABLE READ 利用主键进行等值查询
-- 对id=8的记录加X型Record Lock
select * from girl where id = 8 for update;

Gap Lock

锁住记录前面的间隙,不允许插入记录

MySQL在可重复读隔离级别下可以通过MVCC和加锁来解决幻读问题

当前读:加锁
快照读:MVCC

但是该如何加锁呢?因为第一次执行读取操作的时候,这些幻影记录并不存在,我们没有办法加Record Lock,此时可以通过加Gap Lock解决,即对间隙加锁。

如一个事务对id=8的记录加间隙锁,则意味着不允许别的事务在id=8的记录前面的间隙插入新记录,即id值在(5, 8)这个区间内的记录是不允许立即插入的。直到加间隙锁的事务提交后,id值在(5, 8)这个区间中的记录才可以被提交

我们来看如下一个SQL的加锁过程

-- REPEATABLE READ 利用主键进行等值查询
-- 但是主键值并不存在
-- 对id=8的聚集索引记录加Gap Lock
SELECT * FROM girl WHERE id = 7 LOCK IN SHARE MODE;

由于id=7的记录不存在,为了禁止幻读现象(避免在同一事务下执行相同的语句得到的结果集中有id=7的记录),所以在当前事务提交前我们要预防别的事务插入id=7的记录,此时在id=8的记录上加一个Gap Lock即可,即不允许别的事务插入id值在(5, 8)这个区间的新记录


给大家提一个问题,Gap Lock只能锁定记录前面的间隙,那么最后一条记录后面的间隙该怎么锁定?

其实mysql数据是存在页中的,每个页有2个伪记录

  1. Infimum记录,表示该页面中最小的记录
  2. upremum记录,表示该页面中最大的记录

为了防止其它事务插入id值在(12, +∞)这个区间的记录,我们可以给id=12记录所在页面的Supremum记录加上一个gap锁,此时就可以阻止其他事务插入id值在(12, +∞)这个区间的新记录

Next-key Lock

同时锁住数据和数据前面的间隙,即数据和数据前面的间隙都不允许插入记录
所以你可以这样理解Next-key Lock=Record Lock+Gap Lock

-- REPEATABLE READ 利用主键进行范围查询
-- 对id=8的聚集索引记录加S型Record Lock
-- 对id>8的所有聚集索引记录加S型Next-key Lock(包括Supremum伪记录)
SELECT * FROM girl WHERE id >= 8 LOCK IN SHARE MODE;

因为要解决幻读的问题,所以需要禁别的事务插入id>=8的记录,所以

  • 对id=8的聚集索引记录加S型Record Lock
  • 对id>8的所有聚集索引记录加S型Next-key Lock(包括Supremum伪记录)

表级锁

表锁也有S锁和X锁之分

在对某个表执行select,insert,update,delete语句时,innodb存储引擎是不会为这个表添加表级别的S锁或者X锁。

在对表执行一些诸如ALTER TABLE,DROP TABLE这类的DDL语句时,会对这个表加X锁,因此其他事务对这个表执行诸如SELECT INSERT UPDATE DELETE的语句会发生阻塞

在系统变量autocommit=0,innodb_table_locks = 1时,手动获取InnoDB存储引擎提供的表t的S锁或者X锁,可以这么写

对表t加表级别的S锁

lock tables t read

对表t加表级别的X锁

lock tables t write

如果一个事务给表加了S锁,那么

  • 别的事务可以继续获得该表的S锁
  • 别的事务可以继续获得表中某些记录的S锁
  • 别的事务不可以继续获得该表的X锁
  • 别的事务不可以继续获得表中某些记录的X锁

如果一个事务给表加了X锁,那么

  • 别的事务不可以继续获得该表的S锁
  • 别的事务不可以继续获得表中某些记录的S锁
  • 别的事务不可以继续获得该表的X锁
  • 别的事务不可以继续获得表中某些记录的X锁

所以修改线上的表时一定要小心,因为会使大量事务阻塞,目前有很多成熟的修改线上表的方法,不再赘述

隔离级别

读未提交:每次读取最新的记录,没有做特殊处理
串行化:事务串行执行,不会产生并发

所以我们重点关注读已提交可重复读的隔离实现!

这两种隔离级别是通过MVCC(多版本并发控制)来实现的,本质就是MySQL通过undolog存储了多个版本的历史数据,根据规则读取某一历史版本的数据,这样就可以在无锁的情况下实现读写并行,提高数据库性能

那么undolog是如何存储修改前的记录?

对于使用InnoDB存储引擎的表来说,聚集索引记录中都包含下面2个必要的隐藏列

trx_id:一个事务每次对某条聚集索引记录进行改动时,都会把该事务的事务id赋值给trx_id隐藏列

roll_pointer:每次对某条聚集索引记录进行改动时,都会把旧的版本写入undo日志中。这个隐藏列就相当于一个指针,通过他找到该记录修改前的信息

如果一个记录的name从貂蝉被依次改为王昭君,西施,会有如下的记录,多个记录构成了一个版本链

为了判断版本链中哪个版本对当前事务是可见的,MySQL设计出了ReadView的概念。4个重要的内容如下

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

当对表中的记录进行改动时,执行insert,delete,update这些语句时,才会为事务分配唯一的事务id,否则一个事务的事务id值默认为0。

max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比如现在有事务id为1,2,3这三个事务,之后事务id为3的事务提交了,当有一个新的事务生成ReadView时,m_ids的值就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4


执行过程如下:

  1. 如果被访问版本的trx_id=creator_id,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问
  2. 如果被访问版本的trx_id<min_trx_id,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问
  3. 被访问版本的trx_id>=max_trx_id,表明生成该版本的事务在当前事务生成ReadView后才开启,该版本不可以被当前事务访问
  4. 被访问版本的trx_id是否在m_ids列表中
    4.1 是,创建ReadView时,该版本还是活跃的,该版本不可以被访问。顺着版本链找下一个版本的数据,继续执行上面的步骤判断可见性,如果最后一个版本还不可见,意味着记录对当前事务完全不可见
    4.2 否,创建ReadView时,生成该版本的事务已经被提交,该版本可以被访问

好了,我们知道了版本可见性的获取规则,那么是怎么实现读已提交和可重复读的呢?

其实很简单,就是生成ReadView的时机不同

举个例子,先建立如下表

CREATE TABLE `girl` (`id` int(11) NOT NULL,`name` varchar(255),`age` int(11),PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Read Committed

Read Committed(读已提交),每次读取数据前都生成一个ReadView


下面是3个事务执行的过程,一行代表一个时间点

先分析一下5这个时间点select的执行过程

  1. 系统中有两个事务id分别为100,200的事务正在执行
  2. 执行select语句时生成一个ReadView,mids=[100,200],min_trx_id=100,max_trx_id=201,creator_trx_id=0(select这个事务没有执行更改操作,事务id默认为0)
  3. 最新版本的name列为西施,该版本trx_id值为100,在mids列表中,不符合可见性要求,根据roll_pointer跳到下一个版本
  4. 下一个版本的name列王昭君,该版本的trx_id值为100,也在mids列表内,因此也不符合要求,继续跳到下一个版本
  5. 下一个版本的name列为貂蝉,该版本的trx_id值为10,小于min_trx_id,因此最后返回的name值为貂蝉

再分析一下8这个时间点select的执行过程

  1. 系统中有一个事务id为200的事务正在执行(事务id为100的事务已经提交)
  2. 执行select语句时生成一个ReadView,mids=[200],min_trx_id=200,max_trx_id=201,creator_trx_id=0
  3. 最新版本的name列为杨玉环,该版本trx_id值为200,在mids列表中,不符合可见性要求,根据roll_pointer跳到下一个版本
  4. 下一个版本的name列为西施,该版本的trx_id值为100,小于min_trx_id,因此最后返回的name值为西施

当事务id为200的事务提交时,查询得到的name列为杨玉环。

Repeatable Read

Repeatable Read(可重复读),在第一次读取数据时生成一个ReadView

可重复读因为只在第一次读取数据的时候生成ReadView,所以每次读到的是相同的版本,即name值一直为貂蝉,具体的过程上面已经演示了两遍了,我这里就不重复演示了,相信你一定会自己分析了。

参考博客

[1]https://souche.yuque.com/bggh1p/8961260/gyzlaf
[2]https://zhuanlan.zhihu.com/p/35477890

面试官:MySQL事务的隔离性是如何实现的?相关推荐

  1. 同一事务中未提交的写能读到吗_03、MySQL事务的隔离性分析

    事务可以用来保证数据库的完整性:要么都做,要么不做.在 MySQL 中,事务支持是在引擎层实现的.你现在知道,MySQL 是一个支持多引擎的系统,但并不是所有的引擎都支持事务.比如 MySQL 原生的 ...

  2. Mysql 事务的隔离性(隔离级别)

    Mysql 中的事务分为手动提交和自动提交,默认是自动提交,所以我们在Mysql每输入一条语句,其实就会被封装成一个事务提交给Mysql服务端. 手动提交需要先输入begin,表示要开始处理事务,然后 ...

  3. MySQL事务及隔离级别详解

    MySQL事务及隔离级别详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.MySQL的基本架构 MySQL的基本架构可以分为三块,即连接池,核心功能层,存储引擎层. 1> ...

  4. mysql事务的隔离c_Mysql事务处理与隔离级别 -cyy

    生产环境中事务的应用场景: 事务是多个数据库操作的集合,该集合内必须所有的数据库操作完成,事务才能完成,只要有一个操作失败,事务就不会成功,之前成功的数据库操作会进行回滚以保证事务的完整性不会遭到破坏 ...

  5. MySql约束和隔离性问题练习

    MySql约束和隔离性问题练习 创建一张表,要求包含:主键约束,非空约束,唯一约束:然后插入数据测试3种约束的特点 create table users(uid int primary key,una ...

  6. 腾讯面试:MySQL事务与MVCC如何实现的隔离级别?

    有情怀,有干货,微信搜索[三太子敖丙]关注这个不一样的程序员. 本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点.资料以及我的系列文章. ...

  7. 【MySQL】事务及其隔离性/隔离级别

    目录 一.事务的概念 1.事务的四种特性 2.事务的作用 3.存储引擎对事务的支持 4.事务的提交方式 二.事务的启动.回滚与提交 1.准备工作:调整MySQL的默认隔离级别为最低/创建测试表 2.事 ...

  8. 列举MySQL事务的隔离级别_mysql dba系统学习(22)数据库事务详解

    上个星期去面试数据库管理员的工作,笔试通过之后就是直接的面试,他问了我一个问题,叫我介绍哈数据库的事务的看法和理解,但是不知所错的没有章法的乱答一气,唉唉,基础不扎实啊. 下面来好好的学习哈mysql ...

  9. mysql 事务、隔离级别

    一.事务的四大特性(ACID) 1.原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节.事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有 ...

最新文章

  1. 移动网络安全不容忽视 对恶意程序打好防范补丁
  2. 《信号检测与处理》的学习
  3. JavaSE各阶段练习题----Map
  4. 怎么去掉Flex4生成的SWF加载时的进度条
  5. Selenium不打开浏览器采爬取数据 Java
  6. 面向对象之多态,魔法函数
  7. 【数据库】E-R图向关系模型转换的规则
  8. 尚学堂--面向对象2
  9. 2014年上半年系统集成项目管理工程师真题解析(上午+下午)
  10. 使用通达信一次性获取沪深300成分股
  11. 2009年全国数模比赛,江苏三等奖名单
  12. (译)Xposed Helpers
  13. 野人岛java游戏,生存战争之独闯野人岛
  14. 数字和模拟混合供电20190221
  15. 阿里云免费SSL证书申请与安装使用-附Nginx,Apache,IIS 6,IIS 8配置SSL教程
  16. matlab基本操作与矩阵输入简单表示
  17. iOS开发 音频合成,改变音轨音量,改变背景音乐音量,音频剪辑
  18. 内存,外存,运存,显存,闪存,硬盘,SSD等概念
  19. FPGA学习之路-ZCU106板子点亮PS侧LED
  20. EJB初级篇--何为EJB

热门文章

  1. 第十三章 密码破解
  2. java工程师简历自我介绍范文,薪资翻倍
  3. 简历看出一个人的性格
  4. kafka基础架构(概念篇)
  5. poj 3077 Rounders/bnuoj 3196 Rounders 解题报告
  6. 无名之辈高清版百度云在线观影
  7. [简明C语言]分支和循环P_2:分支 - swtich语句
  8. el-tree 设置选中添加样式
  9. PHP留言本,非常适合新手实战操作!
  10. [leaflet] 1 esri-leaflet