2PL(两阶段锁定)算法是如何工作的
介绍
2PL(两阶段锁定)算法是关系型数据库系统用来保证数据完整性的最古老的并发控制机制之一。
在本文中,我将解释2PL算法是如何工作的以及如何以任何编程语言实现它。
锁类型
在我们开始讨论2PL算法实现之前,解释一下读锁和写锁的工作方式非常重要。
读锁或共享锁可防止在读的同时资源的写操作,但同时允许并发的读操作。
写锁或排他锁不允许对给定资源进行读操作和写操作。
相容性矩阵 | 读锁 | 写锁 |
---|---|---|
读锁 | 允许 | 不允许 |
写锁 | 不允许 | 不允许 |
某些数据库系统(例如PostgreSQL,MySQL或SQL Server)提供了获取给定元组或范围元组上的读锁和写锁的可能性。其他数据库系统(例如Oracle)仅允许通过FOR UPDATE
子句获取写锁/独占锁。
Database name | Read lock clause | Write lock clause |
---|---|---|
Oracle | FOR UPDATE | FOR UPDATE |
SQL Server | WITH (HOLDLOCK, ROWLOCK) | WITH (HOLDLOCK, UPDLOCK, ROWLOCK) |
PostgreSQL | FOR SHARE | FOR UPDATE |
MySQL | LOCK IN SHARE MODE | FOR UPDATE |
但是,读和写锁不仅限于数据库系统。传统上,进入Java的synchronized
块允许获取排他锁,从1.5版开始,Java允许通过ReentrantReadWriteLock
对象获取读写锁。
两阶段锁定
仅靠锁是不足以防止冲突的。并发控制策略必须定义如何获取和释放锁,因为这也会影响事务的交织。
为此,2PL协议定义了一种锁定管理策略,以确保严格的可串行化。
2PL协议将事务分为两部分:
- 扩展阶段(获取锁,并且不允许释放锁)
- 收缩阶段(释放所有锁,并且无法进一步获取其他锁)。
对于数据库事务,扩展阶段意味着允许从事务开始到结束为止获取锁,而收缩阶段由提交或回滚阶段表示,就像在事务结束时一样,所有已获取的锁锁被释放。
下图显示了2PL如何协调事务:
- 爱丽丝(Alice)和鲍勃(Bob)都通过PostgreSQL的
SELECT FOR SHARE
子句获得了对给定记录的读锁。 - 当Bob尝试对
post
记录执行UPDATE语句时,其语句被锁管理器阻塞,因为UPDATE语句需要在该post
行上获取写锁,而Alice仍对该数据库记录持有读锁。 - 只有在Alice的事务结束并且释放了所有锁之后,Bob的UPDATE操作才会恢复。
- Bob的UPDATE语句将会发生锁升级,因此他先前获取的读锁将被排他锁替换,这将防止其他事务获取同一
post
记录上的读取或写锁。 - 爱丽丝启动了一个新事务,并针对同一
post
记录发出了SELECT FOR SHARE
请求的查询,该查询会首先尝试获取读锁,但是由于鲍勃对该记录拥有排他锁,因此该语句被锁管理器阻塞。 - 提交Bob的事务后,将释放他的所有锁,并且可以恢复Alice的SELECT查询。
严格的可串行化
2PL算法提供严格的可串行化,这是涉及数据完整性的黄金标准。严格的可串行化意味着结果既可串行化又可线性化。
如果两个或多个事务的关联读取和写入操作以某种结果等效于某种串行执行的方式交错,则它们是可序列化的。例如,如果我们有两个事务A和B,只要结果是A,B或B,A,则这两个事务都是可序列化的。对于N个事务,结果必须等于N!
事务排列之一。
但是,可串行性未考虑时间流。另一方面,线性化意味着基于时间的排序。例如,如果任何后续读取将反映先前写入操作所做的更改,则系统是可线性化的。
结论
2PL(两阶段锁定)算法于1976年发表在[数据库系统中的一致性和谓词锁定的概念](http://research.microsoft.com/zh-cn/um/people/gray/papers/在Kapali Eswaran和Jim Gray(等)撰写的%20the%20Notions%20of%20Consistency%20and%20Predicate%20Locks%20in%20a%20Database%20System%20CACM.pdf)论文中,该论文证明,如果所有事务使用2PL算法,则这些事务都是串行执行的。
最初,所有数据库系统都采用2PL来实现可串行化事务,但是随着时间的流逝,许多供应商已转向MVCC(多版本并发控制)并发控制机制。
如今,除非未在数据库级别设置了READ_COMMITTED_SNAPSHOT 或ALLOW_SNAPSHOT_ISOLATIONMVCC模式,否则默认情况下,只有SQL Server默认使用2PL算法。即使MySQL的InnoDB 存储引擎是基于MVCC的,在切换到可串行化隔离级别时,数据库也将使用2PL算法,因为将在读取和写入操作上都需要获取锁。
因此,了解2PL算法的工作原理并确保严格的可串行化非常重要。