《Designing Data-Intensive Applications》第7章 读书笔记(3):串行化隔离级别

1.说明

前面讲了几个隔离级别,有这样的问题

1.隔离级别很难理解,并且各db实现方法不一致
2.很难界定自己的应用代码应该在哪个隔离级别下才是安全的
3.并发竞争很难发现以及复现

这一节就讲解串行化,顾名思义,就是结果和串行化执行一样。
往往通过下面三种方式实现

1.真的是串行执行
2.两阶段锁 2PL(2 phase locking)
3.序列化快照隔离 SSI(Serializable Snapshot Isolation)

下面讲解这三种方式

2.真.串行化

最简单的就是真的串行化,优劣如下

有时比支持并发的系统表现更好,以为不用管理锁
但是吞吐量会有限制

由于需要最大程度利用单线程,事务的组织结构会略有变化,用到下面的存储过程

存储过程

比如一个定机票的场景,涉及多步操作:1.查找路线价格和坐位 2.选坐位 3.支付以及订单
这些操作要在一个事务里面完成,但是如果每一步都让用户选择,会浪费资源(线程资源限制,在等待),且影响了其他事务的执行(因为真串行化一次一个)

为了避免网络IO,延迟以及希望sql能够事先编译好,db提出了存储过程
存储过程这里就不讲了,参照refer

存储过程的优缺点:

优点:
1.使得真串行化变得可行,提高了吞吐,减少了并发
缺点:
1.不同db存储过程语言不一样,并且发展慢
2.db跑的代码难以管理,不像自己的应用代码
3.因为db比业务sever更底层,所以如果存储过程写的不好,影响更大

真串行化小结

虽然真串行化可行,但是有如下限制

1.要求每个事务短而且快
2.要求数据集全部载入内存中才行
3.单CPU的话,写的吞吐量必须低
分区的话,写事务的跨区操作会有限制

3. 2阶段锁(2PL)

近30年,2PL是广泛应用于串行化中的方法(备注:2PL不是2PC即2阶段提交)

两阶段锁:

读锁会block住写锁
写锁会block住其他写操作和读锁

注意这个和上一节的快照隔离是不一样的,体现在原则上,区别如下

快照隔离要求写不影响读,读不影响写
2PL中,读写互相影响

实现2PL

主要用锁,共享锁和独占锁,机制如下:

1.读事务去拿共享锁
2.写操作必须拿独占锁
3.如果一个事务先读后写,则需要升级共享锁为独占锁
4.事务拿到锁之后,必须一直持有直到提交或者放弃。

"两阶段锁"中,"两阶段"的意思就是:第一个阶段是上锁,第二个阶段是释放锁

2PL的表现

性能是主要瓶颈,部分原因是锁多了,并发量降低了
由于串行化,前面的事务占有锁可能会使得后面的事务block住
因此,2PL有不确定性的延迟
并且死锁的触发会比读提交的隔离级别更频繁

文中提到了Predicate locks以及Index-range locks,有点晦涩这里就不展开了

3. Serializable Snapshot Isolation (SSI)

前面的两种方法中,

2PL性能不好
真.串行化 拓张兴不好

这里提出SSI,相较于快照隔离,提供了完全的串行化,只有轻微的性能损失

和快照隔离不同的是
快照隔离用悲观锁的思想,假定所有事务都有可能出错,所以要等到安全再执行。
SSI用乐观锁的思想,事务都会continue,希望every thing will be ok,最终提交时再检测是否需要丢弃
这在竞争少的情况下表现优良,在竞争多的情况下表现相对较差(因为频繁丢弃事务)

无论如何,只要足够稀疏,事务竞争不会太高,乐观思想比悲观思想表现的更好

3.1 在过时的假定下的决策

结合上一节讲的幻读的例子
医院值班时,至少要一个医生,此时两个值班医生Alice和Bob都在值班,但是都想请假,会执行事务逻辑如下

1.当前值班医生一共有几个
2.如果大于一个(因为算上自己),那么自己就可以请假了

这样两个事务同时执行,最终可能导致医院没有值班医生。
这种场景,主要体现在一个事务后续的写依赖于前面查询得到的决策
安全起见,db假设前置查询的结果如果有变化,后续的写操作也会变得invalid.
因此,在快照隔离的基础上,结合乐观锁的思想,SSI提供出冲突检测的算法,来判断一个事务提交时,是否需要丢弃

主要分为两个case

1.事务read时,由于MVCC原因忽略了某些写操作,等提交时,之前忽略的写操作已经commit了
2.当前事务的读操作的结果,被后续其他事务的写操作影响了

这里可能并不是很好理解,下面会有demo说明,结合图片理解

3.2 检测旧的MVCC读

简单来说,就是一个事务读取时,忽略掉的MVCC的旧版本的数据,在该事务提交前提交了,最终之前忽略掉的写却生效了
还是按照医生值班的例子

《Designing Data-Intensive Applications》第7章 读书笔记(3):串行化隔离级别_第1张图片
图1,检测事务读到了MVCC过时的数据

方框处的记录在事务42提交时生效,但是事务43读取数据时,这个方框记录由于MVCC原因,被忽视掉了

解决方式如下

db记录一个事务由于MVCC可见性原因而忽略掉其他事务的写操作
当该事务准备提交时,看忽略的写操作是否已经commit
若是,则该事务丢弃

为什么要等到commit时采取检测呢,这样做避免了不必要的丢弃:

1.如果事务43是read only的呢,并不用丢弃。谁能知道后面它会基于此进行写操作呢
2.说不准事务42也会被丢弃,因此不会影响事务43呢

3.3 检测写操作影响了之前的读

和上面的问题类似,直接画图

《Designing Data-Intensive Applications》第7章 读书笔记(3):串行化隔离级别_第2张图片
图2,事务42的写操作影响了事务43读操作的结果

上面的查询中,有index-range锁在entry 1234上,后面事务42写的时候,获取了一个类似于写锁的东西,只不过他并不是阻塞其他事务的提交,而是负责通知:通知所有在entry 1234上的事务,告诉他们读的数据已经过时了。因此事务43最后会被丢弃.

3.4 SSI的表现

许多工程细节影响算法在实践中的工作效果。跟踪事务的读写的粒度会产生影响。
如果数据库非常详细地跟踪每一个事务的活动,那么它就可以精确地判断哪些事务需要中止,但是这些开销会变得很大。
而不太详细的跟踪事务会更快速,但可能导致更多的事务被中止。

相比与两阶段锁,可串行化隔离快照是大有好处的:一个事务不需要阻塞等待另一个事务持有的锁。

4.思考

过时假定下的决策

这个部分很重要,结合上一节幻读的例子,理解这个思想

SSI的构成

在快照隔离的基础上,结合乐观锁的思想,检测两种case来判断事务的提交是否有冲突进而被丢弃。

SSI检测冲突的两种方法的区别

结合上面图1,图2理解
图1是事务42 select,update之后,事务43才执行,因此有MVCC出现
图2,是事务42,43都select之后,42再进行update,所以没有MVCC而是有事务42直接的写的结果,影响了事务43之前的select结果

5.名词

2PL
Predicate locks
Index-range locks
SSI

6.refer

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

存储过程
http://www.cnblogs.com/hoojo/archive/2011/07/19/2110862.html
https://www.w3cschool.cn/sql/sql-storage.html

你可能感兴趣的:(《Designing Data-Intensive Applications》第7章 读书笔记(3):串行化隔离级别)