18-Timestamp Ordering Concurrency Control

本文根据CMU15-445课程内容编写,因为数据库术语较多,为避免翻译问题带来的理解偏差,部分术语使用英语表达。

1. Timestamp Ordering Concurrency Control

时间戳序列(Timestamp ordering)是一种乐观并发控制协议。它假设事务冲突发生的可能性很小,因此允许事务在没有获得对象的锁的情况下对对象进行读写操作。

时间戳序列给每一个事务一个时间戳来决定这些事务的串行化顺序,如果用 T S ( T i ) TS(T_i) TS(Ti)表示事务 T i T_i Ti的时间戳,那么当 T S ( T i ) < T S ( T j ) TS(T_i)<TS(T_j) TS(Ti)<TS(Tj)时,DBMS必须保证事务的执行结果等价于一个事务 T i T_i Ti早于事务 T j T_j Tj出现的串行化方案。

时间戳必须满足单调增长的特点,也就是说,每个事务的时间戳都是不同的,同时,所有的事务也可以根据时间戳排序成一个有序序列。


Q1: 那么在什么时候给事务赋时间戳呢?

A1: 不同的方案在事务处理的不同时间赋予时间戳,一些先进的数据库甚至可以给同一个事务赋予多个时间戳。一般而言可以在事务开始时赋予时间戳。


Q2: 可以用什么作为时间戳呢?

A2: 一般而言可以用系统时钟、逻辑计数器或者混合的方案。


Q3: 使用系统时钟作为时间戳可能哪些问题?

A3: 使用系统时钟作为时间戳可能出现的问题:

  • 在分布式系统中,很难保证所有机器的时钟都同步。
  • 保存系统时间的寄存器可能会溢出。
  • 系统校正时钟时会改变时钟,破坏了时间戳的单调性。

2. Basic Timestamp Ordering

基础的时间戳协议允许事务在没有获得锁的情况下对对象进行读写操作,但是为了保证事务运行的正确性,我们仍然需要给对象一些元数据(meta-data)。

每个对象 X X X都会记录两个时间戳,一个是最近一个对其进行读操作的事务的时间戳,另一个是最近一个对其进行写操作的事务的时间戳,分别用 R − T S ( X ) R-TS(X) RTS(X) W − T S ( X ) W-TS(X) WTS(X)表示。事务对对象进行读写操作之前,需要检查这两个时间戳是否满足基础时间戳序列协议,如果违反了协议,那么需要终止并且重启。下面介绍读写时需要遵循的规则。

读操作

当事务 T i T_i Ti需要对对象 X X X进行读操作时,需要检查对象 X X X W − T S ( X ) W-TS(X) WTS(X)。如果 T S ( T i ) < W − T S ( X ) TS(T_i) < W-TS(X) TS(Ti)<WTS(X),那么说明事务 T i T_i Ti正在读一个未来事务写入的数据,这是违反规则的,因此,事务 T i T_i Ti需要终止并重启,重启之后需要赋予一个新的时间戳。反之,则正常读取,并且更新 R − T S ( X ) R-TS(X) RTS(X)为最新的时间戳,即 m a x ( R − T S ( X ) , T S ( T i ) ) max(R-TS(X), TS(T_i)) max(RTS(X),TS(Ti))。同时,需要保存 X X X的一个本地副本,保证可重复读。

写操作

对于写操作,如果 T S ( T i ) < W − T S ( X ) TS(T_i) < W-TS(X) TS(Ti)<WTS(X)或者 T S ( T i ) < R − T S ( X ) TS(T_i) < R-TS(X) TS(Ti)<RTS(X),那么表明未来的某个事务已经对其写入或者读取了,那么当前事务 T i T_i Ti不能再对其写入,因此事务 T i T_i Ti必须终止并重启。同时,事务 T i T_i Ti需要在本地保存一个对象 X X X的一个副本,保证可重复读。

Thomas write rule

这是对基础时间戳协议的一个优化,当执行写操作时,如果 T S ( T i ) < W − T S ( X ) TS(T_i) < W-TS(X) TS(Ti)<WTS(X),那么此时其实事务可以不用终止,也不用写入对象 X X X,只需要在本地保存一个副本即可,这违反了基础时间戳协议,但是并不影响事务执行的正确性。

评价

  1. 基础时间戳协议不存在死锁问题,因为时间戳序列方式不存在等待,而且时间戳单调增长,等待图(如果有的话)不可能出现环。
  2. 基础时间戳协议依然存在饥饿问题,一个执行时间较长的事务可能会被一些小的事务频繁打断并不断重启。
  3. 基础时间戳协议存在脏读问题
  4. 基础时间戳协议是不可恢复(not recoverable )的。如果一个事务用到了其他事务的修改,那么这个事务必须等待其他事务都提交之后才能提交,那么称这种调度方案是可恢复(recoverable)的
  5. 基础时间戳协议的开销大,需要维护每个对象的元数据(两个时间戳),并且事务运行过程中需要复制数据到本地空间。
  6. 在高并发情况下,时间戳的分配可能成为性能瓶颈。

脏读问题举例

image-20221011213521422

在上述流程中,事务 T 1 T_1 T1写入对象A,最后终止。但是事务 T 2 T_2 T2读到了事务 T 1 T_1 T1写入的脏数据。

3. Optimistic concurrency control(OCC)

虽然时间戳协议是一个乐观并发控制协议,但是其实它的行为也有一些悲观,因为它每次读写操作也要检查时间戳是否符合规则。下面介绍另外一种乐观并发控制协议—Optimistic concurrency control,简称OCC协议。

OCC协议使用时间戳来验证事务,它假设事务之间的冲突很少,即大多数事务是只读的或者事务使用的数据是不相交的。

OCC协议为每个事务创建了一个私有空间,对数据的所有操作都在这个私有空间中进行。读出的数据会被复制到这个空间,写入的数据也写入到这个空间,其他事务不能访问这个私有空间。

当事务提交时,DBMS检查事务的私有空间的写集(write set) 和其他事务是否冲突。如果不存在冲突,那么就可以将写集(write set)写入公共空间(global space)。

OCC协议包含三个阶段

  • Read Phase(Work Phase): 事务处理,并且将事务的修改写入私有空间。
  • Validation Phase: 当事务提交时,检查和其他事务是否存在冲突。
  • Write Phase: 如果validation成功,将事务的修改写入数据库,否则终止事务并重启。

Validation Phase

DBMS在事务进入validation phase时赋予时间戳。因为DBMS需要保证事务可串行化,所以需要在validation phase检查当前事务和其他正在运行的事务是否存在RW和WW冲突。没有进入validation phase的事务时间戳为 ∞ \infty

在validation phase,有两种冲突检测方式,分别为backward validationforward validation

image-20221012213555580

backward validation: 查看当前事务和已提交事务是否存在冲突。

image-20221012213651190

Forward validation: 检查当前事务和正在运行事务是否存在冲突。


我们以Forward validation举例,讲解如何进行validation。

如果事务 T S ( T i ) < T S ( T j ) TS(T_i) < TS(T_j) TS(Ti)<TS(Tj),那么可能有三种情况:

  1. 事务 T i T_i Ti完成了所有阶段,而事务 T j T_j Tj还没开始。 这种情况下事务 T i T_i Ti和事务 T j T_j Tj不会产生冲突,所以不用检测。
  2. 事务 T i T_i Ti正在validation phase,而事务 T j T_j Tj完成了work phase等待进入validation phase。 这种情况下要求事务 T i T_i Ti的写集和事务 T j T_j Tj的读集有交集,即 W r i t e S e t ( T i ) ∩ R e a d S e t ( T j ) = ∅ WriteSet(T_i) \cap ReadSet(T_j) = \varnothing WriteSet(Ti)ReadSet(Tj)=。很好理解,因为此时 T j T_j Tj读到的数据可能不是最新的数据。
  3. 事务 T i T_i Ti正在进行validation,而事务 T j T_j Tj还没有完成work phase。 这种情况下要求事务 T i T_i Ti的写集和事务 T j T_j Tj的读集和写集都不相交,前者很好理解,后者是因为(这里我感觉很难解释),事务 T i T_i Ti尚未写入,我们不能保证 T i T_i Ti T j T_j Tj的写入顺序。

在write phase,DBMS将事务的修改应用到数据库中,并且让它们对其他事务可见。特别的,逻辑上一次只能有一个事务在写阶段,但是可以通过write latch(注意是latch,不是lock,latch是不会发生死锁的) 实现并行的write phase。

注意:即使我们为每个事务设计了一个私有空间,不同事务的私有空间逻辑上是不相交的。但是我们无法在物理上避免这一点,例如,当某一个事务进入validation阶段时,它需要查看其他正在运行的事务中的read set或者write set。同时虽然OCC不需要用到锁lock,但是我们无法避免事务对某些数据结构的并发访问,所以仍然需要latch来保证并发

评价

  1. OCC冲突很少时的效果很好,比如,当大多数的事务都是只读事务,或者事务用到的数据集是不相交的时,OCC的开销会比用lock的协议少一些。
  2. 但是当冲突很多时,OCC的开销比基础时间戳协议还要多,因为OCC需要在事务运行完成之后才能在validation phase发现冲突,而基础时间戳协议在事务运行时就能发现冲突并终止。
  3. 为每一个事务分配私有空间的开销也很大。
  4. Validation/Write phase可能成为瓶颈

4. Partition-based Timestamp Ordering

受到OCC的启发,如果我们更进一步,将整个数据库划分为不相交的分区,每个事务都在独立的分区中运行,这种方式就是Partition-based Timestamp Ordering。

首先将数据库划分为互不相交的水平分片(horizontal partitions or shards)。在每个分片上,用时间戳来控制事务的顺序运行一个分片上一次只有一个事务在运行,这样事务省去了并发控制协议的开销,每个事务好像是在单线程运行一样。

事务提交给DBMS运行时获得时间戳。

每一个分片只有一个锁

  • 每个事务在其所需的分片上排队。对于跨分区事务,事务需要在多个分片上排队。
  • 如果事务是队列中时间戳最小的事务,那么它就可以获得这个分片的锁。
  • 事务只有获得了它所需的所有分片的锁之后才可以开始运行。

难点:怎么在事务运行之前,就能预测其所需要的分区。 如果事务在运行过程中才发现需要用到其他分区的数据,而又没有获取对应分区的锁,那么事务就必须终止并重启。

评价:

  1. 数据库设计和分区对于事务运行效率影响很大。因为在单个分区中run事务很快,但是对于跨分区事务就会变得很复杂。
  2. 数据库分区的粒度越小,那么并发程度就越高,但是出现跨分区事务的情况就会更多。
  3. 可能出现热点问题(hot point),即某一个分区被频繁访问,而其他分区处于闲置状态。

5. The Phantom problem(幻读问题)

之前我们仅仅允许事务对数据进行读写操作,可能引发的问题是不可重复读、脏读。在允许事务插入或者删除数据之后,又会带来新的问题—幻读

幻读是由插入和删除引起的两次SQL语句的操作结果不一致。例如:

image-20221014215729995

解决幻读问题的方法:

  • 层次锁: 可以在表的层次上锁,防止其他事务的插入,删除操作。
  • Predicate Locking(谓词锁): 对where语句中的抽象对象上锁。在SELECT语句中的where语句中的谓词上读锁。在UPDATE,INSERT,DELETE语句中的where语句中的谓词上写锁。例如,对于上图中的情况,我们对T1中的SELECT语句中的所有where status = 'lit’的抽象对象上读锁,这样T2中的INSERT语句需要等待T1释放读锁之后才能获取到where status = 'lit’的抽象对象的写锁。这种方式实现很困难,目前还没有多少系统实现(除了HyPer)
  • Index Locking:在上图的问题中,如果我们建立了一个在status字段上的索引。那么我们可以对所有中status = 'lit’的slot上锁,这样就可以防止其他事务对status = 'lit’的对象进行修改。如果不存在status = 'lit’的slot,那么我们可以使用gap lock,即我们锁住了status = 'lit’的这个gap,这样其他事务就不能向status = 'lit’的slot添加对象。Index locking是predicate locking的一种特殊结构
  • Re-execute scans:这个方法相对简单,在事务运行的过程中,记录所有where语句的scan set(扫描集合),在事务提交之前重新运行这些带where语句的SQL语句,查看是否生成了相同的结果,如果不是,那么说明出现幻读问题,需要重启。

Q4: 2PL可以解决幻读问题吗?

A4: 不能,因为不能对不存在的对象上锁。


6. Isolation Levels

Isolation LevelsDirty ReadUnrepeatable ReadsPhantom Reads
Serializable(可串行化)
Repeatable reads(可重复读)
Read-Committed(读提交)
Read-Uncommitted(读未提交)

有两种额外的隔离等级。

  1. Cursor stability: 在可重复读和可串行化之间,可以阻止Lost Update现象。
  2. Snapshot Isolation: 保证在事务读取的数据都是事务开始时存在的数据库的一致性快照。只有当事务的写入与该快照以来进行的任何并发更新不冲突时,事务才会提交。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值