Part18-Timestamp Ordering Concurrency Control
OCC and T/O都是Optimistic的。
Basic T/O
通过分配时间戳来预先定义这些事务的提交顺序。DBMS保证如果TS(Ti) < TS(Tj) 那么在serial schedule中,Ti会在Tj之前执行。
Timestamp Allocation
Timestamp是唯一且固定长度是数字,有如下特性
- 时间戳是单调递增的
- 值唯一
可以在事务执行期内任意时间点给事务分配时间戳,不一定刚拿到事务或者事务提交的时候分配。
分配方法
- CPU System Clock,可能存在回调那么时间戳就会混乱。(夏令时冬令时)
- Logical Counter,CPU里面一个专门保存时间的寄存器,单调递增,32/64位,如果用完计时器会往回走就不能用了
- Hybird 混合方案:让时间戳和物理计数器以及逻辑计数器匹配来确定正常工作。
Basic T/O
Txns 读写 without lock,需要添加额外的元数据来进行控制,每个Tuple要加两个TS:
- W-TS(X):最近写X的时间戳
- R-TS(X):最近读取X的时间戳
- 每次操作check timestamps,如果事务想要access未来的值 要aborts 或者 restarts
在事务开始的时候分配时间戳
-
Reads
如果TS(Ti) < W-TS(X),违反了Ti相对于X的时间戳顺序,abort Ti或者重启 with a newer TS。
TS(Ti) ≥ W-TS(x):允许Ti读X,更新R-TS(X) = max(R-TS(X),TS(Ti)) 。做一个X的local copy来保证Ti的可重复读
-
Writes
TS(Ti) < R-TS(X) 或者 TS(Ti) < W-TS(x) , txn abort 或者 restart
如果≥ ,允许Ti对X进行写,然后更新W-TS(X),同样要x local copy 保证可重复读。
-
Thomas Write Rules
读一样,写的话如果TS(Ti) < W-TS(x) 那么忽略这个write 允许事务继续。 但是会本事务内维护这个写后的临时值?
(不使用Thomas Write Rules的话)可以生产conflict serializable schedule。不会产生死锁因为没有事务在等待;假设有长事务,可能会因为有许多短事务更新不断地引起冲突,导致这些连环中止的老事务无效化。问题是T/O协议规定了schedule是不可以恢复的。
Recoverable Schedules
可恢复的调度是一个事务只有当它所依赖数据的对应事务已经都提交的情况下它才提交,才是可恢复的调度。(cascading aborts)
下面的例子,T2读T1的未提交的值,但是T1abort了,T2也没法回滚。对于Basic Timestamp Ordering来说允许这种情况发生。
Basic T/O性能问题:复制数据到txn 自己的workspace的overhead太高了;长事务饿死问题。
OCC
如果假设系统中没有很多锁争用情况,这些事务存活周期短,彼此无冲突,那么对要进行操作的数据副本到自己workspace之后,更新,完成之后验证所做的修改与数据库系统中并发执行的其他事务是一致的,验证阶段之后才进行提交,the write set is installed into the “global” database。
OCC Phases
- 1-Read(work) Phase:跟踪事务读写集,存到自己workspace
- 2-Validation Phase:txn commits,检查是否和其他事务冲突
- 3-Write Phase:验证成功,apply,将私有空间所做的修改落地到主数据库否则abort+restart
OCC在事务初始的时候并不分配时间戳,进入验证阶段才分配时间戳给事务,没分配前时间戳认为是无穷大。需要保证只有serializable才被允许,Ti检查和其他事务之间的RW和WW冲突,all conflicts go one way from older txns to younger txns。
OCC-SERIAL VALIDATION
保证串行化,需要全局视野,能看到系统中所有正在运行的活跃事务,记录运行事务的读写集,记录在事务的private workspace。
整个系统在validation阶段有一个大latch,确保一次只有一个事务执行验证操作
维护global view of all active txns,复制tuple保证了可重复读。
验证阶段拿到时间戳之后,查看系统其他并发事务,确保读写集不会交叉,有一个正确是序列化顺序.
如果说TS(Ti) < TS(Tj),那么下面三个条件之一必须成立???
commit → validation phase,两种验证方式
- Backward Validation:找到所有比T2时间戳小(老)的不同事务,对他们执行验证操作,validation scope 验证作用域。检查提交txn的读/写集是否与任何已经提交的txn的读/写集相交,可能T1更新某个数据,该数据应该能被T2读到,但是没有,因为T1对私有副本做的修改,所以就需要abort T1?应该是T2
- Forward Validation:向未来看,看新事务,找到所有并发执行还没提交的事务,如果有一个T3要用,T2改了的,那么T2验证失败
以forward validation为例:
-
Ti 完成了所有的三个阶段,然后Tj才开始
- 很简单可串行化的
-
Ti在Tj开始写阶段之前完成所有阶段,Ti的写集和Tj的读集不相交,则验证通过,否则验证失败。
-
下面的例子,Ti 写A 和 Tj 读A有交集,需要中止Ti,T2其实是读了一个过时的值。
-
如果说,T2先验证,那么在serial order里面T2 就是在T1之前执行的,T2读完A进入验证阶段,T2没有写集,没有相交,所以就验证通过。
-
-
Ti在Tj完成Read phase之前完成了Ti的Read Phase,需要确保Ti的写集和Tj读集没有冲突,Ti写集和Tj写集没有冲突。
-
Ti先进入验证阶段,可以提交,因为T2是read阶段是在T1 read阶段完成之后才开始的,或者说因为在T1验证阶段的时刻,T1写集和T2读集不冲突,因为T2只读到了B,还没读A
-
然后T2 R(A),进入验证阶段,验证成功因为没有并发事务了已经。 10.
-