图解MySQL的四种事务隔离级别与实现原理

事务的几个相关概念

什么是事务?

事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。

为什么需要事务?
技术是服务于业务的,在许多使用数据库的业务场景中,经常需要进行一系列操作,这一系列操作要么全都成功,要么全都失败,不允许出现成功了一半这样的中间情况。(例如经典银行转账案例,A扣10块钱,B加十块钱,要么全部执行成功完成转账,要么A扣的钱回滚回去,B也不会加钱)

事务的ACID特性

基于对业务的总结,人们总结出了事务应该具有如下四个特性,即:

  • Atomic-原子性,原子在化学概念上是指化学反应不可再分的基本微粒,顾名思义原子性即要么都成功,要么都失败。
  • Consistency-一致性,其实和原子性意思差不多,事务必须是使数据库从一个一致性状态变到另一个一致性状态。举例子来说:A有100块钱,B有50块钱,A和B总共有150块钱这是一个一致性状态;转账之后,A有90块钱,B有60块钱,A和B还是一共150块钱。
  • Isolation-隔离性,是指在并发多个事务访问的情况下,并发的事务是相互隔离的,一个事务的执行不能够被其它事务干扰。本文要讨论的MySQL的四种事务隔离级别,就是为了保证并发度的前提下,你的事务到底能接受多大程度的不被干扰
  • Duration-持久性,保证了上述各种特性之后,业务上还需要这个数据库保证持久性,不能出现我转账都完成了/我炉石开出金色传说了,结果数据库断电了,重启后回到解放前了。这种属于不可接受的线上事故,一般数据库都会有各种手段在尽可能保证性能的同时持久化写数据到磁盘上。

MVCC

当前读 vs. 快照读

当前读,每次都读取记录的最新版本,并且会对记录进行加锁,典型的当前读操作:

  • select lock in share mode(共享锁)
  • select for update(排他锁)
  • update(排他锁)
  • insert(排他锁)
  • delete(排他锁)
    快照读,每次读取操作读到的实际是基于当前可见性生成的快照,快照的实现基于多版本并发控制(MVCC),我们日常使用的不加锁的select就是一种快照读(当事务隔离级别退化为串行时,默认select就是当前读)。

快照读是为了解决上文中提到的事务ACID特性中的Isolation隔离性而诞生的,有了快照的存在,会让每个事务只看到自己应该看到,仿佛数据库系统只有当前一个事务在执行一样,正是隔离性的体现。

MVCC实现原理-ReadView

MySQl实现快照读的原理就是MVCC(Multi-Version Concurrency Control,多版本并发控制),而MVCC的核心就是快照ReadView,根据设置的不同的隔离级别(RC/RR)在不同的时机拍一张当前能看到记录的快照,这张照片就是ReadView。

拍个照纪念下

假设当前有一张student表,数据内容如下:

id name
1 Alice

那么在实际的数据库存储中,mysql会有隐藏的几行:

  • DB_TRX_ID
    该行记录的最近修改过的事务id,就像是文件系统里的最近修改时间一样,是生成ReadView时可见性判断的重要依据
  • DB_ROLL_PTR
    回滚指针,如果这个记录被修改过,那么会指向上一个版本,形成了一个历史版本的链表
  • DB_ROW_ID
    隐藏主键,当我们的表没有指定主键的时候,这个字段就会作为聚簇索引

所以这个student表实际在db中存储的格式可能为

student表的实际存储格式

上图表明当前这个记录是 1:Bob,最近修改的事务id是4,隐藏主键也是1。
这个记录历史上最早name是Cao(事务6创建的),
之后被事务1修改为Bob,
最后被事务4修改为Alica

基于ReadView的快照读

依据当前的事务隔离级别,MySQL事务会在某个时机下生成一个ReadView(开始拍照片),基于上面理解,我们知道,一行记录会有DB_ROLL_PTR(回滚指针)拉起来的一个链表,指向改行记录的历史版本。在快照读的时候,会基于DB_TRX_ID判断 这行记录是否可见,从而完成查询,整个过程如下图。

快照读流程

ReadView可见性算法

如何判断这行记录是否可见?ReadView是通过维护了这几个值来判断的:

  • trx_list
    快照生成时还在活跃的事务id(活跃指的是处于 begin -> dosomething -> commit的dosomething阶段,尚未commit的事务)
  • up_limit_id
    记录trx_list中最小的事务id,小于这个事务id的事务必然都已经提交过了,可以理解为是历史记录,肯定可见
  • low_limit_id
    记录ReadView生成时刻系统尚未分配的下一个事务ID,也就是目前已出现过的事务ID的最大值+1,这个id之后都是快照生成之后操作,肯定不可见

基于以上三个属性,可以将student表中各个记录以及其undo log中的回滚版本记录的DB_TRX_ID归类为如下。

可见性

基于以上信息,可以得到MVCC判断某行记录是否可见的伪代码如下:

def is_visible(row):
    if row.DB_TRX_ID < up_limit_id:
        return true # 1. 操作事务是历史事务了,肯定可见
    if row.DB_TRX_ID > low_limit_id:
        return false # 2. 操作事务是一个晚于当前ReadView的事务,肯定看不到
    if row.DB_TRX_ID in trx_list:
        return false # 3. 在活跃事务中(说明尚未提交),不可见
    return true

ACID中的隔离性与并发性的讨论

四种隔离级别总结

MySQL四种隔离级别总结如下:

隔离级别 名称的含义 可能有的问题
READ UNCOMMITTED 这个模式下,一个事务能读(read)到另一个事务还没提交的(uncommitted)更改 脏读,另一个事务还没提交的修改,这种不完整的数据称为脏数据
READ COMMITTED 这个模式下,一个事务能读(read)到另一个事务已经提交的(committed)更改 不可重复读,因为如果重复读的话就会看到结果变了(灵异事件,害怕.jpg)
REPEATABLE READ(MySQL默认 这个模式下,一个事务能重复读(read)同样的记录保证结果相同(不管另一个事务提交还没提交)(可以简单理解为做了一个这个事务id为key的缓存) 幻读,在一个事务中,第一次查询某条记录,发现没有,但是,当试图更新这条不存在的记录时,竟然能成功,并且,再次读取同一条记录,它就神奇地出现了(害怕.jpg)
SERIALIZABLE 完全串行,一个事务开启后,另一个事务被挂起 无任何问题,就是没法并发了,性能很差。幻读出现的条件很苛刻,而且一般来说事务中不会有人更新一个“不存在的记录”,所以一般RR隔离级别足够

READ UNCOMMITTED

基于上文对ReadView的理解,我们可以从原理上理解各个隔离级别是怎么做到的。RU级别,即没有MVCC机制的情况下的方式,即每次select都是获取到某行记录最新的结果。

READ UNCOMMITTED

READ COMMITTED

从RC隔离级别开始,我们开始使用MVCC机制了,在每次读操作的时候都会建立建立一次ReadView,这个ReadView自然会隐藏掉本事务不该见到的记录。

READ COMMITED

REPEATABLE READ(默认隔离级别)

从上面的分析可以看出,每次读操作都建立一次ReadView确实可以保证 在活跃事务中(说明尚未提交)的记录不可见,但是每次读都生成新的ReadView展示当前最新的可见性,会导致看到其它事务已提交的修改,这又在某些场景下不符合隔离性的 仿佛没有其它事务在执行 的要求。因此又有了更严格的RR级别,这种级别下 第一次执行操作后 会生成ReadView,并且以后的查询操作都会使用这一版本的ReadView,保证了可见性始终如一。

REPEATABLE READ

如上图所示,RR级别在某种意义上来说已经是一种非常完美的隔离级别了,事务2的整个执行过程都仿佛完全感知不到事务1的存在。只是这种模式下有一种产生幻读的可能,如果此时事务1插入了一条id=2的记录,这时事务2还是看不到的,但是如果这时事务2更新了这条id=2的记录,那么我们可以发现MySQl会将这条被更新的最新结果加入到事务2当前的ReadView中,从而导致出现了幻读的现象。

SERIALIZABLE

事务的最高隔离级别,不存在任何的脏读、不可重复读、幻读的问题,事务完全按照顺序线性执行,性能极大损失,一般不会使用。

SERIALIZABLE

reference

  1. ^ 事务-百度百科
  2. ^ Repeatable Read - 廖雪峰的官方网站
  3. ^ mysql 设置隔离级别 - anobscureretreat - 博客园
  4. ^ MVCC多版本并发控制 - 简书
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容