原语介绍
时间戳方法假设每个时间戳都是唯一并且准前的表示一个事务的开始的及时时间,在事务系统种没有两个时间戳是相同的,一个大时间戳发生在一个小的时间戳之后。有几种生成时间戳的方法
事务启动时使用系统的时钟作为事务的时间戳
一个线程安全的只增计数器的数值作为事务的时间戳
综合以上两种方法的时间戳生成方法。
从语义上说,在基于时间戳的方法中,认为两个事务是不可能同启动,但是两个事务可以被同时执行,为了避免出现“不表示真实执行事务的序列“出现,时间戳方法使用两个规则。
在事务启动时,给事务一个时间戳,这个时间戳表示事务被执行的顺序,对于同时更改数据元素X的两个事务,早启动的事务意味着此事务比另外一个事务早执行对于数据元素X的更改,然而,一个晚点的事务可能被过早启动,那么此事务必须被终止执行并重新启动。
在数据库中,数据元素具有read_timestamp,表示数据元素最晚的读取时间,比如对于数据元素x最晚读取时间,以RTS(x)表示最晚读取时间;数据元素具有write_timestamp,表示数据元素最晚的写时间,对于数据元素x的最晚读写时间,以WTS(x)表示。
当一个事务需要读取一个数据元素时
如果WTS(x) > TS(t), 说明存在另外一个事务t'比事务t启动的更晚,但已经更改了数据元素x, 这里,TS(t)表示事务t的时间戳,终止事务t,再次启动它,分配一个当前最新的时间戳给这个事务,这个过程称为事务重启。
如果TS(t) > WTS(x),说明事务t比另外一个已更改数据元素x的事务t'晚启动,这样事务t读取数据元素x是安全的,如果RTS(x) < TS(t),更新RTS(x)的值为TS(t)。
当一个事务需要更改一个数据元素
如果RTS(x) > TS(t),意味着一个晚些的事务t'已经读取了数据元素x,此时,不能更改数据元素x使事务t'读取x的拷贝x'失效,因而需要终止事务t,并重新启动它。
如果WTS(x) > TS(t),意味着一个晚启动的事务t'已经更改了数据元素x,此时,可以忽略事务t对于数据元素x的更改,并继续执行它,这称为Thomas法则。
否则,更改数据元素x,更新WTS(x)为TS(t),继续执行事务t。
示例
有三个事务T19、T20、T21分别有时间戳TS(t19), TS(t20)和TS(t21), 并且TS(t19) < TS(t20) < TS(t21)。三个事务执行的操作按照时间戳方法,其详细步骤如下:
T19 T20 T21
----+-------------------+-------------------+-------------------+
t1 | begin_trans | | |
t2 | read(balx) | | |
t3 | balx = balx + 10 | | |
t4 | write(balx) | begin_trans | |
t5 | begin_trans | read(baly) | |
t6 | | baly = baly + 20 | begin_trans |
t7 | | | read(baly) |
t8 | | write(baly) ^1 | |
t9 | | | baly = baly + 30 |
t10 | | | write(baly) |
t11 | | | balz = 100 |
t12 | | | write(balz) |
t13 | balz = 50 ^2 | | commit |
t14 | write(balz) | begin_trans | |
t15 | commit | read(baly) | |
t16 | | baly = baly + 20 | |
t17 | | write(baly) | |
t18 | | commit | |
在时间戳t8的标记^1处,因为晚启动的事务T21已经读取了数据元素baly的值,所以事务T20必须终止,并等到T21提交后重新启动;在时间戳T13的 标记^2处,事务T19准备更新数据元素balz,但发现balz已经被更晚启动的T21更新过,根据Thomas法则,事务T19忽略对数据元素balz的 更新。
终止
从事务的原子性来说,事务中的操作要么全部成功,要么全部失败。终止一个已经执行的事务,还必须要把事务已更改的数据元素恢复到事务未开始之前的状态。幸好有undo日志,记录了事务更改数据元素之前的旧值。根据时间戳规则,当事务t需要终止时,事务处于待终止状态。
根据undo日志规则,事务更改数据元素之前必须保存其旧值,事务t内所有对于数据元素的更改操作 A = {A1, A2,... Ak},其旧值OLDVAL = {ov1, ov2, ... , ovn},其元素是连续的,并且 1 < n < k。恢复到事务未开始之前的状态,其操作是从集合OLDVAL的最后一个元素开始,往前恢复旧值。
尝试恢复旧值的过程中,遵行时间戳方法的两个规则。有可能OLDVAL中的旧值不一定需要真正的被反应到其所在的数据元素之上,也有可能会再次尝试恢复,直到ODLVAL集合中的第一个元素vo1被用于恢复或者被忽略。此时,事务达到终止状态。在事务尝试终止的过程中,事务的时间戳一直保持不变。
依赖和等待
除read uncommit隔离级别外,其他的隔离级别规定一个事务只能读取其他事务已提交的数据。在时间戳方法中,也就是一个读事务t,需要等待此事务之前的事务t'提交之后,才能继续执行,对于事务t‘,有依赖其执行成功后才能执行的事务t。
事务维护一个等待的事务集合WAITFOR和一个依赖的事务集合DEP,事务被创建时,WAITFOR和DEP为空,当事务t是一个读事务,在读数据元素x时,有TS(t) > WTS(x),表明有另外一个事务t'在事务t之前启动并更改了数据元素x,此时,事务t'并不一定是已提交状态,因此,事务t需要在活动的事务列表中查找事务t',并把这个事务t'加入到事务t的WAITFOR之中,把事务t加入到事务t'的DEP集合中。如果没有查找到,则事务t可以读取x的值。
如果事务t'终止,则t也必须终止。事务t'提交,需要给DEP集合中的所有等待事务通知t'已经提交,事务t删除WAITFOR集合中的t'。事务t的WAITFOR集合为空时,事务t从等待状态变成执行状态。
锁
尽管时间戳是非阻塞式的,在事务并发执行的过程中,大多数对象没有加锁,但是数据元素对象的时间戳还是需要一个极短的锁时间,WAITFOR集合的增加、删除操作也是需要加锁的。
参考博文:
Redo日志: http://my.oschina.net/yishanhu/blog/465635
Undo日志: http://my.oschina.net/yishanhu/blog/465947