PostgreSQL 架构原理第三期:事务与并发控制 —— MVCC、快照与锁机制
Clyde Jin
04-24
DBA
43

PostgreSQL 架构原理第三期:事务与并发控制 —— MVCC、快照与锁机制

引言

前两期我们分别从进程模型、内存结构、查询流程以及存储引擎的角度剖析了 PostgreSQL 的内部机制。本期将聚焦于数据库并发控制的核心——事务与隔离。PostgreSQL 凭借其实现精巧的多版本并发控制(MVCC),能够在不使用传统读锁的情况下提供高并发读写的隔离性,同时避免了“读-写”阻塞问题。

本文将系统讲解:

  1. 事务 ID 与元组头中的版本信息
  2. 事务状态与 Commit Log(clog)
  3. 快照(Snapshot)的构成与可见性判断规则
  4. 四种隔离级别及 PostgreSQL 的具体实现
  5. 锁机制:表级锁、行锁、页锁与死锁检测
  6. 可串行化快照隔离(SSI)的原理浅析

一、事务与事务 ID

每个事务在启动时都会获得一个唯一的事务标识符(XID),它是一个 32 位无符号整数,取值范围约为 42 亿。PostgreSQL 采用循环使用机制,通过 VACUUM 处理事务回卷(wraparound)问题。

XID 的分配发生在事务执行任意写操作或使用 SELECT FOR UPDATE 等显式加锁语句时。只读事务默认不分配 XID,除非指定了 SERIALIZABLE 隔离级别。

1.1 特殊 XID

  • :InvalidXid,表示无效事务。
  • 1:BootstrapXid,表示系统表初始化事务。
  • 2:FrozenXid,用于冻结元组,表示该元组对所有事务均可见且早于所有正常 XID。

1.2 元组头上的版本信息

回顾第二期中堆元组头的关键字段:

  • t_xmin:插入元组的事务 XID。
  • t_xmax:删除或更新元组的事务 XID;若为 0,表示元组尚未被删除。
  • t_cid:事务内命令序号,用于同一事务中前后命令间的可见性。
  • t_ctid:指向当前元组或新版本元组的物理位置(页号+行指针)。

当执行 UPDATE 时,PostgreSQL 实际上执行的是:

  1. 将旧元组的 t_xmax 设置为当前事务 XID,t_ctid 指向新元组。
  2. 插入新元组,其 t_xmin 为当前事务 XID,t_xmax 为 0,t_ctid 指向自身。

执行 DELETE 时,仅将旧元组的 t_xmax 设为当前事务 XID。


二、事务状态与 Commit Log(clog)

光有 t_xmint_xmax 还不够,查询时需要知道一个事务到底是已提交还是已中止。PostgreSQL 将每个事务的状态存储在 Commit Log(clog) 中,位于数据目录下的 pg_xact 子目录(早期版本为 pg_clog)。

2.1 clog 存储格式

每个事务状态占用 2 个比特位,可能的取值:

  • TRANSACTION_STATUS_IN_PROGRESS (0x00) —— 进行中
  • TRANSACTION_STATUS_COMMITTED (0x01) —— 已提交
  • TRANSACTION_STATUS_ABORTED (0x02) —— 已中止
  • TRANSACTION_STATUS_SUB_COMMITTED(0x03) —— 子事务已提交(内部用)

clog 被划分为多个 8KB 页面,每个页面可存储 32K 个事务的状态(8KB × 1024 字节/页 × 4 个事务/字节 = 32768)。PostgreSQL 会将 clog 页面缓存到共享内存中,以减少 I/O。

2.2 事务状态查询流程

当可见性判断需要知道某个 XID 的状态时:

  1. 根据 XID 计算出所在 clog 页面及页内偏移。
  2. 若页面不在共享缓存中,从 pg_xact 读取并缓存。
  3. 读取 2 比特状态,返回 COMMITTEDABORTED

为了加速频繁访问的事务状态,PostgreSQL 还在元组头的 t_infomask 中缓存了两个标志位:HEAP_XMIN_COMMITTEDHEAP_XMIN_ABORTED(类似地对 t_xmax 也有缓存)。一旦确认过事务提交状态,就设置相应标志位,避免重复查询 clog。


三、快照(Snapshot)与可见性判断

MVCC 的核心思想是为每个查询(或事务)提供一个数据的“快照”,根据快照中的事务状态信息判断每个元组是否对当前查询可见。

3.1 快照的结构

快照主要由以下几个数组组成:

  • xmin:快照中最早仍活跃的事务 ID(即所有小于 xmin 的 XID 要么已提交,要么已中止)。
  • xmax:快照中第一个未分配的事务 ID(即所有 XID >= xmax 都视为未开始)。
  • xip:当前活跃(进行中)的事务 ID 列表。

例如:当前活跃事务为 [100, 105, 110],则 xmin = 100xmax = 111(假设下一个未使用的 XID 是 111),xip 包含 100、105、110。

注意: 对于 READ COMMITTED 隔离级别,每个 SQL 语句都会获取新的快照;对于 REPEATABLE READSERIALIZABLE,整个事务使用同一个快照。

3.2 可见性判断规则

给定一个元组 T,其 t_xmin = XID_inst_xmax = XID_del(0 表示未删除)。判断元组对当前快照 Snap 是否可见的伪代码:

if XID_ins 为 ABORTED → 不可见
if XID_ins 为 IN_PROGRESS:
    if XID_ins == 当前事务:
        根据 t_cid 判断当前命令是否能看见之前更新(同一事务内)
    else:
        → 不可见(其他未提交事务的更改不可见)
if XID_ins 为 COMMITTED:
    if XID_del == 0:
        → 可见
    if XID_del 为 ABORTED:
        → 可见(删除未生效)
    if XID_del 为 IN_PROGRESS:
        if XID_del == 当前事务:
            根据 t_cid 判断是否在当前命令之前已删除
        else:
            → 可见(其他事务的删除尚未提交)
    if XID_del 为 COMMITTED:
        → 不可见(已被已提交的删除或更新移除)

实际实现中,PostgreSQL 使用 HeapTupleSatisfiesMVCC() 等一系列函数进行判断,并考虑了 SNAPSHOT 类型(如用于 REPEATABLE READSnapShot 需要排除在事务开始后提交的事务)。

3.3 冻结与回卷保护

由于 XID 是 32 位循环使用,当 XID 回卷到小于之前已冻结的 XID 时,可见性判断会出错。PostgreSQL 通过 VACUUM FREEZE 或自动冻结,将足够旧的元组的 t_xmin 替换为 FrozenXid(值为 2),并设置 HEAP_XMIN_FROZEN 标志。冻结后的元组对所有事务直接可见,不再需要进行 XID 比对,从而解决了 XID 回卷问题。


四、隔离级别与行为差异

SQL 标准定义了四种隔离级别,PostgreSQL 的默认隔离级别为 READ COMMITTED,同时支持 REPEATABLE READSERIALIZABLEREAD UNCOMMITTED 被映射为 READ COMMITTED)。

4.1 READ COMMITTED

  • 每个语句获得新的快照
  • 避免脏读(无法看到其他事务未提交的更改)。
  • 不可重复读:同一个事务内两次查询可能看到不同数据(因为语句间其他事务可能提交更改)。
  • 写操作会读取已提交的最新数据,不会使用语句开始时的快照。

表现: 如果事务 A 执行 UPDATE ... WHERE,事务 B 在 A 提交前修改了某些行并提交,A 的 UPDATE 会看到 B 提交的结果,并可能再次更新那些行。

4.2 REPEATABLE READ

  • 整个事务使用同一个快照(事务中第一个非事务控制命令执行时获取)。
  • 避免了脏读和不可重复读,但可能出现幻读?严格来说 PostgreSQL 的 REPEATABLE READ 实际上也避免了幻读,因为快照是基于事务开始时刻的,新插入的行对事务不可见。但 SQL 标准将更严格的幻读要求划给了 SERIALIZABLE
  • 写操作(UPDATEDELETESELECT FOR UPDATE)如果尝试修改其他事务已提交更改的行,则会因为“可重复读”冲突而报错,并回滚事务。

行为: 事务 A 开始后,事务 B 插入一行并提交。事务 A 再次查询不会看到新行。若事务 A 尝试更新 B 插入的行,会收到 could not serialize access due to concurrent update 错误。

4.3 SERIALIZABLE

  • 基于 可串行化快照隔离(SSI) 算法,比普通快照隔离更强。
  • 通过跟踪读/写依赖关系,检测可能导致环状冲突的事务,并主动中止其中一个,从而实现真正的可串行化。
  • 性能比 REPEATABLE READ 略有损耗,但避免了应用程序使用显式锁的复杂性。

4.4 与锁的结合

尽管 MVCC 避免了读-写阻塞,但写-写冲突仍需要锁来防止丢失更新。UPDATEDELETESELECT FOR UPDATE 会对行加上行锁。不同隔离级别下的写冲突处理详见下节锁机制。


五、锁机制

PostgreSQL 的锁分为多个层次:表级锁、行级锁、页级锁(轻量级锁,主要用于内部结构),以及用于死锁检测和 SSI 的谓词锁。

5.1 表级锁

通过 LOCK 命令或 DDL 语句自动获取。主要模式有:

  • AccessShareLockSELECT 获取,与 RowShareLockRowExclusiveLock 等不冲突。
  • RowShareLockSELECT FOR UPDATE/SHARE 获取,防止其他事务并行 ALTER TABLE 等 DDL。
  • RowExclusiveLockUPDATEDELETEINSERT 获取。允许并发读,但阻止 DDL 更改表结构。
  • ShareLock:创建索引时获取,允许读但不允许写。
  • ExclusiveLockREFRESH MATERIALIZED VIEW CONCURRENTLY 等操作,阻止所有并发读。
  • AccessExclusiveLockDROP TABLETRUNCATEVACUUM FULLALTER TABLE 等 DDL 获取,是最严格的锁,与所有其他锁冲突。

使用 pg_locks 视图可以查看当前锁的持有和等待情况。

5.2 行级锁

行级锁存储在元组的 t_infomask 中,而不是单独的一张锁表,因此非常轻量。主要模式:

  • 行共享锁SELECT FOR SHARE,阻止其他事务 FOR UPDATE 但允许 FOR SHARE
  • 行排他锁SELECT FOR UPDATEUPDATE/DELETE,阻止其他事务对这些行加任何行级锁。

行锁的冲突矩阵:

已有锁 / 请求锁 FOR SHARE FOR UPDATE
FOR SHARE 允许 阻塞
FOR UPDATE 阻塞 阻塞

当一个事务修改某行时,会在该行上设置行排他锁。如果另一个事务想要修改同一行,会等待行锁释放(即第一个事务提交或回滚)。若两个事务同时更新同一行,其中一个会等待,另一个完成后在 READ COMMITTED 下会重新读取该行并再次尝试更新(可能导致非直观结果);在 REPEATABLE READSERIALIZABLE 下则会直接报错并回滚。

5.3 页级锁与轻量级锁

  • 页级锁:主要用于缓冲区管理器替换策略的短期锁定,对用户透明。
  • 轻量级锁(LWLocks):保护共享内存中的数据结构(如缓冲区描述符、锁管理器等)。分为两种模式:独占(Exclusive)和共享(Shared)。为了性能,LWLocks 不支持排队等待,而是通过自旋+休眠实现。

5.4 死锁检测

PostgreSQL 提供自动死锁检测机制。当进程等待一个锁时,会启动超时计时器(deadlock_timeout,默认 1 秒)。超时后,锁管理器会沿着等待关系构建有向图,如果检测到环,则选择其中一个事务中止,从而打破死锁。被选中回滚的事务会收到 deadlock detected 错误。

5.5 谓词锁与 SSI

SERIALIZABLE 隔离级别使用谓词锁(predicate locks)来防止幻读。与传统行锁不同,谓词锁锁定了查询的条件范围(例如 WHERE id > 100)。PostgreSQL 的 SSI 实现并不对所有谓词加实际锁,而是通过检测读依赖(读过的行)和写依赖(写入的行)之间的冲突,推断出是否存在可串行化异常。当检测到可能产生交换的循环依赖时,回滚事务。


六、总结与预告

本期我们全面讲解了 PostgreSQL 事务与并发控制的内部机制:

  • 事务 XID 和元组版本头字段(t_xmin/t_xmax)的组合构成了 MVCC 的基石。
  • clog 用于持久化事务的提交/回滚状态,配合快照实现可见性判断。
  • 不同的隔离级别通过快照获取时机和写冲突处理方式区分。
  • 表级锁与行级锁保证了 DDL 和写-写的隔离,而死锁检测解决了多事务循环等待。
  • 可串行化快照隔离提供了真正的可串行化,避免了昂贵的手动锁。

理解这些机制对调优高并发事务、避免死锁和性能瓶颈至关重要。下一期我们将从优化器角度出发,讲解 统计信息与代价模型,看看 PostgreSQL 是如何估算不同执行计划的代价,并最终选择最优的执行方式。


思考题

  1. REPEATABLE READ 隔离级别下,事务 A 先读取了一行,然后事务 B 删除了该行并提交。事务 A 随后尝试 UPDATE 这一行会发生什么?为什么?
  2. 如果 clog 损坏,可能导致哪些严重的数据库故障?如何预防或修复?
  3. 为什么 SELECT FOR UPDATE 即使在 READ COMMITTED 下也能防止“丢失更新”?它的底层是否使用了 MVCC?

Star
Donate
PostgreSQL 架构原理第二期:存储引擎深度解析 —— 堆表、元组结构与 TOAST 机制
Previous
PostgreSQL 架构原理第四期:查询优化器 —— 统计信息与代价模型
Next

Leave a comment

Registration is not required

Clyde Jin
303 Articles
0 Comments
0 Like
Recent Posts

HiddenMerit Daily · Issue 60

📊 HiddenMerit Daily · Issue 60 Focus on Database Frontiers, Practical Insights for DBAs July 22, 2026 | 5 Selected Global Breaking News 01|Oracle Releases Largest Quarterly Patch in History: 1,449 Patches Fix 1,235 CVEs, 261 Critical On July 21, Oracle released its July 2026 Critical Patch Update (CPU), setting a record for the largest single patch release in the company’s history. This CPU contains 1,449 security patches fixing 1,235 independent CVEs across 32 Oracle product families, of which 261 patches are rated Critical. Patch Distribution by Product Family: Product Family Patches Remotely Exploitable Without Authentication Oracle E‑Business Suite 410 45 Oracle Fusion Middleware 355 219 Oracle Communications 168 122 Oracle MySQL 54 9 Oracle Database Server 15 6 Context for This Patch: Oracle had already issued an urgent “Prepare Now” warning a week earlier, emphasising that AI is fundamentally lowering the barrier to discovering and exploiting vulnerabilities – frontier AI models can analyse software changes, reverse‑engineer security patches, and develop attack paths at unprecedented speed. Oracle has collaborated with state‑of‑the‑art models from Anthropic and OpenAI to proactively identify and fix potential security vulnerabilities. Oracle strongly recommends that customers immediately test and deploy this update in their monthly patch cycles. […]

HiddenMerit Daily · Issue 59

📊 HiddenMerit Daily · Issue 59 Focus on Database Frontiers, Practical Insights for DBAs July 21, 2026 | 5 Selected Global Breaking News 01|Oracle Issues Urgent Warning: July 21 Release Update to Fix Large Number of High‑Risk Vulnerabilities, Immediate Deployment Recommended On July 13, Oracle issued an urgent security warning, strongly recommending that all customers running supported Oracle Database versions (including Oracle Database 19c and Oracle AI Database 26ai) immediately test and deploy the Release Update (RU) after its release on July 21. Background: New frontier AI models are significantly lowering the barrier to discovering and exploiting software vulnerabilities – these models can identify weaknesses, analyse software changes, reverse‑engineer security patches, and develop potential attack paths at unprecedented speed and scale. AI models are also becoming increasingly adept at combining multiple weaknesses across the application and data stack into complex attacks, even when individual weaknesses do not themselves pose a serious risk. As a result, protecting systems solely at the network or application layer is no longer sufficient; enterprises must protect the entire technology stack. Fixes Included in This RU: Oracle has collaborated with state‑of‑the‑art models from Anthropic and OpenAI to proactively identify and fix potential security vulnerabilities. The upcoming […]

HiddenMerit Daily · Issue 58

📊 HiddenMerit Daily · Issue 58 Focus on Database Frontiers, Practical Insights for DBAs July 20, 2026 | 5 Selected Global Breaking News 01|CAICT: Domestic Databases Enter Core System “Deep Water,” AI‑Native Leads New Industry Landscape On July 9, at the 2026 Trustworthy Database Development Conference, CAICT released the “Database Development Research Report (2026).” The report notes that domestic databases have basically completed peripheral system replacement and have officially entered the critical business system breakthrough phase. Key Data: The global database market reached $131.6 billion in 2025 (approximately RMB 894.09 billion). The Chinese database market reached $9.49 billion in 2025 (approximately RMB 64.48 billion), and is expected to reach RMB 97.974 billion by 2028, with a CAGR of 13.06%. The number of domestic database vendors has shrunk from a peak of 167 to 94, with a clear head‑concentration effect and an intensifying “Matthew effect.” AI‑Native Becomes the Main Theme: The report points out that database technology is accelerating its evolution toward the AI‑native direction, and the global database industry is entering a new phase of landscape restructuring. The role of databases is upgrading from “underlying support systems” to “core engines enabling intelligent decision‑making and business innovation.” AI agents are becoming […]

HiddenMerit Daily · Issue 56

📊 HiddenMerit Daily · Issue 56 Focus on Database Frontiers, Practical Insights for DBAs July 6, 2026 | 5 Selected Global Breaking News 01|Kingware Releases Manufacturing Scenario Database Evolution White Paper: SQL Server Replacement Enters “Deep Water” On July 5, CETC Kingware published a technical article titled “Database Evolution in Manufacturing Scenarios: How Kingware Replaces SQL Server,” pointing out that the data surge on industrial shop floors has exceeded the processing capacity of single‑node systems. Traditional database architectures that rely on vertical scaling are facing unprecedented performance challenges. Over the next 1‑3 years, manufacturing enterprises will no longer face only the “choice of replacement,” but must answer the strategic question: “How do we build an autonomous data foundation in the context of de‑IOE?” Three Paradigm Shifts: Hybrid Workloads Become the Norm: The IT architecture of modern factories is shifting from separated OLTP and OLAP to HTAP mode. The same data system must simultaneously handle tens of thousands of device instruction writes per second and minute‑level production report analysis. Distributed Architecture Becomes a Hard Requirement: When a single table exceeds 100 million rows with daily increments exceeding 1 million rows, the index maintenance cost of traditional single‑node databases rises exponentially. Autonomous […]
生成中...
扫描二维码
扫描二维码