《Designing Data-Intensive Applications》第7章 读书笔记(2):事务隔离级别与异常

1.说明

如果两个事务同时读写同一份数据,会引发并发竞争问题。
而理论上,隔离性会使得一切变得简单,因为可以让你认为不会有并发竞争出现
如串行化隔离,代表db保证事务执行的结果和他们串行化执行的结果一样。
但是串行化的代价较高,实际上很多系统会用一些弱化的隔离标准
本节主要讲解

几种异常:脏读,脏写,Lost update,幻读
几种隔离标准:未提交读(书中没有展开),读提交,可重复读,串行化(下一节再讲)
以及一些实现的讲解

2.读提交

最基本的是读提交隔离级别,提供两个保证

1.读数据是只会读到已经提交的数据(没有脏读)
2.写数据时,只会覆盖已经提交的写(没有脏写)

脏读

如果一个事务的写还没有提交,另外一个事务就读到了这个未提交的写数据,那么就叫脏读


图1,没有出现脏读

事务2不会读到事务1未提交的数据x=3
解决脏读的意义

1.避免读到部分update,如上一节的图2
2.如果一个事务在回滚中,它之前写的废弃掉的数据不会被其他事务读到

脏写

两个事务同时写一份数据时会发生什么?
由于不知道明确的顺序,常常假定后续的写会覆盖掉前面的写。

那么,如果一个事务的写尚未提交,此时另外一个事务的写操作来覆盖这个数据,则称之为脏写。

读提交往往通过延迟第二个事务的写操作的时间,等到第一个事务进行完了之后再进行,这种方式来避免脏写。
脏写场景如下


图2,Alice和Bob要买同一个东西,脏写导致了最终的买家是Bob,而发票却寄给了Alice。

这里注意一下,上一节的图1不叫脏写,事务2的写在事务1提交了之后才执行。对应的问题叫做Lost Updates,下节会讲

实现读提交

阻止脏写

常见的阻止脏写的方式是用行级锁

事务要修改一个obj时,现获取锁,如果获取成功,那么一直占有锁知道事务提交或者回滚
一次只能有一个事务获取某个obj的锁,此时其他事务都得等到这个锁释放了才能重新去获取

阻止脏读

当然也可以用读锁,但是实际表现不佳。因为耗时长的事务写操作会导致相关的只读事务等待很长时间。

很多db通过类似图1的方式,即对于事务所有写的记录,db记录旧值和新值。
此时其他事务来进行读操作时,返回旧值即可。
仅当新值提交了之后才会读到新值。

3.快照隔离和可重复读

即使在上面读提交的隔离级别下,还是会有并发的问题,如下


图3

图的意思如下:一开始两个账户各500,有个Transfer从其中一个账户转走100到另外一个账户。但是Alice读第一个账户为500,读第二个账户为400.出现了数据不一致的情况。

这个现象称为不可重复读
如果Alice重新去读取两个account,就会发现一个600(之前是500)一个400,达到数据一致(一共1000)
在读提交的隔离级别下,不可重复读是允许的
上面的例子中,Alice只要重新check一遍就好。但是有些场景不允许这种临时的不一致,如
1.备份
2.数据分析以及一致性检测

常常通过快照隔离完成,快照隔离就是

每个事务从一个一致性快照中读取数据,也就是事务开始时,只会看到所有已经提交的数据。即使有些数据会被其他事务修改,当前书屋也只会看到特定时间点的旧数据

快照就是耗时长的只读统计类事务如备份以及数据分析的福音。

实现快照隔离

原则:读事务与写事务互不影响。
这使得db能够在一致性快照基础上处理读事务的同时,处理写请求。而不需要读写两个事务之间有锁。

为了实现快照隔离,db需要类似图1中的机制,保留一个obj的新旧版本。来保证不同的事务能够看到数据不同的状态。这个技术称为MVCC(multi version concurrency control)

和读提交的区别:
1.几个版本
读提交中,obj只用两个版本即可,已提交版本和覆盖但未提交的版本(因为有锁,不用多个版本)
快照隔离中,用MVCC,是multi version的(因为没有锁,可能多个事务同时写)

2.几个快照
读提交中,一个事务每次读都是读不同的快照(参照图3理解)
快照隔离中,一个事务中所有读都是读同一个快照

MVCC实现中,每个事务都要有一个唯一的自增id(txid),实现如下图


MVCC实现快照隔离

注意

上图每个update行为转化成了一个delete以及一个create.
每条记录有created_by以及deleted_by字段.
当没有事务访问删除的记录时,后台会有gc进程回收deleted_by不为空的记录

MVCC中每个obj可能有多个版本,那么一个事务进来读取数据的时候,到底是读取哪个版本呢?
引出下面这个话题

一致性快照的可见性原则

事务id(txid)来决定哪些obj可见

1.事务开始前列出当前进行的(未提交以及丢弃)的事务id,这些id的提交是不可见的
2.丢弃的事务,忽略
3.后续的事务,忽略
4.所有其他的写都会被读到(就相当于之前已经有的记录)

换一种方式理解,一个object对于一个事务可见,当:

1.读事务开始时,创建该obj的事务已经提交
2.该obj没有被删除,或者说删除的事务在读事务开始执行时还未提交

索引以及快照隔离

如何让索引也能在MVCC中工作呢?
一种方法是索引指向数据的所有版本,由可见性去判断正确的版本。
当deleted_by不为空的记录被gc进程处理后,index也会相应的更新。

其他的一些方式是B树上用一些变种(append-only/copy-on-write等),这里不深入展开

快照隔离的命名困惑

快照隔离在不同的db中叫法不一样,这是因为SQL标准里面,并没有“快照隔离”这个概念

4.Lost update

图1的情况中,两个事务同时写一个数据时,出现了类似丢失更新的问题
这个被称为Lost update,参照refer中给的一点定义,如下

当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题

一般解决方式如下,不展开:

原子写操作
显式用锁(for update语句)
CAS操作

不过在分布式系统中,并发的写冲突可能往往靠应用程序自己解决,这个请看前面的相关章节.

5.幻读

除了上面的竞争外,还有其他并发写可能会出现的竞争,如下
医院在任何时候必须至少有一位医生在值班。医生可以调整他们的轮班,前提是至少有一个同事在医院值班。Alice和Bob是两位今天值班的医生。两人都想调整轮班,不幸的是,他们碰巧点击按钮大约在同一时间取消轮班。接下来发生的情况如图所示:


幻读

这个问题既不是脏写又不是lost updates,因为两个事物在update两个不同的obj(Alice和Bob各自的值班表)。这个问题不明显,但毫无疑问是竞争,因为如果串行化执行就不会有这个问题了。

参照之前解决lost updates的方法,解决幻读则需要

1.单obj的原子操作不管用了,需要多obj的原子操作
2.需要串行化的隔离级别(后续再讲)
3.有些db可以配置约束,自行选择触发器或者视图相关
4.select for update锁住多行

幻读的发生,都会有特定的形式,这里不用书中给的定义,用refer中的博客提到的感觉说的更好

幻读发生在正在执行的事务 T1 有断言的读 (select where) 时,另外一个事务 T2 执行了和断言集合有交集的插入操作。

6.总结

这一节讲了几种异常

脏读,脏写
Lost updates
幻读

对应几种解决的隔离级别

未提交读
提交读
可重复读

这一节没有讲串行化,篇幅原因,下一节再讲
几个隔离级别的翻译是参照网络上的定义

参照refer中 https://zhuanlan.zhihu.com/p/29166694
对应关系如下

各具体数据库并不一定完全实现了上述 4 个隔离级别

7.思考

读提交和可重复读的区别
实现快照隔离中 讲解过

MVCC多个版本中,一个事务读取时该选择哪个版本

参照上述 可见性原则

Lost Update
按照refer中,这个似乎用锁避免并发写就可以(http://blog.csdn.net/bluishglc/article/details/5626009),也就是未提交读的隔离级别就能解决的。
因此感觉文章顺序有点问题,这里应该放在最前面,而且注意 Lost update不是隔离级别,只是一个并发考虑的问题

Lost update与脏写的区别
能保证不出现脏写的隔离级别,不一定保证不会出现Lost update
Lost update的情形中,两个事务都没有看到对方未commit的数据(脏写是看得到的)

个人感受
不同的情形可能都需要深挖,书籍也有没有完全的深入讲解,点到即止。
自己知道这些东西就好

8.名词

隔离标准
读提交
脏读,脏写
(不)可重复读
快照隔离
MVCC(multi version concurrency control)
lost updates
幻读

9.refer

https://www.jianshu.com/p/a84e4f41a2aa

lost update相关
http://blog.csdn.net/bluishglc/article/details/5626009
https://zhuanlan.zhihu.com/p/29166694

幻读
这篇博客写的太厉害了 https://ggaaooppeenngg.github.io/zh-CN/2017/04/16/SQL%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB/

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

推荐阅读更多精彩内容