一、粒度之困行级锁的双面性上一篇我们建立了基于锁的并发控制框架——共享锁与排他锁的兼容矩阵两阶段锁协议的可串行化保证以及死锁的检测与预防。在那套框架中我们隐式地假设锁作用于单个数据项——一行记录。行级锁是最细粒度的锁策略它允许多个事务同时操作同一表中的不同行并发度最高。然而行级锁并非没有代价。当一个事务需要扫描整张表——例如执行UPDATE 订单 SET 状态 已归档 WHERE 创建日期 2020-01-01——它需要对满足条件的每一行加锁。如果条件匹配数百万行这个事务将持有数百万个行级锁。锁表本身在内存中占据的空间急剧膨胀每次加锁操作都需要在锁表中插入一条记录并检查冲突CPU开销与锁数量成线性增长。另一个极端是表级锁。事务在执行上述更新时可以直接锁定整张订单表一条锁记录即覆盖全部行。锁管理开销极低——一条锁的获取与释放。但代价也是明显的在表级锁持有期间其他事务对该表的任何读写——即使是不相关的行——都被阻塞。并发度从行级的“各行独立”骤降为表级的“单行排队”。行级锁与表级锁之间的这种张力揭示了锁机制中一个根本性的权衡锁粒度越细并发度越高但锁管理开销也越大锁粒度越粗管理开销越低但并发度也越低。单一的锁粒度无法在所有场景下都表现良好——需要一种允许不同粒度锁共存、并能高效判定不同粒度间锁冲突的机制。这正是多粒度封锁与意向锁的出场背景。二、多粒度层次结构从表到行的锁树多粒度封锁将数据库对象组织为一个层次结构。这个层次结构的根节点是整个数据库中间节点是表和页如果数据库系统使用页存储叶子节点是行。每个节点都可以被独立地加锁——事务可以选择对整个数据库加锁、对某张表加锁、对某个页加锁、或对某一行加锁。当事务T对某个节点加锁时它隐式地对节点的所有后代施加了同类型锁。表上的排他锁意味着该表内所有页和所有行都被排他锁定——因为其他事务无法在不获取锁的情况下访问这些行。这种隐式锁定的传递性是多粒度封锁的核心特征祖先节点的锁覆盖了所有后代节点。这一传递性带来了锁冲突判定的效率挑战。假定事务T₁已经对表R中的某一行r₁加了排他锁。事务T₂现在试图对整张表R加排他锁。T₂的锁请求是否应当被批准在一个朴素的实现中——系统只记录各事务持有哪些锁而不记录锁之间的层次关系——锁管理器需要在表R的所有后代节点中逐一检查是否存在冲突锁。这意味着从表的根向下遍历整个子树检查每一页上的每一个行锁——其复杂度等同于遍历整张表的全部行。这恰恰抵消了使用表级锁所期望获得的管理开销优势。问题的根源在于锁管理器在判定表级锁请求时需要知道“这张表的内部是否已经存在任何不兼容的细粒度锁”。没有一种高效的机制来回答这个查询多粒度锁的粒度灵活性就无法转化为实际的性能收益。三、意向锁在祖先节点上标记意图意向锁的设计正是为了以最小的开销回答上一节提出的问题。其核心思想是当事务在某节点上获取锁之前它必须先在从根到该节点的路径上所有祖先节点上获取一种特殊的锁——意向锁。意向锁不阻塞对数据本身的访问它只标记“此节点下方的某处存在更细粒度的锁”。意向锁有三种类型分别表达不同的意图。意向共享锁IS锁表达的是事务意图在子树中的某个节点上获取共享锁。IS锁本身与S锁相容——多个事务可以同时对同一张表持有IS锁表示它们各自打算读取表中的某些行。IS锁与X锁冲突——如果某个事务已经持有了表的排他锁其他事务不能再对该表的任何部分加共享锁。意向排他锁IX锁表达的是事务意图在子树中的某个节点上获取排他锁。IX锁与IS锁和IX锁相容——多个事务可以同时对同一张表持有IX锁表示它们各自打算修改表中的某些不同行。IX锁与S锁冲突——如果有事务正在读取整张表持有表级S锁其他事务不能修改表中的任何行。IX锁与X锁冲突——同IS锁一样。共享意向排他锁SIX锁是一种复合锁——它同时表达对整张表的共享读取意图以及对其中某些行的排他修改意图。SIX锁与S锁相容两个事务可以同时读取整张表与IS锁相容一个事务读取整张表并修改部分行时另一个事务可以读取部分行但与IX、SIX和X锁冲突。这套意向锁体系的核心价值在于它将“子树内是否存在某种锁”的信息压缩到了祖先节点的单个锁标志位上。当事务T₂试图对表R加X锁时锁管理器只需检查表R节点上是否已经存在任何不兼容的锁——包括普通的S锁和X锁也包括意向锁IS、IX和SIX。如果表R上已经存在IS锁说明有其他事务正在读取R中的某些行T₂的X锁请求被拒绝——因为X锁与IS锁冲突。系统无需遍历表R内部的任何行锁仅通过表R节点上的意向锁标志位即可做出正确的冲突判定。这一判定的时间复杂度从O(N)N为子树大小降为O(1)。四、意向锁的加锁协议意向锁引入了一套层次化的加锁协议规定了事务在获取和释放锁时必须遵循的规则。获取规则事务在获取某个节点上的S锁或X锁之前必须先从根节点到目标节点的路径上逐层获取意向锁。具体而言如果事务意图在叶子节点行上获取S锁它必须先获取根节点上的IS锁再逐层向下获取每个中间节点上的IS锁最后在叶子节点上获取S锁。如果事务意图在叶子节点上获取X锁它必须先获取根节点上的IX锁再逐层向下获取IX锁最后在叶子节点上获取X锁。如果事务意图读取整个子树例如整张表并修改其中的部分节点它需要在目标子树的根节点上获取SIX锁。释放规则锁的释放顺序与获取顺序相反——从叶子节点向根节点逐层释放。事务释放叶子节点上的S锁后可以继续持有祖先节点上的IS锁——因为IS锁不阻塞其他事务的读操作持有IS锁的代价极低。这一协议的层次化结构保证了在任何时刻锁管理器只需要检查目标节点上的锁以及其祖先节点上的锁就可以判定冲突——而无需检查目标节点的后代节点。后代节点的锁信息已经被意向锁“汇总”到了祖先节点上。五、相容矩阵的形式化将所有锁类型——S、X、IS、IX、SIX——纳入统一的相容矩阵是多粒度封锁机制的形式化基石。矩阵的行和列分别代表锁请求的类型和已持有锁的类型矩阵的元素表示两者是否相容。S锁与IS锁相容一个事务读取整张表S另一个事务读取部分行IS彼此不冲突。S锁与IX锁冲突一个事务读取整张表时另一个事务不能修改表中的任何行。S锁与SIX锁冲突SIX包含对部分行的排他修改意图与整表共享读取冲突。S锁与S锁相容多个事务可以同时读取整张表。X锁与IS、IX、SIX、S、X全部冲突一个事务排他锁定整张表时任何其他锁请求——无论读还是写、整表还是部分——都不能共存。IS锁与IX锁相容多个事务可以分别读取和修改表中的不同行。IS锁与SIX锁相容一个事务读取部分行IS另一个事务读取整表但只修改部分行SIX不冲突。IX锁与SIX锁冲突两个事务都意图修改部分行时需要确保它们修改的不是同一行——这个检查在行级别完成但在表级别IX与SIX冲突以简化判定。这个兼容矩阵是多粒度锁管理器的核心调度引擎。每当锁请求到达时锁管理器检查目标节点上已持有的所有锁类型对照相容矩阵如果全部相容则授予锁否则将请求加入等待队列。六、锁升级动态粒度的自适应调整多粒度锁机制赋予了事务选择锁粒度的灵活性——但选择权在谁手中在大多数数据库系统中锁粒度由系统自动决定而非由用户显式指定。系统的核心策略是锁升级。锁升级的动态行为可以描述为事务初始时以最细粒度行级锁获取锁锁管理器监控每个事务持有的行级锁数量。当事务在某个表上持有的行级锁数量超过预设阈值时系统自动将其在该表上的所有行级锁合并升级为一个表级锁。例如当事务在订单表上已经锁定了5000行超过阈值2000锁管理器将所有行级锁释放代之以一个表级S锁或X锁取决于行级锁的类型。锁升级的收益是双重的锁管理器的内存占用大幅缩减——从数千条行级锁记录缩减为一条表级锁记录后续的锁冲突判定也简化——从逐行检查变为单次表级检查。锁升级的代价是并发度的骤降。一旦升级为表级锁其他事务对该表的任何访问——即使不涉及被锁定的行——都可能被阻塞。锁升级是系统面对锁管理压力的一次“安全阀”操作以牺牲局部并发度为代价防止锁内存耗尽引发更严重的系统故障。对于数据库管理员和开发者而言理解锁升级机制有助于诊断特定性能问题。如果一个批处理事务因为锁升级而阻塞了大量的在线查询解决方向可能不是调整事务隔离级别而是将大事务拆分为多个小事务——每个小事务的行锁数量低于升级阈值避免触发表级锁升级。七、现实系统中的多粒度锁主流数据库系统在多粒度锁的实现上各有侧重但核心原理一致。MySQL的InnoDB引擎在行级锁之外同时支持表级意向锁。当事务对某行加S锁时InnoDB自动对包含该行的表加IS锁。当事务尝试执行LOCK TABLES ... WRITE时系统检查表上的IS/IX锁是否存在——如果存在则等待或报错。InnoDB的锁升级策略相对保守通常在内存压力极低时才触发。PostgreSQL不直接使用意向锁的术语但其锁层次结构中的AccessShareLock、RowShareLock、RowExclusiveLock等锁模式在功能上等效于IS、IX和SIX的组合。PostgreSQL的锁管理高度精细化提供了多达八种锁模式以适应不同操作的并发度需求。Oracle的设计在多粒度锁上更具前瞻性——它几乎完全依赖行级锁配合撤销段实现多版本并发控制。Oracle极少发生锁升级因为其行级锁不消耗专门的内存结构而是直接在数据块的头部标记锁信息。这一设计与MVCC的深度融合使得Oracle在行级锁的高并发场景下表现出色。八、结语层次化思维的胜利多粒度封锁与意向锁代表了数据库系统工程中一种深刻的层次化思维将复杂系统的全局状态检查通过精心设计的局部标志位意向锁来高效完成。这种“向上汇总、逐层传递”的信息压缩策略并非数据库领域独有——计算机体系结构中的TLB缓存、文件系统中的inode位图、网络路由表中的前缀聚合都体现了同样的层次化汇总思维。从更广阔的理论图景看多粒度锁是两阶段锁协议的一次实用性增强——它没有改变2PL的可串行化保证而是在锁的物理表示层面进行优化让锁机制在面对大范围操作和小范围操作混合的工作负载时不至于被自身的管理开销所压垮。下一篇我们将离开锁的世界探索并发控制中另一条完全不同的技术路径——多版本并发控制。MVCC放弃了锁的“互斥等待”逻辑转而采用“为每个写操作创建新版本”的策略让读操作永远不阻塞写操作写操作也不阻塞读操作。这一范式转变带来了并发性能的革命性提升同时也引入了快照隔离下特有的并发异常——这将是下一篇的核心议题。