两阶段锁(Two-Phase Locking,简称2PL)是数据库管理系统中的一项关键技术,旨在确保并发事务期间的一致性和隔离性。在本文中,我们将探讨2PL的三种主要类型:严格2PL(Strict 2PL)、强严格2PL(Rigorous 2PL)和保守2PL(Conservative 2PL),并重点介绍它们在加锁协议上的差异。
主要分为以下三类:
- 严格两阶段锁 (Strict 2-PL)
- 强严格两阶段锁 (Rigorous 2-PL)
- 保守两阶段锁 (Conservative 2-PL)
严格两阶段锁协议 (Strict 2PL)
严格两阶段锁协议是两阶段锁(2PL)协议的一种变体,它增加了一项关于释放排他锁(X锁)的特定规则。除了遵循两个阶段(增长阶段和缩减阶段)外,严格2PL还确保:
- 所有排他锁(X锁)必须持有到事务提交或中止为止:
- 事务可以在增长阶段获取锁。
- 在完成操作并提交或中止之前,它不能释放任何排他锁。
- 这条规则防止其他事务访问正在进行的未提交事务所修改的数据,从而确保了数据一致性。
- 优势:
- 可恢复调度: 确保如果事务失败,没有其他事务依赖于其未提交的更改。
- 无级联调度: 防止级联回滚,因为其他事务无法读取未提交的数据。
!<a href="https://media.geeksforgeeks.org/wp-content/uploads/20250107170043924767/strict2pl.webp">strict2plStrict 2PL
上图展示了一个包含两个事务T1和T2的严格两阶段锁(2PL)协议场景,它们正在访问和修改两个数据库账户A和B。让我们分步来解释:
初始数据库状态:
- 账户 A=1000
- 账户 B=1000
调度中的步骤:
#### 事务 T1:
- BEGIN:T1开始并获取A上的排他锁(X-Lock)。这意味着T1可以读取和修改A,且在此期间其他事务无法访问A。
- T1读取A并从中减去100(A=900)。
- T1获取B上的X-Lock,并向B增加100(B=1100)。
- T1只有在提交后才解锁A和B。此时,T1所做的更改才对其他事务可见。
#### 事务 T2:
- BEGIN:T2开始并尝试获取A上的共享锁(S-Lock)以读取其值。
- T2等待,因为T1正持有A上的排他锁。这防止了T2读取未提交的数据,从而确保没有脏读。
- 一旦T1提交并释放了A上的锁,T2就获取A上的S-Lock,读取A=900并继续执行。
- T2获取B上的S-Lock,读取B=1100并计算A+B=2000。
- T2在提交后解锁A和B。
最终输出:
- T1提交后,T2读取A和B的更新值,结果A+B=2000反映了已提交的更改。
强严格两阶段锁 (Rigorous 2PL)
强严格2PL是两阶段锁(2PL)协议的一个更严格的版本。在该协议中:
- 事务持有的所有锁(包括共享锁和排他锁)都必须保持到事务提交或中止为止。
- 只有在事务完成后才释放锁,确保在第一个事务完全完成之前,没有其他事务可以访问被锁定的数据。
这意味着,在当前事务提交或回滚之前,没有其他事务可以读取或写入它正在使用的数据。这确保了数据一致性,并避免了脏读或级联回滚等问题。
!dbms2事务执行序列
上述事务的解释如下:
T1 (左列)
- lock-exclusive(A) → T1对数据项A施加排他锁。
- lock-exclusive(B) → T1也对B施加排他锁。
- 读取并修改A:read(A) → A = A + 50 → write(A)。
- 读取并修改B:read(B) → B = B + 100 → write(B)。
- 在释放任何锁之前执行commit。
- unlock(A)和unlock(B)在commit之后发生。
遵循强严格2PL: 在事务提交之前不释放任何锁。
T2 (右列)
- lock-exclusive(A) → T1对A施加排他锁。
- 读取并修改A:read(A) → A = A + 50 → write(A)。
- 执行commit。
- unlock(A)在commit之后完成。
遵循强严格2PL: 即使是单个项目,锁也会保持到提交为止。
因此,这使我们免于级联中止——而在基本2PL中这仍然是一个问题——此外,它保证了严格调度,但仍然可能发生死锁!
> 注意: 严格2PL和强严格2PL的区别在于,强严格2PL更具限制性,它要求排他锁和共享锁都必须保持到事务提交之后,这也使得强严格2PL的实现变得更容易。
保守两阶段锁 (Conservative 2PL)
保守2PL是两阶段锁协议的一种变体,旨在防止…