交易及其控制机制

交易

事务是对具有开始和结束的数据的操作序列。

事务是读和写操作的顺序执行。 事务的结束可以是保存更改(提交)或取消更改(回滚)。 就数据库而言,事务由多个请求组成,这些请求被视为单个请求。

事务必须满足 ACID 属性

原子性。 交易要么完全完成,要么根本没有完成。

一致性。 完成事务时,不得违反对数据施加的限制(例如数据库中的约束)。 一致性意味着系统将从一种正确状态转移到另一种正确状态。

隔离。 并行运行的事务不应相互影响,例如更改另一个事务使用的数据。 执行并行事务的结果应该与顺序执行事务的结果相同。

可持续性。 一旦提交,更改就不应丢失。

交易日志

日志存储事务所做的更改,确保系统发生故障时数据的原子性和稳定性

日志包含数据在事务更改之前和之后的值。 预写日志策略需要在开始之前添加有关先前值的日志条目,并在事务完成后添加有关最终值的日志条目。 如果系统突然停止,数据库会以相反的顺序读取日志并取消事务所做的更改。 遇到中断的事务后,数据库会执行它并对日志进行更改。 处于故障时的状态,数据库按正向顺序读取日志并返回事务所做的更改。 这样,已经提交的事务的稳定性和被中断的事务的原子性得以保留。

仅仅重新执行失败的事务不足以恢复。

例子。 用户的帐户中有 500 美元,并且用户决定从 ATM 机上提取。 两项交易正在进行中。 第一个读取余额值,如果余额中有足够的资金,则会向用户发放资金。 第二个从余额中减去所需的金额。 假设系统崩溃,第一次操作失败,但第二次操作失败。 在这种情况下,我们无法在系统恢复到原始状态且余额为正的情况下向用户重新发放资金。

绝缘等级

读已提交

脏读问题是一个事务可以读取另一个事务的中间结果。

例子。 初始余额值为 0 美元。 T1 为您的余额添加 50 美元。 T2 读取余额(50 美元)。 T1 放弃更改并退出。 T2 继续执行,但余额数据不正确。

解决方案是读取固定数据(Read Comied),即禁止读取事务改变的数据。 如果事务A更改了某组数据,那么事务B在访问该数据时,将被迫等待事务A完成。

可重复读取

丢失更新问题。 T1 在 T2 的更改之上保存更改。

例子。 初始余额值为 0 美元,两笔交易同时补充余额。 T1 和 T2 读取余额为 0 美元。 然后 T2 将 $200 加到 $0 上并保存结果。 T1 将 $100 加到 $0 上并保存结果。 最终结果是 100 美元,而不是 300 美元。

不可重复读取问题。 重复读取相同的数据会返回不同的值。

例子。 T1 读取余额值为 0 美元。 T2 然后向余额添加 50 美元并结束。 T1再次读取数据,发现与之前的结果有出入。

可重复读取确保第二次读取将返回相同的结果。 在事务完成之前,一个事务读取的数据不能在其他事务中更改。 如果事务A读取了某组数据,那么事务B在访问该数据时,将被迫等待事务A完成。

有序读取(可序列化)

幻读问题。 根据特定条件选择数据的两个查询返回不同的值。

例子。 T1 请求余额大于 0 美元但小于 100 美元的所有用户的数量。 T2 从余额为 1 美元的用户中扣除 101 美元。 T1 重新发出请求。

有序读取(可序列化)。 事务完全按顺序执行。 禁止更新或添加符合请求条款的记录。 如果事务 A 已请求整个表的数据,则整个表将被冻结以供其他事务使用,直到事务 A 完成。

调度程序

设置并行事务期间执行操作的顺序。

提供指定的隔离级别。 如果运算的结果不依赖于它们的顺序,那么这样的运算是可交换的(Permutable)。 读操作和对不同数据的操作是可交换的。 读写和写-写操作不可交换。 调度程序的任务是交错并行事务执行的操作,使得执行结果相当于事务的顺序执行。

控制并行作业的机制(并发控制)

乐观是基于发现和解决冲突,悲观是基于防止冲突发生。

在乐观的方法中,多个用户拥有可供使用的数据副本。 第一个完成编辑的人保存更改,而其他人必须合并更改。 乐观算法允许冲突发生,但系统必须从冲突中恢复。

采用悲观方法,第一个捕获数据的用户会阻止其他人接收数据。 如果冲突很少,那么选择乐观策略是明智的,因为它提供了更高级别的并发性。

锁定

如果一个事务锁定了数据,那么其他事务在访问该数据时必须等待它被解锁。

块可以覆盖在数据库、表、行或属性上。 共享锁可以由多个事务对同一数据施加,允许所有事务(包括施加该数据的事务)读取,禁止修改和独占捕获。 独占锁只能由一个事务施加,允许施加事务的任何操作,禁止其他事务的任何操作。

死锁是指事务最终处于无限期持续的挂起状态的情况。

例子。 第一个事务等待第二个事务捕获的数据被释放,而第二个事务等待第一个事务捕获的数据被释放。

死锁问题的乐观解决方案允许死锁发生,但随后通过回滚死锁所涉及的事务之一来恢复系统。

以一定的时间间隔搜索死锁。 其中一种检测方法是按时间,即如果事务完成时间过长,则认为发生了死锁。 当发现死锁时,其中一个事务将回滚,从而允许死锁中涉及的其他事务完成。 受害者的选择可以基于交易的价值或其资历(Wait-Die 和 Wound-wait 方案)。

每笔交易 T 分配了时间戳 TS 包含交易的开始时间。

等等,死吧。

如果 TS(钛) < TS(Tj),然后 Ti 等待,否则 Ti 回滚并以相同的时间戳重新开始。

如果新的事务已获取资源并且较旧的事务请求相同的资源,则允许较旧的事务等待。 如果较旧的事务已获取资源,则请求该资源的较新的事务将被回滚。

伤口等待。

如果 TS(钛) < TS(Tj),然后 Tj 回滚并以相同的时间戳重新开始,否则 Ti 等待。

如果较新的事务已获取资源,而较旧的事务请求相同的资源,则较新的事务将被回滚。 如果较旧的事务已获取资源,则允许请求该资源的较新的事务等待。 基于优先级的受害者选择可以防止死锁,但会回滚未发生死锁的事务。 问题是事务可以回滚很多次,因为...... 较旧的事务可能会长时间持有该资源。

死锁问题的悲观解决方案不允许事务在存在死锁风险时开始执行。

为了检测死锁,构建一个图(等待图,wait-for-graph),该图的顶点是事务,边从等待释放数据的事务指向已经捕获该数据的事务。 如果图有循环,则认为发生了死锁。 构建等待图,尤其是在分布式数据库中,是一个昂贵的过程。

两阶段锁定 - 通过在事务开始时占用事务使用的所有资源并在事务结束时释放它们来防止死锁

所有阻塞操作必须先于第一个解锁操作。 它有两个阶段 - 增长阶段,在此期间握力积累,以及收缩阶段,在此期间握力被释放。 如果无法捕获其中一项资源,则事务重新开始。 事务可能无法获取所需的资源,例如,如果多个事务竞争相同的资源。

两阶段提交确保提交在所有数据库副本上执行

每个数据库将有关将要更改的数据的信息输入日志并响应协调器 OK(投票阶段)。 当每个人都回答“OK”后,协调员会发出一个信号,要求每个人都做出承诺。 提交后,服务器响应“OK”;如果至少有一个服务器没有响应“OK”,则协调器发送一个信号以取消对所有服务器的更改(完成阶段)。

时间戳法

当尝试访问较新事务涉及的数据时,较旧事务将回滚

每笔交易都会分配一个时间戳 TS 对应于执行的开始时间。 如果 Ti 前辈 Tj,然后 TS(钛) < TS(Tj).

当事务回滚时,它会被分配一个新的时间戳。 每个数据对象 Q 涉及的交易被标记了两个标签。 W-TS(Q) — 成功完成记录的最年轻交易的时间戳 Q. R-TS(Q) — 执行读取记录的最新事务的时间戳 Q.

交易时 T 请求读取数据 Q 有两种选择。

如果 TS(T) < W-TS(Q),即数据被较年轻的事务更新,则该事务 T 回滚。

如果 TS(T) >= W-TS(Q),然后执行读取并且 R-TS(Q) 成为 MAX(R-TS(Q), TS(T)).

交易时 T 请求数据更改 Q 有两种选择。

如果 TS(T) < R-TS(Q),也就是说,数据已经被较年轻的事务读取过,如果进行更改,就会产生冲突。 交易 T 回滚。

如果 TS(T) < W-TS(Q),即事务尝试覆盖较新的值,事务 T 被回滚。 在其他情况下,进行更改并 W-TS(Q) 变得平等 TS(T).

不需要昂贵的等待图构建。 较旧的事务依赖于较新的事务,因此等待图中没有循环。 不存在死锁,因为事务不是等待而是立即回滚。 级联回滚是可能的。 如果 Ti 滚走了并且 Tj 我读取了我更改的数据 Ti,然后 Tj 也应该回滚。 如果同时 Tj 如果已经发生了,那么就会违反稳定性原则。

级联回滚的解决方案之一。 一个事务在最后完成所有写操作,其他事务必须等待该操作完成。 事务在读取之前等待提交。

Thomas 写入规则 - 时间戳方法的一种变体,其中较新事务更新的数据禁止被较旧事务覆盖

交易 T 请求数据更改 Q。 如果 TS(T) < W-TS(Q),即事务尝试覆盖较新的值,事务 T 不会像时间戳方法那样回滚。

来源: habr.com

添加评论