抖音技术能力大揭密!钜惠大礼、深度体验,尽在火山引擎增长沙龙,就等你来! 立即报名>> 了解详情
写点什么

隔离级别的追溯与究明,带你读懂隔离级别(上)

2021 年 3 月 16 日

隔离级别的追溯与究明,带你读懂隔离级别(上)

本篇文章选自事务前沿研究系列文章。


本文主要介绍事务的隔离级别定义。由于隔离级别的一些较为先进的学术定义并未被广泛接受,本文将按照不同的隔离级别定义提出的顺序,分析各种定义的合理性和不足。


最早的 ANSI SQL-92 提出了至今为止仍然是应用最广的隔离级别定义,读提交、可重复读、可序列化。但是 「A Critique of ANSI SQL Isolation Levels」这篇文章指出了 ANSI SQL-92 的缺陷,并对其做出了补充。「Generalized Isolation Level Definitions」这篇文章,指出了此前对隔离级别定义重度依赖数据库的实现,并且提出了与实现无关的隔离级别定义。最后本文会在这些定义的基础上分析 MySQL 和 TiDB 的隔离级别,正确理解在 Snapshot Isolation 隔离级别下同时存在快照读和当前读时出现的一些异常现象的内在原因。


由于隔离级别涉及的内容较多,跨度较大,所以我们将其分为上下两篇。上篇将介绍对在两阶段锁的数据库中,人们对隔离级别的理解;而下篇将会介绍与数据库实现无关的隔离级别定义,并且对 TiDB 进行分析。


事务隔离性的基础概念


事务是一系列操作的集合,具有 ACID 的特性,其中 A 指的是原子性(Atomicity),I 指的是隔离性(Isolation)。


原子性指的是一个事务中的所有操作,要么全部成功、要么全部失败。但是原子性并不能确保事务的过程也是原子发生的,约束事务过程的是隔离性。一个理想的数据库执行多个事务时,从结果看来,这些事务可以看作是顺序执行的,即每个事务的过程也是原子发生的。图 1 展示了是否可序列化在外部观测的区别,左图表示的是从结果看来,可以将看成是按照顺序完整执行了 T1 -> T3 -> T2;但是在右图中,T1 的执行过程中掺入了 T2 的执行,T2 的执行过程中掺入了 T3 的执行,即发生了语义上的非 serializable。在这种情况下,隔离级别定义的是数据库事务间的隔离程度



图 1 - 可序列化和不可序列化的对比

ANSI SQL-92


ANSI SQL-92 提出了最经典的隔离级别定义,包括读未提交(Read Uncommitted)、读提交(Read Committed)、可重复读(Repeatable Read)和可序列化(Serializable)。



Dirty ReadNon-repeatable ReadPhantom Read
Read UncommittedPossiblePossiblePossible
Read CommittedNot PossiblePossiblePossible
Repeatable ReadNot PossibleNot PossiblePossible
SerializableNot PossibleNot PossibleNot Possible

表 1 - ANSI SQL-92 隔离级别


相比于 Phantom Read,Dirty Read 和 Non-repeatable Read 要好理解很多,但因为大部分网络资料对于 MySQL 的 Phantom Read 的解释是存在误区的(把混淆快照读和当前读出现的现象当作 Phantom Read),本文仅对 Phantom Read 做详细的解释。

Predicate - Item


Predicate 的中文翻译是谓词,严格来说,所有的查询条件都属于谓词;而相对的,在 KV 存储引擎中直接读取某个 key 的行为则称为 item。然而关系型数据库在 KV 之上还有 SQL 层,SQL 层即使是读取某个 key 也是通过一些查询条件(predicate)来进行描述的,当我们在 SQL 层面之上讨论是 predicate 还是 item 的时候,需要考虑它是否是一个点查询。点查询是一种查找数据的方法,通过建立好的索引去定位数据的 key,一般能够用非常高的效率查找到所需的数据,其查询的过程和读取某个 key 相似,所以本文的观点认为:


  • 点查询是 item 类型的查询条件。

  • 其他查询均是 predicate 类型的查询条件。

Phantom Read


Phantom Read 是 Non-repeatable Read 的 predicate 的版本,这两种异常现象都需要在一个事务中进行两次读操作,Non-repeatable Read 指的是两次 item 类型的读操作读到了不同的内容,而 Phantom Read 则是指两次 predicate 类型的读操作读到了不同的结果。如图 2 所示,左图表示的是 Non-repeatable Read,右图表示的是 Phantom Read。



图 2 - Non-repeatable Read 与 Phantom Read



例 1 - 虚假的 Phantom Read


例 1 给出了一种对 MySQL 下 Phantom Read 常见的举例,本文认为这是一种虚假的 Phantom Read,因为其本质原因在于 MySQL 的快照读和当前读的混合使用。在有些地方当前读被描述为“降级到 Read Committed 隔离级别”,这个例子所展示的,是两种隔离级别混合使用所带来的一些不符合直觉的现象,在后文讲述快照读和当前读的时候会更加详细的说明这一点。

小结


ANSI SQL-92 所给出的隔离级别的定义被广泛使用,但也造成了今天隔离级别指代混乱的现象,其原因在于这一套定义是不够严谨的,例如 Serializable 被定义为不会发生 Dirty Read,Non-repeatable Read 和 Phantom Read,但这并不与真正的 Serializable 在语义上等价,后来所提出的 Write Skew 现象就是一种违反 Serializable 的行为,但这并没有被 ANSI SQL-92 所包含。

A Critique of ANSI SQL Isolation Levels


「A Critique of ANSI SQL Isolation Levels」这篇文章指出了 ANSI SQL-92 所遗漏的一些问题,同时针对 ANSI SQL-92 的隔离级别在两阶段锁(2PL) 的数据库实现之下提出了更高的要求,最后,这篇文章给出了 Snapshot Isolation 的隔离级别

异常现象


在异常现象中,P1 - P3 是来自于 ANSI SQL-92 的异常现象,因为 ANSI SQL-92 只有语言上的描述,没有准确的定义这些异常,所以这篇文章对其做了两种解释,用 P (Phenomenon) 表明可能发生异常的现象,用 A (Anomaly) 表示已经发生的异常。因为 P 只是代表可能发生异常,所以也被称为扩大解释(broad interpretation),而 A 则称为严格解释(strict interpretation)。

P0 - Dirty Write


脏写是两个事务同时写一个 key 发生冲突的现象,在 2PL 的数据库实现下,如果两个未提交的事务同时写一个 key 失败了,就代表可能会发生异常,如例 2,理由有两点:


  • 当 T1 先成功提交,T2 随后发生 rollback 时,应该回滚到哪个值是不明确的;

  • 如果还写了其他的 key,可能会破坏约束的一致性。



例 2 - Dirty Write 的扩大解释

P4 - Lost Update


写丢失指的是一个事务在尝试根据读到的数据进行写入之前,在其他事务上有另外的写入发生在了读写操作之间,并且成功提交,于是当这个事务继续进行的时候,就会将已经成功提交的写入覆盖掉的现象,造成写入丢失。在例 3 中,T1 和 T2 都需要把 x 的值加一,T1 根据读到的值 10 将 11 写入,写入前,T2 也将 11 写入 x,在 Serializable 的情况下,最后 x 的值是 12,而此时因为丢失了一个事务的写入,x 的最终值是 11。注:根据论文的解释,T2 不一定需要被 commit,此处为了方便理解所以稍微改造了例子。



例 3 - Lost Update 的扩大解释

P4C - Cursor Lost Update


在数据库的实现中,为了保证性能,往往会将读操作分为两类,不加锁读和加锁读,有的数据库可以通过加锁读来防止出现 Lost Update 的现象,对于这种情况,我们就称数据库防止了 P4C 现象的发生,例 4 表示了 P4C 现象。加锁读在关系型数据库里一般实现为 select for update。



例 4 - Cursor 条件下的 Lost Update

A5A - Read Skew


Read Skew 的现象是因为读到两个状态的数据,导致观察到了违反约束的结果,例 5 中的 x 和 y 的和应该等于 100,而在 T1 里,却读到了 x + y = 140,需要注意的是因为 Read Skew 现象中没有重复读取同一个 key,所以不属于 Non-repeatable Read。



例 5 - Read Skew 的违反约束的现象

A5B - Write Skew


Write Skew 是两个事务在写操作上发生的异常,例 6 表示了 Write Skew 现象,即 T1 尝试把 x 的值赋给 y,T2 尝试把 y 的值赋给 x,如果这两个事务 Serializable 的执行,那么在结束之后 x 和 y 应该拥有一样的值,但是在 Write Skew 中,并发操作使得他们的值互换了。



例 6 - Write Skew 的违反约束的现象

P1 - A1 - Dirty Read


Dirty Read 的严格解释是需要一个成功提交的事务读取到一个不会提交的事务的写入内容,如例 7 所示;但是其扩大解释只需要 T1 读取到还未提交的事务的写入内容就算发生了 Dirty Read,如例 8 所示。图 3 解释了采用扩大解释的原因,在这个例子中,T2 读到了 T1 对 x 的写入之后的值,但是读到了 T1 对 y 写入之前的值,因此造成了读到破坏约束的数据。



例 7 - Dirty Read 的严格解释



例 8 - Dirty Read 的扩大解释



图 3 - Dirty Read 在扩大解释下的异常

P2 - A2 - Non-repeatable(Fuzzy) Read


Non-repeatable Read 指的是两次 item 类型的读操作读到了不同的数据。如例 9 所示,在严格解释下需要进行完整的两次读取;但是扩大解释则认为在一个事务读了某个 key 之后,如果读事务还没提交,有事务写这个 key 成功了就可能出现异常,换句话说,读请求应该阻塞写请求。图 4 解释了采用扩大解释的原因,因为 T1 对 x 的读取没能阻塞住 T2 对 x 的写入,导致之后读到了 T2 写入的 y,结果从 T1 观察到的结果来看,x + y = 140 破坏了约束。



例 9 - Non-repeatable Read 的严格解释



例 10 - Non-repeatable Read 的扩大解释



图 4 - Non-repeatable Read 在扩大解释下的异常

P3 - A3 - Phantom


和 ANSI SQL-92 所定义的 Phantom Read 不同,这篇文章把这一异常现象称为 Phantom,例 11 列举了标准的 Phantom Read,其特点是需要两次 predicate 类型的读操作读到了不同的数据。Phantom 现象比 Phantom Read 的定义要更加广一些,如例 12 所示,Phantom 现象的扩大解释只要在一个事务进行了 predicate 类型的读取,而另一个事务对其中的某个 key 进行了写操作,就可能出现异常,换言之,predicate 类型的读请求应该阻塞对它所读取到的数据的写请求。图 5 解释了采用扩大解释的原因,并且指出了以一种一个 predicate 和一个 item 类型的读取到的结果不一致的情况,在 Serializable 的数据库下,通过 predicate 类的查询计算 x 和 y 的和应当获得和通过 item 类的查询直接读取 sum 这个 key 一样的结果



例 11 - Phantom 的严格解释(Phantom Read)



例 12 - Phantom 的扩大解释



图 5 - Phantom 在扩大解释下的异常

Snapshot Isolation


这篇文章还提出了 Snapshot Isolation (SI),在 SI 的隔离级别下,事务会从它开始时间点的快照进行数据的读取,不同于两阶段锁,SI 一般利用 MVCC 的无锁特性来提高性能,因为对一个 key 能够保存多个版本的数据,SI 能够做到读不阻塞写,甚至写也不阻塞写。


熟悉 TiDB 乐观事务的同学可能会发现,如果根据上面给出的 P0 - Dirty Write 的定义,SI 是属于可能发生异常现象的。在乐观事务中,两个事务同时写一个 key 都会成功,但是最后在提交的时候会有一个事务失败,这一机制被称为 “first-committer-wins”。虽然 SI 可能会发生 P0(可能出现异常的现象),但不会发生 Dirty Write 的异常现象(我们可以将其称为 A0)。


P1 描述的现象对 SI 来说并没有意义,因为 SI 能够找到它需要读取的版本,并不会因为出现 P1 而读到违反约束的现象。P2 - P3 所描述的可能发生异常状态的现象会出现在 SI 当中,但是因为 SI 总是从一个快照版本中去读取,所以在所有读到的数据都是同一个版本下的,也不会因此出现违反约束的现象。因此,我们使用 A1 - A3 (严格解释)来约束 SI

隔离级别


图 6 给出了补充后的隔离级别定义,在 ANSI SQL-92 的基础上加入了 Cursor Stability 和 Snapshot Isolation 的隔离级别。Cursor Lost Update 指的是数据库能够通过 cursor 来防止 lost update,而 Snapshot Isolation 没有对 Phantom 现象做出强制性的要求。



图 6 - 隔离级别的定义

小结


这是一篇有里程碑意义的文章,不管是对异常现象的补充,还是提出 Snapshot Isolation 的隔离级别都有着巨大的意义,到今天这些概念被广泛应用。但是其中依然有些在今天看来不合适的观点:


  • 实现相关,P0 - P4 在指出异常的同时甚至还规定了要怎么加锁。

  • 使用了扩大解释,对于论文中给的理由,本文认为出现破坏约束的真实原因是 P0 (Dirty Read) 读到了事务的中间状态、而 P1(Fuzzy Read) 和 P2 (Phantom) 则是从两个状态中读取了数据,导致发生了约束被破坏和不一致的结果。

  • 默认 Snapshot Isolation 是一种存储了多版本的隔离级别,但标准的定义不应该考虑数据库实现的方式。

  • 扩大解释没有被 Snapshot Isolation 所采用,这里存在标准的不一致。

  • 所定义的 Serializable 依旧在语义上和真正的 Serializable 不等价。

总结


在本篇文章中,我们总结了对于隔离级别的早期认知,这些观点大部分都是建立在两阶段锁的数据库之上的。但是以今天的观点来看,其中多少有些不合适之处,但这些观点已经被广泛接受,所以正确理解这些异常现象的真正内涵是十分有必要的。


作者介绍:


童牧:PingCAP 研发工程师。

2021 年 3 月 16 日 18:201655

评论

发布
暂无评论
发现更多内容

隔壁工程师都馋哭了我的逆向工程IDA,说要给我搓背捏脚

网络安全学海

网络安全 信息安全 渗透测试 漏洞分析 逆向工程

《持之以恒的从事运动》五

Changing Lin

7月日更

详聊微服务观测|从监控到可观测性,我们最终要走向哪里?

尔达Erda

开源 微服务 云原生 APM PaaS

5分钟速读之Rust权威指南(三十九)unsafe

码生笔谈

rust

pha挖矿系统源码开发

获客I3O6O643Z97

区块链+ PHA矿机挖矿 PHA质押挖矿

你的直观感受有可能是错的

石云升

学习笔记 认知偏差 7月日更

Linkflow CDP亮相GDMS全球数字营销峰会

Linkflow

CDP 用户画像 数字营销

区块链技术在“三资”监管领域的应用

CECBC区块链专委会

自建开发工具系列-Webkit内存动量监控UI(一)

Tim

FrontEnd 调试工具 Webkit 工具UI

面对大规模 K8s 集群,这款诊断利器必须要“粉一波”!

尔达Erda

开源 云原生 operator PaaS kubernete

推荐系统的价值观(三十二)

数据与智能

价值观 推荐系统

边界防御·信息安全保密圈的 “丈八蛇矛”

郑州埃文科技

模块八 - 设计消息队列存储消息数据的 MySQL 表格

华仔架构训练营

如何科学地系统地梳理出CDP的RFP?

Linkflow

打造中国数字军人 数军科技携黑科技亮相(北京)军博会

科技热闻

讨论 | 低代码能解决制造业企业数字化转型所面临的问题吗?

优秀

低代码

禾木之变:2021我们该如何持续拥抱AI?

脑极体

为什么公司应该效仿开源的文化

WorkPlus Lite

深入浅出 Gitalk 留言插件

悟空聊架构

网站 开源项目 7月日更 网站建设 留言

我看 JAVA 之 并发编程【一】FutureTask & Callable

awen

Java 多线程 Callable FutureTask

数字政府建设如火如荼 区块链保证数据真实安全

CECBC区块链专委会

百度程序员推荐的书籍,今天免费送!

百度Geek说

Ubuntu Server 20.04搭建zookeeper集群

玏佾

zookeeper 群集安装 搭建 zk 集群部署

从零开始学习3D可视化之摄像机

森友小锘

前端 可视化 数字孪生

第一周作业-对比不同公司产品招聘JD

小夏

产品经理训练营 邱岳

《面试八股文》之kafka21卷

moon聊技术

kafka 面试

前端 JavaScript 实现一个简易计算器

编程三昧

JavaScript 前端 代码实现

爱奇艺奇秀直播的秒播体验优化实践

爱奇艺技术产品团队

直播 优化

浅谈云上攻防——Web应用托管服务中的元数据安全隐患

腾讯安全云鼎实验室

安全攻防 云安全 元数据 网络攻防

或许早已封神!阿里最牛的分布式核心原理深度解析全彩手册

程序员小毕

Java 程序员 架构 面试 分布式

架构实战营模块8作业

Geek_649372

架构实战营

Study Go: From Zero to Hero

Study Go: From Zero to Hero

隔离级别的追溯与究明,带你读懂隔离级别(上)-InfoQ