2. PostgreSQL 事务基础:ACID 特性、事务隔离级别、显式事务与隐式事务

事务是数据库系统中保证数据一致性与完整性的核心机制。在异步任务编排场景中,多个子任务可能共享同一个数据库连接或事务上下文,理解 PostgreSQL 的事务模型是设计可靠回滚策略与性能优化的前提。本节将系统梳理事务的 ACID 特性、隔离级别以及显式/隐式事务的使用方式。

2.1 ACID 特性

ACID 是数据库事务正确执行的四个基本特性,PostgreSQL 通过多版本并发控制(MVCC)和预写式日志(WAL)完整实现了这些特性。

特性 含义 PostgreSQL 实现机制
原子性(Atomicity) 事务中的所有操作要么全部成功提交,要么全部回滚,不存在部分执行的状态。 通过 WAL 日志和事务提交日志(clog)实现。回滚时利用 WAL 中的 undo 记录恢复数据页。
一致性(Consistency) 事务执行前后,数据库必须满足所有预定义的约束、触发器和业务规则。 依赖约束检查(主键、外键、唯一、检查约束)和触发器在事务提交前验证。
隔离性(Isolation) 并发执行的事务之间互不干扰,每个事务都感觉不到其他事务的并发执行。 基于 MVCC 的快照隔离,每个事务看到的是数据的一个一致性快照。
持久性(Durability) 一旦事务提交成功,其对数据库的修改就是永久的,即使系统崩溃也不会丢失。 WAL 日志在事务提交时强制刷盘(fsync),确保日志先于数据页写入。
异步任务中的 ACID 陷阱: 在编排多个异步子任务时,如果每个子任务使用独立数据库连接,原子性将无法跨连接保证。常见做法是使用 Saga 模式或两阶段提交(2PC)来弥补,但会引入额外的协调开销。
ACID 特性与 PostgreSQL 实现对应关系 ACID 特性与 PostgreSQL 实现对应关系 原子性 一致性 隔离性 持久性 WAL + 事务提交日志 约束 + 触发器验证 MVCC 快照隔离 WAL 强制刷盘 PostgreSQL 通过 WAL + MVCC 双引擎完整实现 ACID 异步任务编排中需注意跨连接事务的原子性边界

2.2 事务隔离级别

SQL 标准定义了四种事务隔离级别,用于控制并发事务之间的可见性。PostgreSQL 默认使用 读已提交(Read Committed),同时完整支持所有标准级别。

隔离级别 脏读 不可重复读 幻读 序列化异常
读未提交(Read Uncommitted) 可能(PG 实际不会发生) 可能 可能 可能
读已提交(Read Committed) 不可能 可能 可能 可能
可重复读(Repeatable Read) 不可能 不可能 可能(PG 不会发生) 可能
可序列化(Serializable) 不可能 不可能 不可能 不可能
PostgreSQL 隔离级别特殊行为:
  • 读未提交 在 PG 中实际行为等同于 读已提交,因为 MVCC 架构天然防止脏读。
  • 可重复读 在 PG 中通过快照隔离实现,不会出现幻读(标准 SQL 允许幻读)。
  • 可序列化 使用可序列化快照隔离(SSI)技术,检测读写冲突并强制回滚冲突事务。
PostgreSQL 隔离级别与并发异常对照 PostgreSQL 隔离级别与并发异常对照 隔离级别 读未提交 读已提交 可重复读 可序列化 脏读 不可重复读 幻读 序列化异常 ✓ 不可能 ✓ 不可能 ✓ 不可能 ✓ 不可能 ✗ 可能 ✗ 可能 ✓ 不可能 ✓ 不可能 ✗ 可能 ✗ 可能 ✓ 不可能 ✓ 不可能 ✗ 可能 ✗ 可能 ✗ 可能 ✓ 不可能

2.3 显式事务与隐式事务

PostgreSQL 支持两种事务管理方式:隐式事务(自动提交)和显式事务(手动控制)。理解两者的区别对于异步任务编排中的事务边界控制至关重要。

2.3.1 隐式事务(自动提交)

默认情况下,PostgreSQL 的每个 SQL 语句都被当作一个独立的事务执行,语句执行完毕后自动提交。这种方式适用于简单的单语句操作。

-- 每条语句自动提交
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- 如果第一条成功、第二条失败,第一条的修改已经提交,无法回滚
异步任务风险: 在异步编排中,如果多个子任务使用隐式事务,任何一个子任务失败都无法撤销其他子任务已提交的修改。这是导致数据不一致的常见原因。

2.3.2 显式事务(手动控制)

使用 BEGIN 或 BEGIN TRANSACTION 启动事务,通过 COMMIT 提交或 ROLLBACK 回滚。显式事务允许将多个操作组合成一个原子单元。

-- 显式事务:原子性保证
BEGIN;
  UPDATE accounts SET balance = balance - 100 WHERE id = 1;
  UPDATE accounts SET balance = balance + 100 WHERE id = 2;
  -- 如果任何一步失败,可以 ROLLBACK
COMMIT;  -- 或 ROLLBACK;

2.3.3 事务控制语句速查

语句 作用 异步编排场景说明
BEGIN 启动一个显式事务块 每个异步子任务组应使用独立的 BEGIN/COMMIT 包裹
COMMIT 提交当前事务,使修改永久生效 所有子任务成功后再提交,避免部分提交
ROLLBACK 回滚当前事务,撤销所有修改 任一子任务失败时触发全局回滚
SAVEPOINT 在事务中设置保存点,支持部分回滚 可用于子任务粒度的回滚,保留其他子任务进度
RELEASE SAVEPOINT 释放保存点,使其不再可用 子任务成功后释放对应保存点
ROLLBACK TO SAVEPOINT 回滚到指定保存点,保留保存点之后的修改 实现子任务级别的部分回滚

2.3.4 保存点示例:子任务级回滚

BEGIN;
  -- 子任务 1
  UPDATE task_status SET state = 'running' WHERE task_id = 1;
  SAVEPOINT sp_task1;
  -- 子任务 1 的具体操作
  INSERT INTO task_log VALUES (1, 'start', now());
  -- 假设子任务 1 失败
  ROLLBACK TO SAVEPOINT sp_task1;
  -- 子任务 1 的修改被撤销,但事务仍在进行

  -- 子任务 2
  UPDATE task_status SET state = 'completed' WHERE task_id = 2;
  -- 子任务 2 成功
COMMIT;  -- 只提交子任务 2 的修改
性能优化建议: 在异步任务编排中,尽量将多个子任务合并到同一个显式事务中,减少事务提交次数(每次 COMMIT 涉及 WAL 刷盘)。使用保存点实现细粒度回滚,避免整个事务重做。

2.4 事务与异步任务的交互模式

在异步任务编排中,事务管理通常遵循以下三种模式:

  1. 单连接单事务: 所有子任务共享同一个数据库连接和事务,适合短流程、低并发的场景。
  2. 多连接独立事务: 每个子任务使用独立连接和独立事务,适合高并发但需要补偿事务(Saga)的场景。
  3. 多连接共享事务(XA/2PC): 通过两阶段提交协调多个连接的事务,适合分布式强一致性场景,但性能开销较大。
模式 原子性保证 性能 复杂度 推荐场景
单连接单事务 强(ACID) 高(减少刷盘) 低 短流程、低并发、内部任务
多连接独立事务 弱(需补偿) 中(连接池开销) 中 长流程、高并发、微服务
多连接共享事务 强(2PC) 低(协调开销) 高 分布式强一致性、金融场景
实践建议: 对于大多数异步任务编排场景,优先采用 单连接单事务 模式,配合保存点实现子任务级回滚。当任务必须跨数据库实例时,再考虑 Saga 或 2PC。