数据库管理系统 (DBMS) 中的事务
定义:
数据库管理系统 (DBMS) 中的事务是作为单个逻辑工作单元执行的一系列操作。事务可以涉及读取、插入、更新或删除数据库中的数据。事务的关键特征是它是原子的,这意味着事务中的所有操作都成功完成,或者根本没有任何操作应用于数据库。
事务的关键属性:ACID 属性
事务必须遵守ACID属性以确保数据库的一致性和可靠性。这些属性是:
-
原子性:
- 原子性确保事务被视为单个、不可分割的工作单元。要么执行事务中的所有操作,要么不执行。如果事务的任何部分失败,整个事务将回滚,确保数据库保持一致的状态。
-
示例:如果一笔交易涉及从账户 A 向账户 B 转账,原子性可确保整个转账完成(从 A 扣除资金并添加到 B)或不执行任何操作(不转账)如果失败)。
-
一致性:
- 一致性确保事务将数据库从一种有效状态转移到另一种有效状态。任何事务都不应该违反数据库的完整性约束(例如主键、外键等)。事务提交后,数据库应该始终处于一致状态。
-
示例:在两个银行帐户之间转账后,两个帐户中的余额总和应保持不变。如果违反数据库一致性规则,事务将会回滚。
-
隔离:
- 隔离性确保事务的操作在执行时对其他事务隐藏。即使多个事务同时发生,每个事务也应该不知道其他事务的操作。这可以防止脏读、不可重复读和幻读。
-
示例:如果两个用户同时从同一个银行账户转账,隔离可确保一笔交易不会干扰另一笔交易,从而防止重复提款等错误。
-
耐用性:
- 持久性保证一旦事务提交,其更改就是永久性的,即使在系统崩溃的情况下也是如此。成功提交后,事务所做的更改将保存到数据库中,并且不会丢失。
-
示例:如果用户成功下订单,数据库中的更改(例如库存更新和下订单)应该持续存在,即使提交后突然断电也是如此。
交易类型
-
简单交易:
- 这些涉及单个操作,例如读取或写入数据。例如,简单的读取查询或更新查询可以是事务的一部分。
-
复杂交易:
- 这些涉及多种操作,包括读取、更新、插入和删除。复杂的交易可以是旨在完成业务流程的一系列操作,例如将资金从一个帐户转移到另一个帐户,其中涉及检查帐户余额、从一个帐户中扣除以及向另一个帐户中添加。
交易生命周期
典型的交易遵循以下步骤:
-
开始交易:
-
执行操作:
- 事务执行一系列数据库操作,例如选择、插入、更新或删除记录。这些操作作为事务的一部分按顺序执行。
-
提交交易:
- 所有操作完成后,事务提交。这意味着事务所做的所有更改都将永久保存到数据库中。一旦提交,事务就无法回滚。
-
回滚事务:
- 如果事务执行过程中出现错误或失败(例如违反约束),事务将被回滚。这意味着事务所做的所有更改都将被撤消,并且数据库将返回到其原始状态。
-
结束交易:
- 提交或回滚后,事务结束。这意味着交易的生命周期已经完成。
交易状态
交易在其生命周期内必须处于以下状态之一:
-
活跃:
- 交易的初始状态。事务在执行和执行操作时保持此状态。
-
部分承诺:
- 此状态发生在事务的最终语句执行之后。至此,事务已完成所有操作,但尚未提交到数据库。
-
失败:
- 当发现交易无法正常执行时,它会进入失败状态,通常是由于约束冲突或系统故障等问题。
-
已中止:
- 事务回滚并且数据库恢复到事务开始之前的状态后,进入中止状态。
-
承诺:
- 事务在所有操作成功完成后进入已提交状态,并且其更改已永久应用到数据库。
-
终止:
- 如果事务已提交或中止,则该事务被视为已终止。一旦事务达到已提交或已中止状态,就无法重新启动。
DBMS 中的时间表
schedule 是按特定顺序执行的多个事务的操作序列。调度决定了多个事务的读写操作的执行顺序。主要目标是确定并发事务如何交互并确保数据库保持一致状态。
时间表可以是连续或非连续。
时间表的类型
-
连续剧时间表:
- 串行计划是一种交易一个接着一个执行而没有任何交错的计划。这意味着一个事务的操作在下一个事务开始之前完全完成。
- 在串行调度中,没有并发性,调度相当于顺序执行事务。
串行时间表示例:
- 交易1:T1:读取(A);写(A);犯罪
- 交易2:T2:读(B);写(B);犯罪
- 交易是依次执行的,操作上没有重叠。
-
非连续剧时间表:
- 非串行调度允许多个事务的操作交错。在这种类型的调度中,事务是并发执行的,这意味着它们的操作混合在一起。
- 非串行调度可能会导致不同的结果,具体取决于操作执行的顺序,这需要仔细管理以维护 ACID 属性。
非连续剧时间表示例:
- T1:阅读(A);写(A);
- T2:读取(B);写(B);
- T1:提交;
- T2:提交;
- 事务T1和T2的操作是交错的。
可串行化
可串行性是确保并发执行多个事务的结果与串行执行事务(一个接一个)的结果相同的概念。如果一个调度在对数据库的影响方面等同于串行调度,则该调度被称为可序列化。
重要性:
可串行化的目标是确保并发事务不会导致冲突或异常(例如脏读、丢失更新等)并且数据库保持一致的状态。
可序列化有两种主要类型:
-
可串行化冲突:
- 如果一个调度可以通过交换非冲突操作转换为串行调度,那么它就是冲突可串行化的。如果两个操作来自不同的事务,访问相同的数据项,并且其中至少一个是写操作,则认为它们是冲突的。
-
冲突操作:
- 当一个事务读取另一事务写入的数据项时,会发生读写冲突。
- 当两个事务都写入同一个数据项时,就会发生写-写冲突。
可串行化冲突示例:
- 时间表:
- T1:写入(A);
- T2:写入(B);
- T1:读取(B);
- T2:阅读(A);
- 此调度可以重新排列为串行调度,并且可以冲突串行化。
-
查看可序列化性:
- 如果调度在最终结果方面等同于串行调度,即发生相同的读取和写入,并且事务被正确序列化,则该调度是视图可串行化的。然而,在视图可序列化性中,操作不一定必须像冲突可序列化性中那样交换。
冲突等价、冲突可序列化
-
等效冲突:
- 如果两个调度包含相同的操作,操作的顺序相同,并且保留冲突操作的顺序,则称它们是冲突等效。这意味着两个计划中的事务交错会产生相同的结果,确保它们是冲突等价的。
-
可序列化冲突:
- 如果一个调度表的事务可以重新排列以形成一个串行调度表,从而保持冲突顺序,那么该调度表就是冲突可序列化的。简单来说,如果事务可以在不违反数据一致性的情况下序列化,即保持冲突操作的顺序,则调度是冲突可序列化的。
冲突可串行性和冲突等效计划示例
让我们考虑以下交易及其操作:
- 交易T1:读取(A);写(A);犯罪
- 交易T2:读取(A);写(A);犯罪
时间表1:
T1: Read(A);
T2: Read(A);
T1: Write(A);
T2: Write(A);
T1: Commit;
T2: Commit;
登录后复制
时间表2:
T2: Read(A);
T1: Read(A);
T2: Write(A);
T1: Write(A);
T1: Commit;
T2: Commit;
登录后复制
冲突可串行性检查:
-
时间表 1 和 时间表 2 是 冲突等效,因为冲突操作(在 A 上读/写)的顺序保持不变。两个调度都可以转换为串行调度:
- T1:阅读(A);写(A);承诺;
- T2:阅读(A);写(A);承诺;
- 因此,两个调度都是可序列化冲突。
事务隔离性和原子性
除了事务的基本属性(例如 ACID 属性)之外,管理事务失败是维护数据库一致性和可靠性的一个重要方面。当事务并发执行时,数据库必须确保事务期间的任何失败都不会破坏数据库状态,并保持数据库的原子性和一致性。本节讨论故障处理如何影响 DBMS 中的事务隔离和原子性。
原子性和故障处理
原子性 是事务的属性,可确保将其视为单个不可分割的工作单元。这意味着事务要么完全完成,要么根本不执行。如果事务的任何部分失败,则必须回滚整个事务,以确保不会将部分更改应用于数据库。
当事务失败时,它对数据库可能产生的任何影响都必须撤消,以便数据库可以返回到事务开始之前的状态。在允许并发执行事务的系统中,如果事务 T1 失败,则所有依赖于 T1 的事务(即,读取或写入受 T1 影响的数据的任何事务 T2)也必须中止,以保留数据库的原子性。如果一个事务依赖于另一个失败的事务,它不应该留下部分更改或不一致的数据。
为了防止事务执行期间出现不一致,管理规定事务操作顺序的时间表至关重要,尤其是当事务失败时。需要限制某些类型的计划,以确保能够正确管理故障。
可恢复的时间表
可恢复调度 是一种事务 T2 仅在其依赖的事务 T1 提交后才提交的调度。简单来说,如果事务 T2 读取事务 T1 写入的数据,则 T1 必须在 T2 之前提交。这确保 T2 不会基于未提交的事务提交更改,从而防止回滚 T1 也需要回滚 T2 的情况。
违反此规则的计划称为不可恢复的计划。例如,如果T2在读取T1写入的数据后提交,但T1失败并回滚,则数据库无法恢复到一致状态,因为T2的提交依赖于T1未提交的更改。在这种情况下,T2 就无法撤销,从而导致数据不一致。
不可恢复的时间表示例:
在不可恢复的时间表中,假设:
- 事务 T6 将数据写入项目 A。
- 事务T7读取T6写入的A的值并立即提交。
如果 T6 在提交之前失败,T7 依赖于 T6 所做的未提交的更改。由于T7已经提交,我们无法回滚T7,从而导致无法从T6的故障中恢复的情况。这会导致不可恢复的时间表。
为了使调度可恢复,T7 应该将其提交延迟到 T6 提交之后,确保 T7 不依赖于 T6 中未提交的数据。
无级联时间表
即使计划是可恢复的,它仍然可能导致事务失败恢复期间出现问题,特别是在发生级联回滚时。 级联回滚是指一个事务的失败导致其他依赖事务回滚的连锁反应。当事务读取另一个未提交事务写入的数据时,就会发生这种情况。
例如,考虑以下场景:
- 事务 T8 将值写入数据项 A,该值由事务 T9 读取。
- 事务 T9 将一个值写入 A,然后由事务 T10 读取该值。
- 如果T8失败,依赖于T8的T9也需要回滚。 T10依赖于T9,也必须回滚。这会产生级联回滚效应,不必要地撤消多个事务的工作。
级联回滚是不可取的,因为它们会导致大量工作的撤销,即使只有一个事务失败。为了防止级联回滚,我们可以使用级联调度。无级联调度是一种事务不读取尚未提交的事务写入的数据的调度。
形式上,级联调度是这样一种调度,对于任意两个事务T1和T2,如果T2读取T1写入的数据项,则T1必须在T2读取数据项之前提交。这确保没有事务可以依赖于未提交的数据,并且不会出现级联回滚。
每个无级联调度也是一个可恢复调度。这意味着无级联调度不仅可以防止级联回滚,还可以确保数据库在事务失败时能够正确恢复。
无级联调度示例:
考虑以下几点:
- 事务T8写入A,T9读取A。
- 在级联调度中,T9 只能在 T8 提交后读取 A。
这保证了除非 T8 成功完成,否则 T9 不会从 T8 读取数据,从而确保在 T8 失败时不需要回滚 T9。
事务隔离级别
事务隔离确保并发事务不会以违反数据库一致性的方式相互干扰。事务的隔离级别定义了该事务与其他事务隔离的程度。
有不同的隔离级别,范围从低到高隔离:
-
未提交的读取:
- 此隔离级别允许事务读取其他未提交事务写入的数据。此级别可能会导致诸如脏读之类的问题,其中事务读取稍后可能回滚的数据,从而导致结果不一致。
-
已提交读:
- 该级别的事务只能读取其他事务已提交的数据。虽然这可以防止脏读,但它仍然可能导致不可重复读取,其中一个事务读取相同的数据两次并获得不同的结果,因为另一个事务同时修改了数据。
-
可重复阅读:
- 此级别可防止脏读和不可重复读。但是,它仍然允许幻读,如果其他事务插入或删除记录,事务可能会遇到不同的行集。
-
可序列化:
- 这是最高的隔离级别。它确保并发事务的结果与事务一个接一个地串行执行的结果相同。这可以防止脏读、不可重复读和幻读,但由于其严格的性质,可能会对性能产生影响。
为数据库或事务选择的隔离级别会影响数据一致性和性能之间的平衡,较高的隔离级别通常会因并发限制较大而导致性能变慢。
DBMS 中的并发控制
并发控制是数据库管理系统(DBMS)的一个关键方面,它确保正确的事务执行,同时允许多个事务同时执行。并发控制的目标是在面对事务交错和故障场景时保持数据库的一致性和完整性。本节涵盖基于锁的协议,包括各种锁定模式、两阶段锁定协议、死锁处理机制和基于时间戳的协议.
基于锁的协议
锁定是 DBMS 中用于防止并发执行事务之间发生冲突的基本机制。锁应用于数据项以控制访问,确保多个事务不会违反数据库的完整性。在基于锁的并发控制中,事务在对数据项执行操作之前获取锁,并在操作完成后释放锁。
锁的类型
有多种模式可以锁定数据项。在本节中,我们将注意力限制在两种基本模式上:
-
共享锁(S):
- 当事务只需要读取数据项时,它会持有该数据项的共享锁。
- 多个事务可以同时持有同一个数据项的共享锁。这允许不同事务并发读取数据项。
-
共享锁不允许事务修改数据项。
示例:
- 事务 T1 持有项目 A 的共享锁并读取 A。
- 事务 T2 持有项目 A 的共享锁并读取 A。
- 两者都可以读取数据,但不能写入A。
-
独占锁(X):
- 当事务需要读取和写入数据项时,它会持有该数据项的排他锁。
- 任何时候只有一个事务可以持有特定数据项的排他锁。如果一个事务拥有排它锁,则其他事务无法获取同一数据项的共享锁或排它锁。
示例:
- 事务 T1 持有项目 A 的独占锁并对其进行写入。
- 当 T1 持有 A 上的独占锁时,没有其他事务可以读取或写入 A。
授予锁
锁是根据系统遵循的协议来授予的,不同的基于锁的协议可以控制在事务执行期间如何请求和授予锁。这些协议有助于避免冲突,例如更新丢失、临时不一致以及其他事务访问未提交的数据。
两阶段锁定协议 (2PL)
两阶段锁定协议是一种广泛使用的协议,以确保可序列化性 - 一种保证事务执行方式的属性,其结果相当于在之后执行它们另一个(连续的)。两阶段锁定通过在事务执行期间强制执行两个阶段来确保可串行化:
-
成长阶段:
- 在此阶段,事务可以获取锁,但不能释放任何锁。
- 事务可以请求任意数量的锁,但是一旦释放锁,就无法获取任何新的锁。当执行第一次解锁操作时,此阶段结束。
-
收缩阶段:
- 在此阶段,事务可以释放锁,但无法获取任何新锁。
- 一旦事务开始释放锁,它就无法锁定任何其他数据项。当事务提交或中止时,此阶段结束。
两相锁定协议保证可串行化,因为它可以防止锁定图中的循环,确保执行顺序遵循严格的可序列化顺序。
示例:
- 事务T1和T2需要更新数据项A和B。使用2PL,两个事务都会在增长阶段获取必要的锁,并在完成操作后(收缩阶段)释放它们。
死锁处理
死锁 当两个或多个事务正在等待对方释放锁,导致其中一个都无法继续进行的情况时,就会发生。这会造成等待事务的循环,除非回滚一个或多个事务,否则无法解决该循环。
预防死锁
死锁预防技术旨在通过对事务行为进行限制来避免死锁的发生。防止死锁的一种常见策略是使用时间戳来确定事务的优先级。
基于时间戳的死锁预防方案
有两种使用时间戳的著名死锁预防方案:
-
等待死亡计划:
- 这是一种非抢占式死锁预防技术。
- 如果事务 Ti 请求 Tj 持有的数据项,则仅当其时间戳小于 Tj 的时间戳(即 Ti 早于 Tj)时才允许 Ti 等待。
- 如果 Ti 的时间戳大于 Tj,则 Ti 会回滚(即 Ti“死亡”)。
示例:
- 如果事务 T14、T15 和 T16 的时间戳分别为 5、10 和 15:
- 如果 T14 请求 T15 持有的数据项,T14 将等待,因为 T14 较旧。
- 如果T16请求T15持有的数据项,T16将被回滚,因为T16比T15年轻。
-
伤口等待计划:
- 这是一种抢占式死锁预防技术。
- 如果事务 Ti 请求 Tj 持有的数据项,则仅当其时间戳大于 Tj 的时间戳(即 Ti 比 Tj 年轻)时才允许 Ti 等待。
- 如果Ti的时间戳小于Tj的时间戳,Ti抢占Tj,并且Tj被回滚(即Tj被Ti“伤害”)。
示例:
- 如果事务 T14、T15 和 T16 的时间戳分别为 5、10 和 15:
- 如果T14请求T15持有的数据项,T14将抢占T15,导致T15回滚。
- 如果T16请求T15持有的数据项,T16将等待,因为它比T15年轻。
基于时间戳的协议
除了基于锁的协议之外,基于时间戳的协议还管理数据库中的并发性。这些协议使用时间戳来排序事务并解决冲突,确保系统的行为就像事务是串行执行的一样。
时间戳及其作用
时间戳是创建交易时分配给交易的数值。交易的时间戳决定了其优先级——较低的时间戳值表示较旧的交易,较高的值表示较新的交易。
-
W 时间戳(Q):
- 这表示已成功对数据项 Q 执行写入操作的任何事务的最大时间戳。
- 它有助于识别修改数据项的最后一笔交易。
-
R-时间戳(Q):
- 这表示已成功对数据项 Q 执行读取操作的任何事务的最大时间戳。
- 它有助于识别读取数据项的最后一个事务。
时间戳排序协议
时间戳排序协议通过根据时间戳对事务执行总排序来确保可序列化。该协议要求:
- 如果一个事务 Ti 写入数据项 Q,而另一个事务 Tj 读取或写入 Q,则 Ti 的时间戳必须小于 Tj。
- 类似地,如果 Ti 读取数据项 Q,Tj 写入 Q,则 Ti 的时间戳必须小于 Tj。
该协议根据事务的时间戳而不是锁来解决冲突。
示例:
- 时间戳为 10 的事务 T1 写入数据项 A。
- 时间戳为 12 的事务 T2 读取数据项 A。
- 时间戳排序协议确保 T1 的写入操作发生在 T2 的读取操作之前,从而保持正确的事务顺序。
以上是事务和并发控制:DBMS的详细内容。更多信息请关注PHP中文网其他相关文章!