Timestamp Ordering(T/O)
时间戳排序算法是一种乐观的并发控制策略,它为每个事务分配一个唯一的时间戳,表示该事务的逻辑开始时间。时间戳越小,表示事务越早开始。时间戳排序算法要求事务按照时间戳的顺序执行,以保证可串行化调度。
如果TS(Ti) < TS(Tj),则DBMS必须确保执行计划等同于串行计划,其中Ti出现在Tj之前
每个txn的 Ti被分配一个单调增加的唯一固定时间戳。
- 设TS(Ti)是分配给txn Ti的时间戳
- 不同的方案在txn期间在不同的时间分配时间戳。
多种实施策略
- System/Wall Clock.
- Logical Counter.
- Hybrid
Basic T/O
Txns无锁读写对象。
每个对象X都标有成功读/写的最后一个txn的时间戳:
- W-TS(X) – Write timestamp on X
- R-TS(X)–X上的读取时间戳
检查每个操作的时间戳,如果txn尝试访问未来的对象,它将中止并重新启动。
Basic T/O Reads
如果TS(Ti)<W-TS(X),则这违反了Ti相对于X写入器的时间戳顺序。
那么中止Ti并使用新的TS重新启动它。
或者
- 允许Ti读取X
- 将R-TS(X)更新为最大值(R-TS(X)、TS(Ti))
- 制作X的本地副本,以确保Ti的可重复读取
Basic T/O Writes
如果TS(Ti) < R-TS(X)或TS(Ti) < W-TS(X)那么中止并重启Ti
或者
- 允许Ti写入X并更新W-TS(X)
- 同时制作X的本地副本,以确保可重复读取
Thomas Write Rule
Thomas写规则是时间戳排序算法的一个改进,它可以提高并发性和效率,同时保证可串行化调度的正确性。Thomas写规则可以概括为忽略过时的写操作。它规定,如果一个更晚开始的事务已经写入了一个数据项的值,那么一个更早开始的事务就不需要执行它的写操作,因为它的写入值最终会被覆盖。
Thomas写规则适用于事务在开始时就被分配了一个预定义的逻辑顺序的情况。例如,一个事务在创建时就被分配了一个单调递增的时间戳。Thomas写规则防止了事务执行顺序的变化导致输出结果不同:输出结果总是与预定义的逻辑顺序一致。
例如,考虑一个数据库有三个变量(A, B, C),和两个原子操作 C := A (T1) 和 C := B (T2)。每个事务涉及一个读操作(A 或 B),和一个写操作(C)。这两个事务之间唯一的冲突是对 C 的写操作。如果(当事务被创建时)T1 被分配了一个先于 T2 的时间戳(即根据逻辑顺序,T1 先于 T2),那么只有 T2 的写操作应该被可见。如果,然而,T1 的写操作在 T2 的写操作之后执行,那么我们需要一种方法来检测并丢弃这个写操作。一种实用的方法是给每个值标记一个写时间戳(WTS),表示最后修改该值的事务的时间戳。执行 Thomas 写规则只需要检查对象的写时间戳是否大于执行写操作的事务的时间戳。在上面的例子中,如果我们用 TS(T) 表示事务 T 的时间戳,用 WTS(O) 表示对象 O 的写时间戳,那么 T2 的写操作会将 WTS 设置为 TS(T2)。
从上面的条件可以看出,如果 TS(Ti) < WTS(X),那么违反了 Ti 相对于 X 写入器的时间戳顺序。因为这意味着 Ti 比 X 的上一个写入器更早开始,但是却要在它之后写入 X。这样就破坏了可串行化调度的要求,因为按照时间戳顺序,Ti 应该在 X 的上一个写入器之前写入 X
如果不使用Thomas Write Rule,则生成可冲突序列化的计划。
Basic T/O Performance Issues
因为每次读取都需要txn写入数据库,所以将数据复制到txn的工作区和更新时间戳的开销很高。
因为txn从较新的txn读取内容的可能性增加,所以长时间运行的txns可能会挨饿。
OCC Phase
OCC是乐观并发控制(Optimistic Concurrency Control)的缩写,它是一种并发控制策略,它假设事务之间的冲突很少发生,因此在事务执行过程中不加锁,而是在提交时检查是否有冲突,并根据情况决定是否回滚或重试。OCC可以提高事务的并发性和效率,但也可能导致更多的回滚和重试。
OCC分为三个阶段:
- 读写阶段(Read Phase):在这个阶段,事务对数据进行读取和修改,但不实际写入数据库,而是将读取的结果和修改的内容保存在本地缓存中。同时,事务也记录下它所读取和修改的数据项的读时间戳(RTS)和写时间戳(WTS),表示数据项在数据库中的最新版本。
- 验证阶段(Validation Phase):在这个阶段,事务准备提交时,需要向数据库发送验证请求,检查自己是否与其他事务产生了冲突。验证请求包含了事务的开始时间戳(STS)和结束时间戳(ETS),以及事务所涉及的数据项的RTS和WTS。数据库根据不同的验证规则来判断事务是否可以提交。一般有以下几种验证规则:
-
基本规则(Basic Rule):如果一个数据项的WTS大于事务的STS,说明该数据项已经被其他事务修改过了,那么当前事务就不能提交。
-
Thomas规则(Thomas Rule):如果一个数据项的WTS大于事务的STS,但小于或等于ETS,说明该数据项已经被其他事务修改过了,但是当前事务没有读取或修改该数据项,那么当前事务可以忽略该数据项的冲突,继续提交。
-
快照规则(Snapshot Rule):如果一个数据项的RTS大于或等于事务的STS,并且WTS小于或等于ETS,说明该数据项在事务执行期间没有被其他事务修改过,那么当前事务可以提交。
验证的两种方式,分别是:
-
backward validation(向后验证):这种方式在事务提交时,检查它的读集和写集是否与已经提交的事务的写集有交集,如果有则说明有冲突,否则说明没有冲突。这种方式的优点是只需要检查已经提交的事务,而不需要检查还未提交的事务;缺点是可能会导致较高的回滚率
-
forward validation(向前验证):这种方式在事务提交时,检查它的读集和写集是否与还未提交的事务的读集有交集,如果有则说明有冲突,否则说明没有冲突。这种方式的优点是可以减少回滚率,因为可以选择回滚哪个事务;缺点是需要检查所有活跃的事务,而不仅仅是已经提交的事务
- 写入阶段(Write Phase):在这个阶段,如果事务通过了验证阶段,那么它就可以将本地缓存中的修改内容写入数据库,并更新相应数据项的WTS为自己的ETS。如果事务没有通过验证阶段,那么它就需要回滚或重试。
OCC Phase Issues
- 本地拷贝数据开销大
- 验证/写入阶段瓶颈。
- 中止比2PL中更浪费,因为它们只在txn已经执行之后发生。
Phantom Problem
幻读问题是指在一个事务内,同一查询语句在不同时间执行,得到不同的结果集时,就会发生所谓的幻读问题。例如,如果一个事务先查询了某个表中的所有记录,然后另一个事务在该表中插入了一些新的记录,并提交了事务,那么第一个事务再次查询时,就会发现有一些之前不存在的“幻影”记录。幻读问题会破坏事务的隔离性和一致性,因此需要采用一些方法来避免或解决。
- Re-Execute Scan(重新执行扫描):这种方法是在每次查询时,都重新扫描整个表或索引,并检查是否有新插入或删除的记录。如果发现有新的记录,就回滚当前事务,并重新开始。这种方法的优点是简单易实现,缺点是效率低下,因为每次查询都要扫描整个表或索引1。
- Predicate Locking(谓词加锁):这种方法是根据查询语句中的条件(谓词),对满足该条件的所有记录或范围加锁,防止其他事务在该范围内插入或删除记录。
- Index Locking(索引加锁):这种方法是利用索引结构来实现范围加锁。当一个事务查询某个范围内的记录时,不仅要对找到的记录加锁,还要对该范围内可能存在但未找到的记录加锁。
Index Lock
Index Lock是一种在索引结构上实现数据的并发控制的方法,它可以保证事务的隔离性和一致性。不同类型的索引锁定有不同的特点和适用场景,以下是它们的简要介绍:
- Key-Value Lock(键值锁):这种锁定方式是对索引中的某个键值加上共享锁或排他锁,防止其他事务对该键值对应的记录进行读取或修改。例如,如果一个事务要更新一个记录的某个属性,它就需要对该记录的主键加上排他锁,确保其他事务不能同时读取或修改该记录。这种锁定方式的优点是简单直观,缺点是不能防止幻读问题,即其他事务在同一个范围内插入或删除新的记录1。
- Gap Lock(间隙锁):这种锁定方式是对索引中的某个间隙(两个相邻键值之间的空隙)加上共享锁或排他锁,防止其他事务在该间隙内插入新的记录。例如,如果一个事务要查询一个范围内的所有记录,它就需要对该范围内的所有间隙加上共享锁,确保其他事务不能在该范围内增加新的记录。这种锁定方式的优点是可以避免幻读问题,缺点是需要维护额外的间隙信息,并且可能会造成较多的锁冲突2。
- Key-Range Lock(键范围锁):这种锁定方式是对索引中的某个范围(由两个边界键值确定)加上共享锁或排他锁,防止其他事务在该范围内插入或删除新的记录。例如,如果一个事务要删除一个范围内的所有记录,它就需要对该范围加上排他锁,确保其他事务不能在该范围内读取或修改任何记录。这种锁定方式的优点是可以避免幻读问题,并且可以支持变长键值和无穷大键值,缺点是需要维护额外的边界信息,并且可能会造成较多的锁冲突3。
- Hierarchical Lock(层次锁):这种锁定方式是利用索引结构本身的层次性,在不同层次上加上不同类型的锁,从而实现更细粒度和更高效率的并发控制。例如,在B+树索引中,可以在根节点上加上意向共享锁或意向排他锁,表示事务要在子节点上加上共享锁或排他锁;在叶子节点上加上键值锁或间隙锁,表示事务要对某个键值或间隙进行读取或修改。这种锁定方式的优点是可以减少不必要的锁冲突和死锁,并且可以支持多粒度和可串行化的隔离级别,缺点是需要维护额外的意向信息,并且可能会造成较多的层次遍历
Locking Without an Index
如果没有合适的索引,则txn必须获取
- 表中每一页上的锁,以防止记录的 status=“lit”更改为lit。
- 表本身的锁,以防止添加或删除 status='lit的记录。
Weaker Levels of Isolation
Weaker levels of isolation 是一种用来比较不同数据库系统的事务隔离级别的方法。事务隔离级别是指一个数据库系统在处理多个并发事务时,如何保证数据的一致性和完整性的能力。
通常,一个数据库系统需要在事务隔离级别和性能之间做出权衡。如果要求高度的事务隔离级别,例如 Serializable,那么就需要对数据进行严格的锁定或版本控制,以防止多个事务之间发生冲突或异常。这样做会降低数据库系统的并发性和吞吐量。
如果要求较低的事务隔离级别,例如 Read Committed 或 Snapshot Isolation,那么就可以放宽对数据的锁定或版本控制,以提高数据库系统的并发性和吞吐量。但是,这样做会导致一些不可预期的结果或异常,例如脏读、不可重复读、幻读、更新丢失等。
Weaker levels of isolation 就是一种用来评估不同数据库系统在选择较低的事务隔离级别时,可能出现的结果或异常的方法。这种方法最早由 Waaker 在 1995 年提出,他根据四种读写现象(dirty read, non-repeatable read, phantom read, and lost update)将事务隔离级别分为四个层次(Level 0, Level 1, Level 2, and Level 3),并用一个四维向量来表示一个数据库系统的隔离级别能力。
Isolation Levels
数据库系统的事务隔离级别是指一个数据库系统在处理多个并发事务时,如何保证数据的一致性和完整性的能力。事务隔离级别通常需要在性能和隔离性之间做出权衡,因为高度的隔离性往往会降低并发性和吞吐量,而低度的隔离性则可能导致一些不可预期的结果或异常。
SQL 标准定义了四种隔离级别,它们分别是:
- 读未提交(Read Uncommitted):这是最低的隔离级别,它允许一个事务读取另一个事务未提交的数据,也就是可能发生脏读(Dirty Read)的情况。脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。
- 读已提交(Read Committed):这是大多数数据库系统的默认隔离级别,它只允许一个事务读取另一个事务已提交的数据,也就是避免了脏读的情况。但是,它仍然可能发生不可重复读(Non-repeatable Read)的情况。不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。
- 可重复读(Repeatable Read):这是 MySQL 的默认隔离级别,它确保同一事务的多次查询都能看到同样的数据行,也就是避免了不可重复读的情况。但是,它仍然可能发生幻读(Phantom Read)的情况。幻读是针对数据插入(INSERT)操作来说的。假设事务 A 对某些行的内容作了更改,但是还未提交,此时事务 B 插入了与事务 A 更改前的记录相同的记录行,并且在事务 A 提交之前先提交了,而这时,在事务 A 中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务 B 刚插入进来的,让用户感觉很魔幻,感觉出现了幻觉,这就叫幻读。
- 串行化(Serializable):这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读取或修改数据行上加上锁。在这个级别下,可能导致大量的超时现象和锁竞争。
读未提交(Read Uncommitted):这个级别下基本不需要任何锁。
读已提交(Read Committed):这个级别下需要用到行锁或页锁来保证写操作不会被其他写操作干扰,并且在写操作完成后立即释放锁以提高并发性。
可重复读(Repeatable Read):这个级别下需要用到行锁或页锁来保证写操作不会被其他写操作干扰,并且在事务结束后才释放锁以保证多次查询结果一致。另外,在 MySQL 中还需要用到间隙锁(Gap Lock)来防止其他事务插入新行导致幻读的问题。
串行化(Serializable):这个级别下需要用到表锁来保证整个表的数据不会被其他事务修改,从而实现完全的串行化
事务隔离级别特性
隔离级别 | 锁类型 | 二阶段锁协议 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|---|
SERIALIZABLE | 数据行锁 + 索引锁 | 严格 2PL | 否 | 否 | 否 |
REPEATABLE READS | 数据行锁 | 严格 2PL | 否 | 否 | 否 |
READ COMMITTED | 数据行锁 | 部分 2PL | 是 | 是 | 是 |
READ UNCOMMITTED | 数据行锁 | 部分 2PL | 是 | 是 | 是 |