InnoDB事务隔离级别及其实现原理

Posted Hollis Chuang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了InnoDB事务隔离级别及其实现原理相关的知识,希望对你有一定的参考价值。

 大部分服务端系统都是数据密集型应用,主要的功能是基于数据库对各种业务数据进行增删查改。在互联网这种高并发场景,如何确保数据的准确性以及保证系统的吞吐量,事务的隔离性有很大一部分功劳

本文主要探究mysql数据库InnoD存储引擎的事务隔离级别及其背后实现原理,并且会回答以下问题:

  1. 不同事务隔离级别解决什么问题,如何解决的?

  2. MVCC和数据库锁之间的相同和不同之处是什么?

  3. 如何根据自己系统的特性来选择事务隔离级别?

01

事务是什么?

事务是数据库一个不可分的工作单元,可以将多个操作步骤表示为一个步骤,事务拥有ACID四大特性即

原子性(Atomicity):事务的操作是不可分的,即使有多个操作步骤,要么全部执行成功,要么全部执行失败。背后的实现原理是通过undo log做到的,回滚的时候基于undo log上的数据,如果执行了INSERT,那就再做一次DELETE,如果做了一次UPDATE,则会做一个相反的UPDATE。

隔离性(Isolation):并行执行的事务之间互相不影响,即A事务操作但未提交的数据,不会影响B事务的处理结果。背后的实现原理是通过MVCC以及数据库锁做到的,这部分也是本文介绍的重点。

持久性(Durability):事务一旦提交,其结果就是持久的,即使发生宕机,事务提交的数据也能恢复。背后的实现原理是通过redo log做到的,每次提交事务redo log都会刷到磁盘里。redo log作为重做记录了数据库的物理日志,上面记录了某个数据页做了什么修改,即使宕机也可以根据redo log恢复数据页的内容。

一致性(Consistency):事务执行前后会从一个正确一致状态到另一个正确一致状态,一致性是ACID里约束最弱的特性,因为这个特性更像一个结果。即做到了原子性、隔离性、持久性,自然就做到了一致性。

当然上述关于事务ACID的定义都是理论上的,实际情况可能并没有严格满足,不同事务隔离级别表现并不相同,所以下面会讨论事务的隔离级别

02


事务异常情况

脏读:一个事务读到了另一个事务未提交的数据

时间

事务A

事务B

1


插入库存为10

2

查询库存为10


3


回滚事务

不可重复读:一个事务里多次查询一条数据,得到的结果不同

时间

事务A

事务B

1

查询库存为10


2


修改库存为11

3


提交事务

4

查询库存为11


幻读:一个事务里多次查询一批数据,得到的结果不同,看到了新插入的行数据

时间

事务A

事务B

1

查询一个班级人数为10


2


班级增加1人

3


提交事务

4

查询一个班级人数为11


03


基本概念

接下来会介绍隔离级别是如何解决这些异常情况的,先介绍几个基本概念

两阶段锁协议

在 InnoDB 事务中,行锁是在用到的的时候才加上的,但并不是不再用了就立刻释放,而是要等到事务提交或回滚时才释放

锁的类型

共享锁(shared lock):允许事务读一行数据

排他锁(exclusive lock):允许事务删除或更新一行数据


共享锁

排他锁

共享锁

兼容

不兼容

排他锁

不兼容

不兼容

锁的算法

行锁(record lock):针对单行记录的锁

间隙锁(gap lock):锁定一个范围但不包含记录本身,在可重复读级别下才存在

Next-Key Lock:间隙锁+行锁,锁定一个范围并且包含记录本身,每个next-key lock都是前开后闭区间,假设数据库里有3条记录0,5,10 那么就有(-∞,0]、(0,5]、(5,10]、(10,+∞]4个可能的next-key lock,在可重复读、串行化级别下才存在

快照读与当前读

多版本并发控制(MVCC):一行数据有多个版本,根据事务Id获取一致性读视图,数据版相关内容维护在undo log里,在读已提交、可重复读级别下才存在,MVCC避免了锁的争抢,通过空间换时间来保证了数据读取的正确性

快照读:读取的是快照数据,可能并不是最新数据,如果MVCC生效就会走到MVCC背后相关的逻辑,最普通的查询语句就是快照读

select * from table

当前读:读取到的是最新数据,且读的时候会进行加锁

select * from table lock in share mode

读的时候会加一把共享锁

select * from table for update

读的时候会加一把排他锁

实际情况在获取共享锁、排他锁的过程,还有意向共享锁,意向排他锁的获取,对本文想阐述的内容关系不大,不做赘述。

04


事务隔离级别

SQL标准定义了四个隔离级别

  1. 读未提交(READ UNCOMMITED)

  2. 读已提交(READ COMMITED)

  3. 可重复读(REPEATABLE READ)

  4. 串行化(SERIALIZABLE)


    脏读

    不可重复读

    幻读

    读未提交

    未解决

    未解决

    未解决

    读已提交已解决
    快照读未解决,通过显示加锁可解决
    未解决

    可重复读

    已解决

    已解决

    InnoDB引擎已解决

    串行化

    已解决

    已解决

    已解决

读未提交

特点:读的时候未对数据加锁,修改数据的时候对数据添加行级共享锁

效果:因为修改数据时候添加的是共享锁,所以会被其他事务读到,产生

脏读的问题

读已提交

特点:

(快照读)读的时候会通过MVCC的一致性视图读到最新的、已提交事务

的数据版本

(当前读)根据sql语句的不同读数据的时候,会加一把共享行锁(lock in

share mode)或者排他行锁(for update)

当读取数据不存在的时候,不会加锁。修改数据的时候会对数据行添加行

级排他锁,事务结束的时候才会释放,如果在执行语句的时候没有走到索

引,并且在修改的时候如果发现某行数据已经被锁定,这时会通过MVCC

读到最新的、已提交事务的数据版本再根据where条件来决定是否必须更

新改行,从而决定是否要等待锁的释放

效果:

(快照读)因为通过MVCC读取的是已提交事务的数据版本,所以不会存在

脏读。通过MVCC读到的数据,没有进行加锁操作, 所以其他事务仍然可

以对读到的数据进行修改并提交,这时候再次读取就发生了不可重复读。

(当前读)通过显示加锁,防止了其他事务对其行数据的修改,从而避免了

不可重复读

可重复读

特点:

(快照读)读的时候会通过MVCC的一致性视图读到开启事务时刻、已提

交事务的数据版本

(当前读)根据sql语句的不同读数据的时候,会加一把共享锁(lock in

share mode)或者排他锁(for update), 如果查询行不存在,next-key lock

则会退化成gap lock修改数据的时候会对数据行添加next-key lock不仅锁

住行数据本身,还锁住相邻的间隙,事务结束的时候才会释放

效果:

(快照读)因为通过MVCC读取的是开启事务时刻,已提交事务的数据版

本,所以无论其他事务怎么修改,看到的数据都是雷打不动的固定的数据

版本,这时候再次读取就避免了不可重复读。因为在修改数据的时候使用

了next-key lock,所以即使进行插入操作也会被锁阻塞,这样也避免了幻

读情况的产生。在实际情况中,锁的算法比较灵活 ,根据sql语句的不

同,和命中索引的区别,即有next-key lock+gap lock锁住更大范围的情

况,也有在唯一索引场景 next-key lock退化成 record lock以及gap lock

的情况,这里后面可以单独拉一篇文章进行阐述,内容较多

串行化

特点:

串行化和可重复读很相似,

区别是串行化级别会为每一个普通的

select *from table

语句隐式转化为

select *from table lock in share mode

给索引增加一个共享的next-key lcok,同时也不存在通过MVCC读取数据

的情况。针对修改,和可重复读一样会通过next-key lock进行索引数据行

的锁定

效果:

和不可重复读效果一样,解决了脏读、不可重复读、幻读,并且比不可重

复读的并发控制更加严格

05


如何选择?

MySQL InnoDb引擎默认的事务隔离级别是可重复读,但是阿里内部设置

的数据库隔离级别是读已提交,这是为什么呢?谈谈我自己的看法

  1. 不可重复读并不一定是缺点,虽然第二次读到的数据和第一次不一样,但是第二次的数据来自于其他事务提交过的正确数据,那么就代表第一次读的数据已经是旧的、过时的数据了,那为什么要基于一份旧的、过时的数据进行操作呢?而是及时止损,用最新的数据进行后续业务操作处理。

  2. Oracle的默认事务隔离级别是读已提交,猜测是之前去IOE的过程中,方便Oracle到MySQL的迁移,沿用了读已提交

  3. 虽然可重复读用了大量锁算法来保证可重复读和幻读,但是幻读对业务影响有限,如第一点所述,幻读出来新增的数据也确实是其他事务正确提交得来的,幻读出来的数据也是最新的数据。当然幻读在出库备库数据复制过程可能带来数据不一致,但是可以通过修改bin log的数据格式得到解决

  4. 读已提交锁算法更简单,锁粒度更细并发度更高,更加适合互联网场景,同时方便开发人员排查死锁等线上问题

所以在没有对不可重复读、幻读有强烈诉求的情况下,读已提交是一个不错的选择

06


参考资料

  InnoDB Locking and Transaction Model

《MySQL技术内幕 InnoDB存储引擎 第2版》

《数据库系统内幕》

本文转载自公众号:

以上是关于InnoDB事务隔离级别及其实现原理的主要内容,如果未能解决你的问题,请参考以下文章

MySQL事务隔离级别的实现原理

4种事务的隔离级别,InnoDB如何巧妙实现?

mysql数据引擎

MySQL-InnoDB究竟如何巧妙实现,4种事务的隔离级别(收藏)

深入解析Mysql中事务的四大隔离级别及其所解决的读现象

MySQL数据库引擎详解