01.什么是时间戳
“时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。通俗的讲, 时间戳是一份能够表示一份数据在一个特定时间点已经存在的完整的可验证的数据。
02.用时间戳实现调度
定义
数据库给予一个事务一个时间戳(具有唯一性)
时间戳的标志是TS(),TS(Ti)表示 事务 Ti的时间戳
数据库两种定义时间戳的方法:
无论如何定义时间戳,都会按照时间来进行调度的串行化
时间戳实现调度中每个数据项有两个重要的时间戳:
都是成功执行方法的事务的最大的时间戳:
时间戳排序协定的具体过程:
这里比较的都是用的事务最开始被系统设定的时间戳,成功的后的值也是执行方法的事务的时间戳的值,不是读写方法过程的那一刻的系统时间戳
1.事务读方法用数据项的写的时间戳:
2.事务写方法用数据项的读和写的时间戳:
事务如果回滚,属于事务的时间戳会重置:
例子:
时间戳排序的缺点:
可以保证调度串行化,但是由于长事务的IO时间长,会导致数据项的两个时间戳都大于这个IO事务的时间戳,会反复回滚。
改进方法:
1.保证事务写入数据项A时候,数据项A的读时间戳绝对不会大于现在这个时刻的时间戳,万一事务就是刚刚创建的呢
2
3.这个是可恢复调度(已经读了)
可恢复调度:先写数据的事务,先提交
无级联调度:在事务之间,读操作之前提交。
托马斯写作规则:改进型时间戳协议
用原来的时间戳排序来调度T27和T28
T27的写函数不能写入,T27回滚
解释:
改进的方向:
Thomas写规则:
区别:
托马斯写作规则的主要改革是无视过时的写作行为。
在调度4中,T27的read(Q)和T28的write(Q) 指令冲突了,这个调度不是冲突串行化的。
基于有效性检查的协议:
具体内容:
每一个事务都分成三个阶段:读阶段,有效检查阶段,写阶段
重点是第二步.要进行有效性检测的需要的参数
三个阶段分别有自己的标志时间戳:
把Validation(Ti)作为事务Ti的时间戳,参加有效性检测,参与时间戳排序,看是不是可以串行化
有效性检测的具体要求:完成以下两个条件之一,就算是通过,可以得到的结论 TS(Tk)<TS(Ti) ,事务k在事务i之前完成
1.Finsh(Tk) 写完成 < Start(Ti) 读开始 -------- 事务k在事务i之前完成,调度可以串行化
Finsh(Tk) 写完成 < Validation(Ti) 验证阶段开始,这个只能证明两个写阶段时间绝对不一样,一个已经写完,一个还没有开始还在验证,在事务Tk的写阶段,这个时间有可能Ti还在读阶段没有结束啊
并且 两个事务 Tk的写 和 Ti的读 的数据项集合不相交,这表明事务的读写指令不冲突,可冲突串行化
例子:
图中的validate就是检测阶段: display(A+B)< T26的 validate 并且 T25的写阶段 和 T26的读阶段 并没有冲突,这个是满足第二条的,只是这个时候Tk=T25,Ti=T26
可以自动预防级联回滚,(级联回滚的关键在于,如果存在依赖的情况下,依赖的一方先行提交的话,被依赖的事务如果在后续的运行中发生错误的话,被依赖的事务可以回滚,但是已经提交的事务不可以回滚,因为已经提交了,有效性检测协议的第二步有效检测,第一条条件是 写阶段完成时间<读阶段开始时间 这个就已经自动预防级联回滚了。第二条,写阶段的数据项集合与读阶段的数据项的集合并没有交集,没有依赖,这说明也已经自动预防级联回滚了),
多版本:
这里系统给事务一个时间戳
每一个数据项都有一组版本
一个版本有三个元素:C,W,R
write方法创建一个数据项的新版本
content 是 写入数据的值
W,R初始化的时候,都是事务本身的时间戳
成功读取这个数据项版本的条件是R<TS(Ti), 即读取这个版本的事务要比创建这个版本的事务要新一些 ,事务Ti就可以成功读取这个版本的数据,R更新成为TS(Ti),要更加新一些。
具体排序事务过程:
这里的写时间戳 W 是 创建这个数据项版本的事务的时间戳
第二条,TS(Ti)<R ,说明要访问这个版本的事务是比较先创建的
TS(Ti)=W,说明是这个数据项版本的创建者,可以重新写里面的数据
TS(Ti)>R,说明新的事务访问这个版本
数据项版本的删除
优点:
不足
改进
更新事务
这个时候,版本的时间戳不是创建这个版本的事务的时间戳,而是一种计数器
更新事务的读写操作:读+在数据项加共享锁+读取最新版本的内容
写 +在数据项加排他锁+创建一个新的版本+新版本的时间戳是无穷大
数据项的所有版本的值都是ts-counter+1,再将数据项的本身的ts-counter+1,这就会让数据项的ts-counter(类似指针)指向Ti创建的版本,也就是最新版
只读事务
数据项版本的删除
快照隔离:
快照的定义
快照对于只读事务是理想的
快照对更新事务的写方法需要检查,因为可以存在两个事务同时写一个数据项的冲突问题
当事务提交的时候
更新事务的写方法的检查
问题是更新丢失
先提交获胜,直接检查其他并发的事务有没有提交,没有,就自己提交
先更新获胜(还没有提交),因为可能在更新过程中正在更新事务出现错误中止:
快照串行的问题:
快照不能保证串行化调度的实现
例子:两个并发的事务,都读了彼此下一步要写的数据项,这样就会在优先图中形成环,这样就会出现不能串行调度
注解:
优先图的形成条件:有环的话,调度就不能串行化
写偏斜:由于更新的是不同的数据项,所以没有快照更新检测的约束。
此外:完整性约束不能在快照上进行检测,只能在数据库
补充:
这里的完整性约束:不是数据库数据的完整性约束
我们先举个例子。如图所示,假设完整性约束要求事务执行前后总有数据A和数据B的和为10。那图中的事务致性后,该完整性约束依然满足。因此,这个事务就满足一致性。
这是我们发现,“完整性约束”是一个外在的业务约束。这就意味着,同样的操作,对应的外在的业务约束不同,则该操作可能满足事务条件,又可能不满足事务条件。假设完整性约束要求事务执行前后总有数据B减数据A为8,那图中的事务便不成立,因为它执行结束后该完整性约束不在满足,即不满足一致性。
说到这里,大家可能发现了一些不对劲的地方。这可能也是大家最开始困惑的源头。因为,在事务的ACID约束中,一致性与其他三个特性也颇有不同。其实这点确实引发了一些争议。
其他三个特性均是事务内在保证的,而一致性的约束条件是由外部业务逻辑规定的。这就意味者,同样的一个操作,根据外部业务逻辑规定的完整性约束的不同,可能满足事务要求,也可能不满足。
另一个例子:
新增一个只读的事务
此时优先图出现环
调度串行化的目的是保证数据库的一致性,如果不是串行化的调度,但是一致性可以保证,非串行化调度也可以进行
例子:插入操作在快照检测中可以通过(并没有更新相同的元组,也就是相同的数据项),但是同时出现两个一样的主键,会在提交的时候,报错
串行化表示作用和一个串行的调度一个效果,很明显,这个快照插入不会固定结果输出,没有保证一致性,是非串行的:
原因
改进方法:
当同一个查询在不同的时间产生不同的结果集时,事务中就会出现所谓的幻象问题。例如,如果 SELECT 执行了两次,但第二次返回了第一次没有返回的行,则该行是“幻像”行。
举个例子
假设一个事务在 T1 时刻和 T2 时刻分别执行了下面查询语句,途中没有执行其他任何语句:
SELECT * FROM t_test WHERE id > 100;
只要 T1 和 T2 时刻执行产生的结果集是不相同的,那就发生了幻读的问题,比如:
T1 时间执行的结果是有 5 条行记录,而 T2 时间执行的结果是有 6 条行记录,那就发生了幻读的问题。
T1 时间执行的结果是有 5 条行记录,而 T2 时间执行的结果是有 4 条行记录,也是发生了幻读的问题。
for update 会把 读取的数据 当做更新来执行,就会出现 两个快照更新的局面,用上面的两个方法中的一个(先提交获胜, 先更新获胜)
并发控制扩展到插入和删除:
01.删除:
删除和基本四种,读,写,插入,删除都有冲突:这里指的是并发的那一瞬间
删除之后读,找不到相关的数据项,出现错误
删除之后写,找不到相关的数据项,出现错误
删除之后删除,找不到相关的数据项,出现错误
前提条件是关系中没有这个数据项,删除之后新增,删除找不到数据项,出现错误
改进
02.插入
03.读select
例子
T30,T31
预计的冲突:
解决方法
T30的查询过程