数据库并发控制

并发控制是一组用于处理并发事务间交互的技术。这些技术可以大致分为以下几类:

  • 乐观并发控制 (OCC) 允许多个事务执行并发的读取和写入操作,最后确定其执行 结果能否被串行化 (serializable译注)。换句话说,事务不会彼此阻塞,而是保留其 操作历史,并在提交前检查这些历史操作是否存在冲突的可能。如果会产生冲突, 则中止其中某一个冲突的事务。
  • 多版本并发控制 (MVCC) 允许一条记录同时存在多个时间戳的版本,通过这种方 式保证事务读到的是数据库过去某个时刻的一致视图。 MVCC 可以使用验证技术来 实现,即只允许多个更新或事务提交中的某一个获胜,也可以使用无锁技术(例如 时间戳排序)或基于锁的技术(例如两阶段锁)。
  • 悲观(也称为保守)并发控制 (PCC) 既有基于锁的实现,也有不加锁的实现,它们的主要区别在于如何管理和授权对共享资源的访问。基于锁的实现要求事务维护数 据库记录上的锁,以防止其他事务修改被加锁的记录或访问当前事务正在修改的记录,直到锁被释放为止。不加锁的实现根据未完成事务的调度,维护读取与写入的 操作列表以限制事务的执行。悲观的调度可能导致死锁;多个事务需要互相等待对方释放锁才能继续执行。

1 可串行化

调度 (schedule) 是指数据库视角执行一组事务所需的操作列表(即仅包含与数据库状态交互的操作,例如读、写、提交和中止),因为我们假定其他所有的操作都没有副作用(换句话说,不影响数据库状态)。

如果一个调度中的事务完全独立且无交错地执行,则我们称它为串行的调度。如果一个调度等效于同一组事务的某一完整串行调度,则该调度是可串行化的。换句话说,它产生的结果与我们以某种顺序一个接一 个地执行一组事务的结果相同。

2 事务隔离

事务型数据库系统有着不同的隔离级别。隔离级别指定了事务的各部分如何以及何时可以被其他事务看到。

实现隔离需要付出一定的代价,为了防止其他事务读到不完整或临时的写入,我们需要额外的协调和同步机制,而这会对性能产生负面影响。

3 读异常和写异常

脏读 (dirty read) 是指一个事务能读到其他事务未提交的更改。

不可重复读 (nonrepeatable read, 有时也称为模糊读 (fuzzy read)), 是指同一事务两次查询同一行却得到不同的结果。

丢失更新 (lost update) 发生在事务T₁T₂ 同时尝试更新V 的值时,T₁T₂读取V 的值。T₁ 更新V并提交,之后T₂也更新V并提交。由于这两个事务不知道彼此的存在, 所以如果允许二者都提交,则T₁的结果将被T₂的结果覆盖, T₁的更新将丢失。

脏写 (dirty write) 指的是某个事务拿到了一个未提交的值(即脏读),对其进行修改并保存。

写偏斜 (write skew) 是指各个单独的事务都遵守要求的约束,但它们的组合却违反了这些约束。例如,事务 T₁T₂ 修改两个账户A₁A₂的值。最初A₁100元, A₂有150元。账户的值可以为负,只要两个账户之和为非负数就行,即要满足A₁+A₂≥ 0T₁T₂分别尝试分别从A₁A₂取出200元。在两个事务开始时,A1+A₂= 250元,因此共有250元可用。两个事务都认为它们没有违反约束,可以提交。提交后, A₁为*-100元, A₂-50*元,这显然违反了账户之和为非负数的要求。

4 隔离级别

在这里插入图片描述

5 乐观并发控制

乐观并发控制假设事务冲突很少发生,并且不同于加锁阻塞事务执行的方式,我们可以通过在事务提交前验证事务防止并发事务间发生读或写冲突,确保可串行化。 一般来说,事务执行分为三个阶段:

  • 读阶段
  • 验证阶段
  • 写阶段

验证有两种实现方式检查与已提交事务的冲突(后向式),以及检查与当前处于验证阶段的事务的冲突(前向式)

后向式并发控制确保任意一对事务T₁T₂ 都具有以下性质:

  • 如果T₁T₂ 的读阶段开始之前提交,则T₂ 可以提交。
  • 如果T₁T₂ 的写阶段之前提交,则T₁ 的写集合与T₂ 的读集合不相交。
  • 如果T₁ 的读阶段在T₂ 的读阶段之前完成,则T₂ 的写集合与T₁ 的读或写集合均不相交。

由于重试会对性能产生明显的负面影响,所以在验证通常是成功的因而无须重试事务的情况下,这种方法是高效的。当然,乐观并发仍然具有临界区同一时刻只能进入一个事务

6 多版本并发控制

多版本并发控制是数据库中实现事务一致性的一种方法,它允许记录存在多个版本,并使用单调递增的事务ID时间戳。这使得读写操作在存储层面上只需要最小限度的协调,因为读操作可以继续访问旧的值,直到新的值被提交。

可以用加锁、调度和冲突解决技术(例如两阶段锁)来实现,也可以用时间戳排序实现。实现快照隔离是MVCC的一大使用场景。

7 悲观并发控制

悲观并发控制方案比乐观方案更保守。这种方案在事务运行时确定其间的冲突并阻塞或中止执行

时间戳排序是最简单的悲观(无锁)并发控制方案之一,其中每个事务都有一个时间戳, 事务操作能否执行取决于是否已经提交过时间戳更晚的事务。

8 基于锁的并发控制

基于锁的并发控制方案是悲观并发控制的一种形式,它对数据库对象显式地加锁。使用锁的缺点包括锁竞争扩展性问题

  • 死锁

在锁协议中,事务尝试获取数据库对象上的锁,如果无法立刻获得锁,则事务必须等待直到锁被释放。可能会发生这样的情况:两个事务都在尝试获取所需的锁以继续执行, 而最后都在等待对方释放其持有的某个锁。这种情况称为死锁

死锁检测通常是用等待图实现的,等待图跟踪进行中的事务间的关系并建立其间的等待关系。等待图中的环表明出现了死锁

用于隔离和调度重叠的事务管理数据库内容(而非内部存储结构),并且锁是在键上获取的。锁可以保护某个特定的键(无论该键存不存在)或一个范围内的键。锁通常在树实现之外进行存储和管理,它表示一个较高层级的概念,由数据库的锁管理器管理

锁比闩锁更重量级,且在事务执行期间一直持有

  • 闩锁

闩锁用于保护物理表示:在插入、更新和删除操作期间叶子页的内容会被修改;在叶子页发生下溢或上溢时页的分裂与合并向上传播非叶子页的内容以及树结构也会被修改。在这些操作期间闩锁保护了树的物理表示(页内容及树结构),并且它是在页的级别上获取的。在访问任何页前必须先加闩锁,以确保并发安全无锁并发控制技术也必须使用闩锁。

  • 读写锁

最简单的闩锁实现将会授予请求线程排他性的读写访问权限。大多数时候,我们不需要把所有进程相互隔离。我们只要确保并发的写操作不重叠,以及读操作与写操作不重叠。为了达到这种粒度级别,我们可以使用读写锁(RW锁)。

  • 闩锁耦合

加闩锁最直接的方法是获取从根到目标叶节点途中的所有闩锁。这会导致并发性能瓶颈,而且在大多数情况下可以避免。持有闩锁的时间应尽可能短。为了达到这一目标, 其中一种优化方式称为闩锁耦合

闩锁耦合是一种非常简单的方法:它允许持有闩锁的时间更短,并在当前操作明显不再需要它们时立即释放。在读取路径上, 一旦找到子节点并获得它的闩锁,就可以释放父节点的闩锁

插入时,如果能保证操作不会引起能传播到当前节点的结构变化,则可以释放父节点上的闩锁。换句话说,如果子节点还没满,则可以释放父闩锁。在删除时,如果子节点包含足够多的元素,或者该操作不会导致节点合并,则可以释放父节点上的闩锁

  • Blink

Blink构建在B *树的基础上并添加了高键同级链接指针除根节点外Blink树中的每个节点都有两个指针子节点指针同级链接指针

Blink树允许存在所谓的半分裂状态,这时该节点已被同级指针引用,但没有被上一层 的子节点指针引用。通过检查节点高键,可以识别出半分裂状态。如果要查找的键大于节点的高键(违反了高建的约束),则查找算法可以断定树结构已被并发地修改,因此它根据同级链接指针找到右侧相邻节点并继续查找。

为了获得最佳性能,最好尽快将指针添加到父节点中,但是无须终止并重启整个查找过程,因为树中的所有元素都是可以被访问的。这样做的好处是,即使子节点会分裂,遍历子节点时也无须保留父节点的锁:我们可以通过同级链接指针使新节点可见,并懒惰地更新父节点的指针而不失正确性

这种方法有很多好处

  • 减少了锁竞争
  • 在分裂期间不用保留父节点的锁,并把在树结构变化期间持有锁的数量减少到一个常数。
  • 读操作和树结构变化可以并发地进行,并防止死锁——并发的修改操作在向父节点传播时可能引起死锁。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值