InnoDB事务隔离实现原理

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: ### 前言大部分服务端系统都是数据密集型应用,主要的功能是基于数据库对各种业务数据进行增删查改。在互联网这种高并发场景,如何确保数据的准确性以及保证系统的吞吐量,事务的隔离性有很大一部分功劳,本文主要探究MySQL数据库InnoD存储引擎的事务隔离级别及其背后实现原理,并且会回答以下问题:1. 不同事务隔离级别解决的问题,如何解决的?2. MVCC和数据库锁之间的相同和不同之处是什么?

前言

大部分服务端系统都是数据密集型应用,主要的功能是基于数据库对各种业务数据进行增删查改。在互联网这种高并发场景,如何确保数据的准确性以及保证系统的吞吐量,事务的隔离性有很大一部分功劳,本文主要探究MySQL数据库InnoD存储引擎的事务隔离级别及其背后实现原理,并且会回答以下问题:

  1. 不同事务隔离级别解决的问题,如何解决的?
  2. MVCC和数据库锁之间的相同和不同之处是什么?
  3. 如何根据自己系统的特性来选择事务隔离级别?

事务是什么

事务是数据库一个不可分的工作单元,可以将多个操作步骤表示为一个步骤,事务拥有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的定义都是理论上的,实际情况可能并没有严格满足,不同事务隔离级别表现并不相同,所以下面会讨论事务的隔离级别

事务异常情况

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

时间 事务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

其中幻读和不可重复读很类似,都是多次查询得到的结果不同,具体的区别在于是对一行数据多次查询还是对一批数据进行多次查询。这三个事务异常情况也是事务隔离级别需要解决的问题。

基本概念

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

两阶段锁协议

在 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背后相关的逻辑,最普通的查询语句就是快照读

select * from table

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

select * from table lock in share mode 读的时候会加一把共享锁

select * from table for update 读的时候会加一把排他锁

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

事务隔离级别

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进行索引数据行的锁定

mysql官方文档是这么描述串行化的查询的

  • SELECT ... FROM is a consistent read, reading a snapshot of the database and setting no locks unless the transaction isolation level is set to SERIALIZABLE. For SERIALIZABLE level, the search sets shared next-key locks on the index records it encounters. However, only an index record lock is required for statements that lock rows using a unique index to search for a unique row.
  • This level is like REPEATABLE READ, but InnoDB implicitly converts all plain SELECT statements to SELECT ... LOCK IN SHARE MODE if autocommit is disabled. If autocommit is enabled, the SELECT is its own transaction. It therefore is known to be read only and can be serialized if performed as a consistent (nonlocking) read and need not block for other transactions. (To force a plain SELECT to block if other transactions have modified the selected rows, disable autocommit.)

效果:

和不可重复读效果一样,解决了脏读、不可重复读、幻读,并且比不可重复读的并发控制更加严格

如何选择?

MySQL InnoDb引擎默认的事务隔离级别是可重复读,但是阿里内部设置的数据库隔离级别是读已提交,这是为什么呢?谈谈我自己的看法

  1. 不可重复读并不一定是缺点,虽然第二次读到的数据和第一次不一样,但是第二次的数据来自于其他事务提交过的正确数据,那么就代表第一次读的数据已经是旧的、过时的数据了,那为什么要基于一份旧的、过时的数据进行操作呢?而是及时止损,用最新的数据进行后续业务操作处理。
  2. Oracle的默认事务隔离级别是读已提交,猜测是之前去IOE的过程中,方便Oracle到MySQL的迁移,沿用了读已提交
  3. 虽然可重复读用了大量锁算法来保证可重复读和幻读,但是幻读对业务影响有限,如第一点所述,幻读出来新增的数据也确实是其他事务正确提交得来的,幻读出来的数据也是最新的数据。当然幻读在出库备库数据复制过程可能带来数据不一致,但是可以通过修改bin log的数据格式得到解决
  4. 读已提交锁算法更简单,锁粒度更细并发度更高,更加适合互联网场景,同时方便开发人员排查死锁等线上问题

参考资料

InnoDB Locking and Transaction Model

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

《数据库系统内幕》

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
7月前
|
存储 缓存 关系型数据库
⑩⑧【MySQL】InnoDB架构、事务原理、MVCC多版本并发控制
⑩⑧【MySQL】InnoDB架构、事务原理、MVCC多版本并发控制
228 0
|
关系型数据库 MySQL 数据库
InnoDB事务和锁定信息:如何识别和解决阻塞查询问题
InnoDB事务和锁定信息:如何识别和解决阻塞查询问题
|
SQL 关系型数据库 MySQL
【Mysql-InnoDB 系列】事务模型
提到事务,大家都有基本的了解,例如mysql的事务隔离级别包括:读未提交、读已提交、可重复读、串行化;InnoDB默认是RR(可重复读);基本的MVCC等等。但大部分人对深入一些的原理就知之甚少了。本文整理事务模型的相关内容,仅供参考。
132 0
|
6月前
|
存储 关系型数据库 MySQL
MySQL数据库进阶第六篇(InnoDB引擎架构,事务原理,MVCC)
MySQL数据库进阶第六篇(InnoDB引擎架构,事务原理,MVCC)
|
3月前
|
关系型数据库 MySQL 数据库
InnoDB 的 MVCC 实现原理
InnoDB 的 MVCC 实现原理
51 0
|
7月前
|
SQL 安全 关系型数据库
详解InnoDB(1)——事务
详解InnoDB(1)——事务
95 1
|
7月前
|
存储 关系型数据库 MySQL
InnoDB 引擎底层事务的原理
InnoDB 引擎底层事务的原理
|
存储 SQL 关系型数据库
【Mysql-InnoDB 系列】事务提交过程
MySQL InnoDB的事务模型、锁机制等设计,都是InnoDB架构设计的一部分。要全面了解它的设计实践,就必须从头看起。只有这样,才能够弄清楚它的设计思想,理解其实现上的精妙之处。
599 1
|
算法 关系型数据库 MySQL
【Mysql-InnoDB 系列】幻读、死锁与事务调度
本篇继续分析Mysql InnoDB引擎中的幻读、死锁和事务调度的相关问题
102 0
|
存储 SQL 缓存
MySQL InnoDB如何保证事务特性
MySQL InnoDB如何保证事务特性
146 0