2PL是悲观锁,Pessimistic,这章讲乐观锁,Optimistic,单机的,非分布式的算法
Timestamp Ordering,以时间为序,这个是很是天然的想法,按每一个transaction的时间来排谁先执行数据库
这里会有几个问题,Timestamp是什么,由谁来打,何时打上Timestamp缓存
首先每一个T须要有一个unique的timestamp,这个在单机很容易实现;
其次,Timestamp必须是单调递增的
最后,不一样的schema会选择在不一样的时间给txn打上timestamp,多是txn刚到的时候,也多是txn执行完的时候安全
Timestamp能够有多种形式,系统时间,逻辑counter,或者hybrid并发
Basic Timestamp Ordering Protocol分布式
设计比较直觉,性能
首先,给每一个object加上,读时间戳,写时间戳,表示最后一次读写该对象的时间优化
读的时候,拿当前事务ts和写ts比较,若是写ts比较新,那么读须要abort,由于,你不能读一个将来的值;
写的时候,要同时比较该对象的读,写ts,比较写是由于你不能用过去的值覆盖将来的值,比较读,是由于若是有将来的txn读过这个值,你就不能再更新spa
同时,这里不管读写,都会把当前的value,copy到local进行缓存,这是避免txn频繁冲突,由于对于一个txn数据应该能够重复读的,因此若是不缓存,那么若是这个值被别的txn改了,会很容易致使txn abort设计
例子,
T1在更新A的时候,ts已经小于W-TS,因此不能更新,须要abort
Thomas write rule,一种下降abort几率的方法
思路若是txn在write的时候发现,W-TS大,即将来的时间,那么直接跳过这个write,由于这个write反正都是被覆盖的,因此不关键;
用Basic T/O产生的shedule是不可recoverable的
什么叫recoverable,当你commit的时候,你所依赖的全部txn已经完成commit;
这样才是可恢复的,否则你commit完后,数据库crash了,那么你以前看到的或依赖的txn尚未commit就丢失了,但你的结果已经完成commit,就产生不一致
Basic T/O的问题,
1. 不可recoverable
2. overhead比较重,须要每次读写都更新ts,并且还须要把数据copy到local
3. 长txn会starve,比较难成功,由于很容易被冲突,abort
乐观锁,基于的假设,冲突极少发生,不然乐观锁的成本反而更加高
因此基于这样的假设,那么算法能够进一步优化,OCC算法
分3个阶段,
1. 每一个txn都建立一个独立的workspace,不管读写,都把数据copy到本身的workspace里面进行操做
2. validate,这个txn是否和其余txn冲突
3. 若是不冲突,把变动合并到global数据库
能够看到,其中比较难的是第二步,validation
validation就是判断当前txn和全部其余的txn是否有WR或WW冲突
首先假设同时只有一个txn进行validation,即serial validation
而后txn的ts是在validation阶段的开始被assign的,这个很关键
如何check当前txn和其余全部的txn之间是否有冲突?
若是Ti 先于 Tj,那么咱们要知足如下3个条件中的一个
1. Ti在Tj开始以前,完成全部3个阶段,就是串行执行,这个确定没有冲突
2. Ti在Tj开始Write phase前完成,而且Ti没有写任何会被Tj读到的对象
例子,
T1写了A,而在T2中会读到A,这样就不知足上面的条件2
这种状况是安全的,由于这个时候T2也已经结束了,而且T2只是读了A当并无写任何数据
3. Ti的Read phase比Tj的Read phase早结束,而且Ti没有写任何会被Tj读或写到的对象
例子,
这个例子T1在validation的时候,T2没有读或写A,因此安全的,把T1的结果提交到Database
而后这个时候T2读A,是不会有问题的
OCC算法的性能问题,
也要把数据copy到本地,比较高的overhead
Validation和Write会成为瓶颈,由于这里须要串行
Abort的代价更高,由于这里txn已经作完了,才会validation判断是否要abort
OCC算法,还有明显的性能问题,当txn不少的时候,每一个txn提交,都须要去判断是否和其余的txn冲突,就算没有冲突,可是每次比较的代价也是很是高的
因此提出Partition-based T/O,若是数据水平划分红不少partition,那么在每一个partition上的txn就会变少,比较txn之间的冲突的效率就会提高
Partitioned T/O的性能问题是,
若是每一个txn都只访问一个partition,那么性能会比较好
幻读
以前的txn都是读写,可是没有insert或delete
因此对于下面的状况,2PL是无论用的,由于锁机制只能锁已经存在的tuple,因此这个问题是Phantom,幻读
解决这个问题的方法,
predict locking,知足这个predict的records都lock,这个很难实现
对于predict locking一种可能的实现方式是,index locking,锁包含这个predict的index page
若是没有合适的index,只能锁表上的每一页或直接锁table
经过重复查询来判断是否有幻读,很低效的方式,通常数据库都不会采用
这里最后再讲一个概念,隔离级别
最强的就是以前一直在讲的,serializable,虽然有最强的一致性,但也大大牺牲了数据库的并发性
可是有的应用和场景,其实不须要那么强的一致性,因此能够牺牲一些一致性来换取必定的并发性
不一样的隔离级别会产生哪些不一致状况在右图能够看出
那么如何实现这些隔离级别?
列出在SQL标准中,若是设置隔离级别
这里列存各个数据库引擎,默认的和支持的隔离级别