数据库封锁协议是数据库管理系统实现事务并发控制的核心机制。封锁指的是事务在对某个数据对象(如表、记录、字段)进行操作之前,先向系统发出请求对该数据对象加锁,限制其他事务的访问权限。封锁协议则是一组规则,规定了事务何时申请锁、持有多久、何时释放,目标是在多事务并发环境下保证数据库的一致性不被破坏。
在软考数据库系统工程师考试大纲中,封锁协议属于"事务管理"章节,与ACID特性中的隔离性直接对应。教材将封锁协议划分为三个递进级别——一级、二级和三级封锁协议,再加上两段锁协议作为保证可串行化的理论框架。这四个概念构成了并发控制中封锁机制的知识主干,在历年真题中反复出现。
封锁协议之所以成为高频考点,在于它不仅考查概念记忆,更考查给定并发场景下的判断能力。典型考题会给出两个事务的并发执行时间序列,描述每个事务在何时对哪个数据项申请了什么锁、何时释放,要求判断该序列符合几级封锁协议、是否会产生数据不一致、调度是否可串行化。这要求考生同时掌握封锁协议定义、三种数据不一致成因和可串行化判定方法,是典型的知识交叉考查。
要理解封锁协议为什么能防范并发异常,首先要理解冲突操作的物理本质。在数据库中,两个操作被称为冲突操作,当且仅当它们满足三个条件:它们属于不同事务、它们操作同一数据项、其中至少有一个是写操作。对同一数据项的两个读操作不冲突(可以并行执行),但一个读和一个写冲突、两个写也冲突。并发控制的根本任务就是对这些冲突操作进行排序,确保它们的执行效果等价于某个串行执行的次序。
封锁协议通过互斥锁机制实现排序。排它锁(写锁/X锁)是最强力的锁类型:事务加X锁后可读写该数据项,其他事务不能加任何锁。共享锁(读锁/S锁)允许多个事务同时持有并读取,但这些事务不能写。S锁与X锁互斥——有S锁不能加X锁,有X锁不能加S锁。多个S锁之间兼容。
锁兼容矩阵决定了封锁协议能防范哪类异常:S与S兼容,S与X不兼容,X与S不兼容,X与X不兼容。读不加S锁→别人可同时加X锁修改→数据不一致;写不加X锁→别人可同时修改→修改被覆盖。封锁协议的本质是在冲突操作间建立等待关系,用时间换一致性。
一级封锁协议的规则最为基础,只有一条:事务在修改数据之前必须先对该数据加排它锁,并且这个排它锁必须一直持有到事务结束时才释放。这条规则的高明之处在于它用排它锁的互斥性保护了写操作的原子性——从加排它锁到事务提交的整个区间,没有其他事务能够对该数据项进行任何操作。这就从根本上解决了丢失修改问题,因为丢失修改的成因恰是两个事务对同一数据项的写操作发生了覆盖。但一级封锁协议对读操作没有任何约束,事务可以在不加任何锁的情况下读取数据。这意味着如果一个事务在读到某个数据值之后,另一个事务对该数据进行了修改并提交,前一个事务基于已过时的数据值所做的后续操作就可能产生错误。此外,因为没有对读操作加锁,事务可能读取到其他未提交事务正在修改的中间状态数据,这就是读脏数据。
二级封锁协议在一级的基础上扩展了对读操作的控制:事务在读取数据之前必须先对该数据加共享锁,但共享锁可以在读取操作完成后立即释放,不必等到事务结束。这个改动看似简单,但它的效果是显著的——当事务A对某数据项加共享锁进行读取时,事务B如果想修改该数据,必须申请排它锁,而排它锁与共享锁不兼容,所以事务B必须等待。这样,在事务A持有共享锁的期间内,数据不会被其他事务修改,从而避免了不可重复读。不过要注意,二级封锁协议的"读完即放"规则意味着如果事务A需要多次读取同一个数据项,两次读取之间共享锁如果已经释放,间隔期内仍然可能被其他事务修改。因此二级封锁协议对不可重复读的防范是不完全的,尤其是在一个事务需要多次访问同一数据并基于之前的读取值做判断的场景下。
三级封锁协议在二级的基础上将共享锁的持有规则进一步收紧:事务在读取数据前必须加共享锁,并且共享锁必须一直持有到事务结束时才释放。这个改动彻底堵住了二级封锁协议留下的窗口——既然共享锁在整个事务生命周期内都不释放,那么从第一次读取到最后一次读取之间的任何时刻,其他事务都无法修改该数据。同时,因为排它锁也必须持有到事务结束,而共享锁和排它锁互斥,这意味着一个事务要么全程持有共享锁在读数据期间排除写操作,要么全程持有排它锁在写数据期间排除读写操作,两种锁互斥形成了对所有冲突操作的全覆盖。三级封锁协议因此能够同时防范丢失修改、不可重复读和读脏数据三种并发异常。
锁持有时间越长,系统并发度越低。三级封锁协议虽提供最强隔离性,但要求S锁持有到事务结束,显著降低吞吐量。实际数据库产品(如MySQL InnoDB)采用MVCC多版本并发控制在隔离性和并发度之间取得平衡,虽然不在软考范围内,但理解这个权衡有助于建立完整认知。
两段锁协议则是从另一个理论层面来审视并发控制。如果说三级封锁协议回答的是"这个调度能不能防止数据不一致",那么两段锁协议回答的是"这个调度是否等价于某个串行调度"。两段锁协议的规则不关心具体使用S锁还是X锁,也不关心锁持有到何时释放,它只关心锁的申请操作和释放操作在时间线上的排列方式。按照两段锁协议,每个事务的执行过程必须明确地划分为两个不相交的阶段:在增长阶段,事务只能申请锁,不能释放任何锁;在缩减阶段,事务只能释放锁,不能申请任何新的锁。一旦事务释放了第一个锁,它就进入了缩减阶段,此后再也不能申请任何锁。
两段锁协议与可串行化的关系是数据库理论中的一个重要定理:如果一个并发调度中的所有事务都遵守两段锁协议,那么该调度一定是可串行化的。这是充分条件而非必要条件,即存在不遵守两段锁协议但调度仍然是可串行化的情况。在软考中,这一定理被频繁考查,命题人通常给出一组事务的锁操作序列,要求考生判断该调度是否遵守两段锁协议,以及是否可串行化。答这类题的关键技巧是画出每个事务的锁申请和释放时间线,检查是否存在释放锁之后又申请锁的情况。如果存在交叉,就违反了两段锁协议。
理解三种封锁协议之间的递进关系是答题的基础。用最简洁的语言概括就是:一级封锁协议只管写不管读,二级封锁协议管写也管读但读完就放,三级封锁协议管写也管读而且读锁也要持有到底。这个递进关系带来的防范能力进阶是:一级能防丢失修改但不能防不可重复读和读脏数据;二级能防丢失修改和不可重复读但不能防读脏数据;三级三种异常全部能防。
将封锁协议与SQL标准中的事务隔离级别做对照是另一个常考角度。SQL标准定义了四种隔离级别,从低到高依次为READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ和SERIALIZABLE。一级封锁协议大致对应READ UNCOMMITTED的写锁策略,但实际上SQL标准中READ UNCOMMITTED连读锁都不加,比一级封锁协议还要宽松。二级封锁协议的策略(写锁持有到事务结束,读锁读完即放)在逻辑上对应READ COMMITTED隔离级别——因为读锁在读完后就释放了,两次读取之间其他已提交事务的修改是可见的。三级封锁协议要求读锁持有到事务结束,这在逻辑上对应REPEATABLE READ隔离级别——事务执行期间读到的数据是可重复的。两段锁协议则是实现SERIALIZABLE的典型手段,但SERIALIZABLE的语义(完全隔离,并发执行效果等价于串行执行)比单纯遵守两段锁协议要更强,因为两段锁协议只保证可串行化,并不保证不出现幻读等现象。
封锁粒度是另一个可能被混淆的相关概念。封锁协议规定的是申请锁和释放锁的规则,而封锁粒度规定的是锁的作用范围——表级锁锁定整张表,页级锁锁定一个物理存储页,行级锁锁定特定行,字段级锁锁定特定列。同一封锁协议可以在不同粒度级别上实施,但锁的兼容性规则不变。在软考的语境下,封锁协议的考点通常不涉及封锁粒度和意向锁等更高级的概念,但偶尔会出现混搭考查的情况,需要考生能够区分这两个维度的独立性。
还需注意封锁协议与死锁处理机制的区别。封锁协议本身并不解决死锁问题,恰恰相反,封锁协议将锁的持有时间拉长(尤其是三级封锁协议),反而会增加死锁发生的概率。死锁是两个或多个事务互相等待对方释放锁形成的
本篇完!