PostgreSQL笔记28: PostgreSQL ACID 特性与实现机制深度解析

纲要

  • ACID 事务特性概述
    • Atomicity(原子性)
    • Consistency(一致性)
    • Isolation(隔离性)
    • Durability(持久性)
  • 原子性(Atomicity)子事务(Subtransaction)
    • SAVEPOINT 与子事务机制
    • psqlON_ERROR_ROLLBACK 行为
    • 子事务的性能代价与资源消耗
    • JDBC 中的 autosave 参数
  • 一致性(Consistency) 的保障机制
  • 隔离性(Isolation)MVCC
    • 四种标准隔离级别
    • PostgreSQL 的隔离级别实现
    • MVCC(多版本并发控制)机制
    • SSI(可串行化快照隔离)
  • 持久性(Durability)WAL
    • WAL(预写日志)机制
    • fsynccheckpoint
    • 流复制场景中的同步复制
  • API 速览
  • Demo 简单示例
  • 官方文档
  • 参考链接

事务的 ACID 特性

ACID 是关系数据库事务的四个核心属性,分别是 Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)和 Durability(持久性)。这四个属性共同保证了数据库事务的可靠执行。

原子性(Atomicity)

原子性是指一个事务中的所有操作,要么全部成功提交,要么全部失败回滚,不存在中间状态。当事务在执行过程中发生错误时,整个事务会被回滚到开始之前的状态,就好像这个事务从未执行过一样。

在 PostgreSQL 中,原子性通过 事务日志WAL)和 CLOG(事务提交日志)共同保证。崩溃恢复完成后,未提交的事务会被完整回滚,已提交的事务会被完整重做。

子事务(Subtransaction)

PostgreSQL 支持子事务机制,允许在事务内部通过 SAVEPOINT 创建保存点,从而实现部分回滚。

sql 复制代码
-- 创建测试表
CREATE TABLE t1 (id INTEGER PRIMARY KEY);

-- 开启事务
BEGIN;

-- 插入第一条数据
INSERT INTO t1 VALUES (99);

-- 创建保存点 sp1
SAVEPOINT sp1;

-- 插入第二条数据(此处模拟错误)
INSERT INTO t1 VALUES (999);  -- 假设此处发生错误

-- 回滚到保存点 sp1
ROLLBACK TO SAVEPOINT sp1;

-- 查询结果:仅能看到 id=99,id=999 已被回滚
SELECT * FROM t1;
-- 输出: 99

-- 提交事务
COMMIT;

子事务的本质是将一个事务拆分为多个更小的事务单元,但它仍然属于同一个顶层事务,不会破坏事务的原子性。

子事务的性能代价

子事务虽然提供了灵活的细粒度错误处理能力,但其使用需要谨慎评估:

  1. 事务 ID 消耗 :每个子事务都会分配一个永久事务 ID(XID)。事务 ID 在 PostgreSQL 中是一种有限资源。有实测表明,仅插入 5000 条数据,若每条都使用子事务,可能消耗 64 万个 XID,这会直接导致 autovacuum freeze 提前到来。

  2. 缓存溢出 :当一个会话中的子事务超过 64 个时,会触发 overflow 状态,性能会急剧下降。

  3. 查找性能退化 :在存在大量活跃子事务的场景中,TransactionIdIsCurrentTransactionId 函数的查找会退化为对链表的线性扫描。PostgreSQL 社区有报告显示,在包含 1000 个保存点的测试中,性能极度缓慢,优化补丁可将性能提升约 10 倍。

  4. 日志膨胀:子事务为记录保存点元数据,产生的日志量可能达到普通操作的数万倍。

  5. 锁竞争 :当并发事务较多时,子事务可能引入 SubtransControlLock 等待事件,影响整体性能。

结论:在生产环境中,非必要不应使用子事务。如必须使用,应严格控制每个事务中的子事务数量,建议不超过 64 个。

ON_ERROR_ROLLBACK

psql 命令行工具提供了 ON_ERROR_ROLLBACK 选项,当启用时,如果事务块中的语句发生错误,错误会被忽略,事务继续执行。

sql 复制代码
-- 启用 ON_ERROR_ROLLBACK
\set ON_ERROR_ROLLBACK on

BEGIN;
INSERT INTO t1 VALUES (100);
INSERT INTO t1 VALUES (200);
-- 假设此处发生错误(例如违反约束)
INSERT INTO t1 VALUES (100);  -- 主键冲突
-- 错误被忽略,事务继续
INSERT INTO t1 VALUES (300);
COMMIT;

ON_ERROR_ROLLBACK 的工作原理是在事务块中每条命令执行前隐式创建一个 SAVEPOINT,出错时回滚到该保存点。

sql 复制代码
-- 上述行为等价于手动执行
BEGIN;
SAVEPOINT pg_psql_temporary_savepoint;
INSERT INTO t1 VALUES (100);
RELEASE SAVEPOINT pg_psql_temporary_savepoint;
SAVEPOINT pg_psql_temporary_savepoint;
INSERT INTO t1 VALUES (200);
RELEASE SAVEPOINT pg_psql_temporary_savepoint;
-- 以此类推

需要注意的是,ON_ERROR_ROLLBACK 仅在 psql 交互模式下生效。

JDBC 中的 autosave

JDBC 驱动中,autosave 参数提供了类似的能力。当启用时,驱动会在每条语句执行前自动创建保存点,出错时回滚到该保存点。这也意味着每条语句都会消耗一个子事务,同样需要注意性能影响。

一致性(Consistency)

一致性是指事务执行前后,数据库的完整性约束没有被破坏。数据必须符合所有预定义的约束规则,包括:

  • 主键约束(PRIMARY KEY
  • 外键约束(FOREIGN KEY
  • 唯一约束(UNIQUE
  • 检查约束(CHECK
  • 触发器(TRIGGER
sql 复制代码
-- 创建带约束的表
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    customer_id INTEGER NOT NULL,
    amount DECIMAL(10, 2) CHECK (amount > 0),
    status VARCHAR(20) DEFAULT 'pending'
);

-- 以下插入将因 CHECK 约束失败而回滚
BEGIN;
INSERT INTO orders (customer_id, amount) VALUES (1, -100.00);  -- 违反 CHECK
COMMIT;  -- 事务回滚

在 PostgreSQL 中,一致性主要通过数据类型约束触发器来保证。

隔离性(Isolation)

隔离性是指多个事务并发执行时,彼此的操作不会相互干扰。PostgreSQL 通过 MVCC(多版本并发控制)实现了高效的隔离性,避免了读写事务之间的互相阻塞。

事务隔离级别

SQL 标准定义了四种事务隔离级别:

隔离级别 脏读(Dirty Read) 不可重复读(Nonrepeatable Read) 幻读(Phantom Read) 序列化异常(Serialization Anomaly)
READ UNCOMMITTED 可能(PG 中不允许) 可能 可能 可能
READ COMMITTED 不可能 可能 可能 可能
REPEATABLE READ 不可能 不可能 可能(PG 中不允许) 可能
SERIALIZABLE 不可能 不可能 不可能 不可能

PostgreSQL 在内部仅实现了三种不同的隔离级别:

  1. READ COMMITTED :PostgreSQL 的默认隔离级别。事务中的每条查询只能看到查询开始前已提交的数据。同一事务中的两条 SELECT 语句可能看到不同的数据。
sql 复制代码
-- 设置隔离级别为 READ COMMITTED
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
  1. REPEATABLE READ :事务中的所有语句只能看到事务中第一个查询或数据修改语句执行前已提交的数据。PostgreSQL 的 REPEATABLE READ 不允许幻读。
sql 复制代码
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
  1. SERIALIZABLE :最严格的隔离级别。并发事务的执行效果必须与串行执行的效果一致。如果并发读写模式会导致不可串行化的结果,其中一个事务将被回滚并报 serialization_failure 错误。
sql 复制代码
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

值得注意的是,在 PostgreSQL 中请求 READ UNCOMMITTED 级别时,实际行为等同于 READ COMMITTED

MVCC(多版本并发控制)

PostgreSQL 的隔离性主要通过 MVCC 实现。其核心原理是:

  1. 数据文件中存放同一逻辑行的多个版本(称为 Tuple)。
  2. 每个行版本的头部记录了创建和删除该版本的事务 ID(分别称为 xminxmax)。
  3. 每个事务在执行时获取一个数据库快照,根据快照中的事务状态信息决定哪些行版本可见。
  4. 读者不会阻塞写者,写者也不会阻塞读者,从而极大提高了并发性能。

#mermaid-svg-z1eAW4gU2vVE89j4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-z1eAW4gU2vVE89j4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-z1eAW4gU2vVE89j4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-z1eAW4gU2vVE89j4 .error-icon{fill:#552222;}#mermaid-svg-z1eAW4gU2vVE89j4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-z1eAW4gU2vVE89j4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-z1eAW4gU2vVE89j4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-z1eAW4gU2vVE89j4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-z1eAW4gU2vVE89j4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-z1eAW4gU2vVE89j4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-z1eAW4gU2vVE89j4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-z1eAW4gU2vVE89j4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-z1eAW4gU2vVE89j4 .marker.cross{stroke:#333333;}#mermaid-svg-z1eAW4gU2vVE89j4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-z1eAW4gU2vVE89j4 p{margin:0;}#mermaid-svg-z1eAW4gU2vVE89j4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-z1eAW4gU2vVE89j4 .cluster-label text{fill:#333;}#mermaid-svg-z1eAW4gU2vVE89j4 .cluster-label span{color:#333;}#mermaid-svg-z1eAW4gU2vVE89j4 .cluster-label span p{background-color:transparent;}#mermaid-svg-z1eAW4gU2vVE89j4 .label text,#mermaid-svg-z1eAW4gU2vVE89j4 span{fill:#333;color:#333;}#mermaid-svg-z1eAW4gU2vVE89j4 .node rect,#mermaid-svg-z1eAW4gU2vVE89j4 .node circle,#mermaid-svg-z1eAW4gU2vVE89j4 .node ellipse,#mermaid-svg-z1eAW4gU2vVE89j4 .node polygon,#mermaid-svg-z1eAW4gU2vVE89j4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-z1eAW4gU2vVE89j4 .rough-node .label text,#mermaid-svg-z1eAW4gU2vVE89j4 .node .label text,#mermaid-svg-z1eAW4gU2vVE89j4 .image-shape .label,#mermaid-svg-z1eAW4gU2vVE89j4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-z1eAW4gU2vVE89j4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-z1eAW4gU2vVE89j4 .rough-node .label,#mermaid-svg-z1eAW4gU2vVE89j4 .node .label,#mermaid-svg-z1eAW4gU2vVE89j4 .image-shape .label,#mermaid-svg-z1eAW4gU2vVE89j4 .icon-shape .label{text-align:center;}#mermaid-svg-z1eAW4gU2vVE89j4 .node.clickable{cursor:pointer;}#mermaid-svg-z1eAW4gU2vVE89j4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-z1eAW4gU2vVE89j4 .arrowheadPath{fill:#333333;}#mermaid-svg-z1eAW4gU2vVE89j4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-z1eAW4gU2vVE89j4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-z1eAW4gU2vVE89j4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-z1eAW4gU2vVE89j4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-z1eAW4gU2vVE89j4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-z1eAW4gU2vVE89j4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-z1eAW4gU2vVE89j4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-z1eAW4gU2vVE89j4 .cluster text{fill:#333;}#mermaid-svg-z1eAW4gU2vVE89j4 .cluster span{color:#333;}#mermaid-svg-z1eAW4gU2vVE89j4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-z1eAW4gU2vVE89j4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-z1eAW4gU2vVE89j4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-z1eAW4gU2vVE89j4 .icon-shape,#mermaid-svg-z1eAW4gU2vVE89j4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-z1eAW4gU2vVE89j4 .icon-shape p,#mermaid-svg-z1eAW4gU2vVE89j4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-z1eAW4gU2vVE89j4 .icon-shape .label rect,#mermaid-svg-z1eAW4gU2vVE89j4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-z1eAW4gU2vVE89j4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-z1eAW4gU2vVE89j4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-z1eAW4gU2vVE89j4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 数据行(元组)
版本1: xmin=100, xmax=∞
事务 B (XID=101)
BEGIN (获取快照)
SELECT
快照判断: xmin=100 已提交 → 可见
事务 A (XID=100)
BEGIN
INSERT 创建版本 xmin=100
COMMIT

PostgreSQL 在最严格的 SERIALIZABLE 隔离级别下,使用创新的 可串行化快照隔离(SSI,Serializable Snapshot Isolation) 技术来检测并解决序列化异常。

持久性(Durability)

持久性是指事务提交后,其对数据库的修改是永久的,即使系统宕机也不会丢失。

WAL(预写日志)

PostgreSQL 通过 WAL(Write-Ahead Logging,预写日志)保证持久性。其核心原则是:
对数据文件的修改必须在描述这些修改的日志记录被刷写到持久存储之后才能进行。
#mermaid-svg-5ax13tQSytKy0qbW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5ax13tQSytKy0qbW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5ax13tQSytKy0qbW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5ax13tQSytKy0qbW .error-icon{fill:#552222;}#mermaid-svg-5ax13tQSytKy0qbW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5ax13tQSytKy0qbW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5ax13tQSytKy0qbW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5ax13tQSytKy0qbW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5ax13tQSytKy0qbW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5ax13tQSytKy0qbW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5ax13tQSytKy0qbW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5ax13tQSytKy0qbW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5ax13tQSytKy0qbW .marker.cross{stroke:#333333;}#mermaid-svg-5ax13tQSytKy0qbW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5ax13tQSytKy0qbW p{margin:0;}#mermaid-svg-5ax13tQSytKy0qbW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5ax13tQSytKy0qbW .cluster-label text{fill:#333;}#mermaid-svg-5ax13tQSytKy0qbW .cluster-label span{color:#333;}#mermaid-svg-5ax13tQSytKy0qbW .cluster-label span p{background-color:transparent;}#mermaid-svg-5ax13tQSytKy0qbW .label text,#mermaid-svg-5ax13tQSytKy0qbW span{fill:#333;color:#333;}#mermaid-svg-5ax13tQSytKy0qbW .node rect,#mermaid-svg-5ax13tQSytKy0qbW .node circle,#mermaid-svg-5ax13tQSytKy0qbW .node ellipse,#mermaid-svg-5ax13tQSytKy0qbW .node polygon,#mermaid-svg-5ax13tQSytKy0qbW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5ax13tQSytKy0qbW .rough-node .label text,#mermaid-svg-5ax13tQSytKy0qbW .node .label text,#mermaid-svg-5ax13tQSytKy0qbW .image-shape .label,#mermaid-svg-5ax13tQSytKy0qbW .icon-shape .label{text-anchor:middle;}#mermaid-svg-5ax13tQSytKy0qbW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5ax13tQSytKy0qbW .rough-node .label,#mermaid-svg-5ax13tQSytKy0qbW .node .label,#mermaid-svg-5ax13tQSytKy0qbW .image-shape .label,#mermaid-svg-5ax13tQSytKy0qbW .icon-shape .label{text-align:center;}#mermaid-svg-5ax13tQSytKy0qbW .node.clickable{cursor:pointer;}#mermaid-svg-5ax13tQSytKy0qbW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5ax13tQSytKy0qbW .arrowheadPath{fill:#333333;}#mermaid-svg-5ax13tQSytKy0qbW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5ax13tQSytKy0qbW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5ax13tQSytKy0qbW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5ax13tQSytKy0qbW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5ax13tQSytKy0qbW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5ax13tQSytKy0qbW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5ax13tQSytKy0qbW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5ax13tQSytKy0qbW .cluster text{fill:#333;}#mermaid-svg-5ax13tQSytKy0qbW .cluster span{color:#333;}#mermaid-svg-5ax13tQSytKy0qbW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5ax13tQSytKy0qbW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5ax13tQSytKy0qbW rect.text{fill:none;stroke-width:0;}#mermaid-svg-5ax13tQSytKy0qbW .icon-shape,#mermaid-svg-5ax13tQSytKy0qbW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5ax13tQSytKy0qbW .icon-shape p,#mermaid-svg-5ax13tQSytKy0qbW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5ax13tQSytKy0qbW .icon-shape .label rect,#mermaid-svg-5ax13tQSytKy0qbW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5ax13tQSytKy0qbW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5ax13tQSytKy0qbW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5ax13tQSytKy0qbW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 崩溃恢复
系统重启
读取 WAL
重做已提交事务
回滚未提交事务
事务提交流程
事务执行
WAL Buffer 写入
WAL 刷盘 fsync
返回 COMMIT 成功
后台异步写入数据文件

事务提交时,PostgreSQL 会执行 fsyncWAL 刷写到磁盘,然后才返回提交成功。数据文件的更新是延迟进行的,由后台的 checkpoint 进程负责。

fsync 与 checkpoint
  • fsync :控制是否强制将 WAL 和数据文件同步到磁盘。生产环境绝不能 关闭 fsync,否则电源故障可能导致不可恢复的数据丢失。
  • checkpoint :定期将共享缓冲区中的脏数据写入数据文件,并更新 pg_control 文件。相关参数包括 checkpoint_timeout(默认 5 分钟)和 max_wal_size
流复制场景

在主从流复制场景中,同步复制可以进一步保证持久性------只有当事务的 WAL 已被传输并刷写到至少一个同步备库后,主库才返回提交成功。

API 速览

事务控制命令

BEGIN / START TRANSACTION

开启一个新事务。

语法

sql 复制代码
BEGIN [ WORK | TRANSACTION ] [ transaction_mode [, ...] ]
START TRANSACTION [ transaction_mode [, ...] ]

参数

  • ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED }
  • READ WRITE | READ ONLY
  • [ NOT ] DEFERRABLE

示例

sql 复制代码
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY;
-- 执行只读查询
COMMIT;
COMMIT / END

提交当前事务。

sql 复制代码
COMMIT [ WORK | TRANSACTION ];
ROLLBACK

回滚当前事务。

sql 复制代码
ROLLBACK [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ];

保存点命令

SAVEPOINT

在当前事务中创建一个新的保存点。

sql 复制代码
SAVEPOINT savepoint_name;
ROLLBACK TO SAVEPOINT

回滚到指定的保存点。

sql 复制代码
ROLLBACK [ WORK | TRANSACTION ] TO [ SAVEPOINT ] savepoint_name;
RELEASE SAVEPOINT

销毁一个保存点。

sql 复制代码
RELEASE [ SAVEPOINT ] savepoint_name;

会话设置

SET TRANSACTION

设置当前事务的特性。

sql 复制代码
SET TRANSACTION transaction_mode [, ...]
SET SESSION CHARACTERISTICS

设置会话的默认事务特性。

sql 复制代码
SET SESSION CHARACTERISTICS AS TRANSACTION transaction_mode [, ...]

psql 元命令

\set ON_ERROR_ROLLBACK

启用或禁用错误自动回滚。

sql 复制代码
\set ON_ERROR_ROLLBACK on
\set ON_ERROR_ROLLBACK off

Demo 简单示例

以下是一个完整的 Node.js 示例,使用 pg 驱动演示 PostgreSQL 事务的 ACID 特性。

运行说明

  1. 确保本地已安装 PostgreSQL 并创建测试数据库
  2. 安装依赖:npm install pg
  3. 运行脚本:node demo.js

代码

js 复制代码
const { Pool } = require('pg');

// PostgreSQL 连接配置
const pool = new Pool({
    host: 'localhost',
    port: 5432,
    database: 'testdb',
    user: 'postgres',
    password: 'your_password',
});

async function runDemo() {
    const client = await pool.connect();

    try {
        // ============================================================
        // 1. 创建测试表
        // ============================================================
        await client.query(`
            DROP TABLE IF EXISTS accounts;
            CREATE TABLE accounts (
                id SERIAL PRIMARY KEY,
                name VARCHAR(100) NOT NULL,
                balance DECIMAL(10, 2) CHECK (balance >= 0)
            );
        `);
        console.log('✓ 测试表创建成功');

        // ============================================================
        // 2. 原子性(Atomicity)演示:事务回滚
        // ============================================================
        console.log('\n--- 原子性演示:事务回滚 ---');

        await client.query('BEGIN');

        // 插入第一条数据
        await client.query(
            'INSERT INTO accounts (name, balance) VALUES ($1, $2)',
            ['Alice', 1000.00]
        );

        // 创建保存点
        await client.query('SAVEPOINT sp1');

        try {
            // 插入第二条数据(此处故意违反 CHECK 约束)
            await client.query(
                'INSERT INTO accounts (name, balance) VALUES ($1, $2)',
                ['Bob', -100.00]  // 负数余额,违反 CHECK (balance >= 0)
            );
        } catch (err) {
            console.log(`  捕获错误: ${err.message}`);
            // 回滚到保存点,保留 Alice 的插入
            await client.query('ROLLBACK TO SAVEPOINT sp1');
            console.log('  已回滚到保存点 sp1,Bob 的数据被撤销');
        }

        // 提交事务 ------ Alice 的数据被保留
        await client.query('COMMIT');

        const result1 = await client.query('SELECT * FROM accounts');
        console.log('  最终数据:', result1.rows);
        // 输出: [ { id: 1, name: 'Alice', balance: '1000.00' } ]

        // ============================================================
        // 3. 隔离性(Isolation)演示:READ COMMITTED vs REPEATABLE READ
        // ============================================================
        console.log('\n--- 隔离性演示 ---');

        // 清空表
        await client.query('TRUNCATE accounts RESTART IDENTITY');
        await client.query(
            'INSERT INTO accounts (name, balance) VALUES ($1, $2)',
            ['Charlie', 500.00]
        );

        // 3.1 READ COMMITTED(默认)
        console.log('\n  [READ COMMITTED]');

        // 事务1:开启 READ COMMITTED
        await client.query('BEGIN ISOLATION LEVEL READ COMMITTED');
        const snapshot1 = await client.query('SELECT balance FROM accounts WHERE name = $1', ['Charlie']);
        console.log(`    初始余额: ${snapshot1.rows[0].balance}`);

        // 模拟:在另一个连接中修改数据(此处用同一连接模拟)
        await client.query('SAVEPOINT mock_other_tx');
        await client.query('UPDATE accounts SET balance = balance + 100 WHERE name = $1', ['Charlie']);
        await client.query('RELEASE SAVEPOINT mock_other_tx');

        // 再次查询 ------ READ COMMITTED 看到最新提交的数据
        const snapshot2 = await client.query('SELECT balance FROM accounts WHERE name = $1', ['Charlie']);
        console.log(`    再次查询: ${snapshot2.rows[0].balance}`);
        // 输出: 600.00(看到了其他事务的提交)

        await client.query('COMMIT');

        // 3.2 REPEATABLE READ
        console.log('\n  [REPEATABLE READ]');

        // 重置数据
        await client.query('TRUNCATE accounts RESTART IDENTITY');
        await client.query(
            'INSERT INTO accounts (name, balance) VALUES ($1, $2)',
            ['Charlie', 500.00]
        );

        // 事务2:开启 REPEATABLE READ
        await client.query('BEGIN ISOLATION LEVEL REPEATABLE READ');
        const snapshot3 = await client.query('SELECT balance FROM accounts WHERE name = $1', ['Charlie']);
        console.log(`    初始余额: ${snapshot3.rows[0].balance}`);

        // 模拟其他事务修改
        await client.query('SAVEPOINT mock_other_tx2');
        await client.query('UPDATE accounts SET balance = balance + 100 WHERE name = $1', ['Charlie']);
        await client.query('RELEASE SAVEPOINT mock_other_tx2');

        // 再次查询 ------ REPEATABLE READ 看到的是快照,数据不变
        const snapshot4 = await client.query('SELECT balance FROM accounts WHERE name = $1', ['Charlie']);
        console.log(`    再次查询: ${snapshot4.rows[0].balance}`);
        // 输出: 500.00(仍然看到事务开始时的快照)

        await client.query('COMMIT');

        // ============================================================
        // 4. 持久性(Durability)验证:WAL 机制
        // ============================================================
        console.log('\n--- 持久性说明 ---');
        console.log('  PostgreSQL 通过 WAL(预写日志)保证持久性:');
        console.log('  1. 事务提交时,WAL 记录先 fsync 到磁盘');
        console.log('  2. 然后才返回 COMMIT 成功');
        console.log('  3. 数据文件的更新由 checkpoint 进程异步完成');
        console.log('  ✓ 即使宕机,已提交事务的修改可通过 WAL 恢复');

        // 查看当前 WAL 相关配置
        const walConfig = await client.query(`
            SELECT name, setting, unit
            FROM pg_settings
            WHERE name IN ('fsync', 'synchronous_commit', 'checkpoint_timeout', 'max_wal_size')
            ORDER BY name;
        `);
        console.log('\n  WAL 相关配置:');
        walConfig.rows.forEach(row => {
            console.log(`    ${row.name} = ${row.setting}${row.unit || ''}`);
        });

        console.log('\n✅ Demo 执行完成');

    } catch (err) {
        console.error('❌ 错误:', err.message);
        await client.query('ROLLBACK');
    } finally {
        client.release();
        await pool.end();
    }
}

runDemo();

PostgreSQL 原生指令注释

上述 Node.js 代码对应的 PostgreSQL 原生 SQL 指令:

sql 复制代码
-- ============================================================
-- 1. 创建测试表
-- ============================================================
DROP TABLE IF EXISTS accounts;
CREATE TABLE accounts (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    balance DECIMAL(10, 2) CHECK (balance >= 0)
);

-- ============================================================
-- 2. 原子性演示:事务与保存点
-- ============================================================
BEGIN;
INSERT INTO accounts (name, balance) VALUES ('Alice', 1000.00);
SAVEPOINT sp1;
INSERT INTO accounts (name, balance) VALUES ('Bob', -100.00);  -- 违反 CHECK
ROLLBACK TO SAVEPOINT sp1;
COMMIT;
SELECT * FROM accounts;

-- ============================================================
-- 3. 隔离性演示
-- ============================================================
TRUNCATE accounts RESTART IDENTITY;
INSERT INTO accounts (name, balance) VALUES ('Charlie', 500.00);

-- READ COMMITTED
BEGIN ISOLATION LEVEL READ COMMITTED;
SELECT balance FROM accounts WHERE name = 'Charlie';
-- 另一个事务修改: UPDATE accounts SET balance = balance + 100 WHERE name = 'Charlie';
SELECT balance FROM accounts WHERE name = 'Charlie';  -- 看到新值 600.00
COMMIT;

-- REPEATABLE READ
TRUNCATE accounts RESTART IDENTITY;
INSERT INTO accounts (name, balance) VALUES ('Charlie', 500.00);
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT balance FROM accounts WHERE name = 'Charlie';
-- 另一个事务修改: UPDATE accounts SET balance = balance + 100 WHERE name = 'Charlie';
SELECT balance FROM accounts WHERE name = 'Charlie';  -- 仍看到 500.00
COMMIT;

-- ============================================================
-- 4. 查看 WAL 配置
-- ============================================================
SELECT name, setting, unit
FROM pg_settings
WHERE name IN ('fsync', 'synchronous_commit', 'checkpoint_timeout', 'max_wal_size')
ORDER BY name;

技术点总结

技术点 说明
BEGIN / COMMIT / ROLLBACK 标准事务控制命令
SAVEPOINT / ROLLBACK TO SAVEPOINT 子事务与保存点机制,实现部分回滚
ISOLATION LEVEL 事务隔离级别控制
READ COMMITTED 默认隔离级别,语句级别快照
REPEATABLE READ 事务级别快照,避免不可重复读
MVCC 多版本并发控制,读写不互斥
WAL 预写日志,保证持久性
fsync 强制刷盘,确保持久化
CHECK 约束 保证数据一致性

多语言示例

基于上述 Node.js 的 ACID 演示逻辑,本节提供 Go、Python 和 Java 三种语言的完整实现,并附多语言对比表格,便于团队在不同技术栈间迁移与参考。

Go 示例

使用 pgx 驱动(推荐的高性能 PostgreSQL 驱动)。

运行说明
  1. 安装依赖:go get github.com/jackc/pgx/v5
  2. 运行:go run main.go
代码
go 复制代码
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/jackc/pgx/v5"
)

func main() {
	ctx := context.Background()

	// 连接配置
	conn, err := pgx.Connect(ctx, "postgres://postgres:your_password@localhost:5432/testdb")
	if err != nil {
		log.Fatal("连接失败:", err)
	}
	defer conn.Close(ctx)

	// 1. 创建测试表
	_, err = conn.Exec(ctx, `
		DROP TABLE IF EXISTS accounts;
		CREATE TABLE accounts (
			id SERIAL PRIMARY KEY,
			name VARCHAR(100) NOT NULL,
			balance DECIMAL(10, 2) CHECK (balance >= 0)
		);
	`)
	if err != nil {
		log.Fatal("建表失败:", err)
	}
	fmt.Println("✓ 测试表创建成功")

	// 2. 原子性演示:事务与保存点
	fmt.Println("\n--- 原子性演示:事务回滚 ---")

	// 开启事务
	tx, err := conn.Begin(ctx)
	if err != nil {
		log.Fatal(err)
	}
	defer tx.Rollback(ctx)

	// 插入 Alice
	_, err = tx.Exec(ctx, "INSERT INTO accounts (name, balance) VALUES ($1, $2)", "Alice", 1000.00)
	if err != nil {
		log.Fatal(err)
	}

	// 创建保存点
	_, err = tx.Exec(ctx, "SAVEPOINT sp1")
	if err != nil {
		log.Fatal(err)
	}

	// 尝试插入 Bob(负数余额,违反 CHECK)
	_, err = tx.Exec(ctx, "INSERT INTO accounts (name, balance) VALUES ($1, $2)", "Bob", -100.00)
	if err != nil {
		fmt.Printf("  捕获错误: %v\n", err)
		// 回滚到保存点
		_, rbErr := tx.Exec(ctx, "ROLLBACK TO SAVEPOINT sp1")
		if rbErr != nil {
			log.Fatal(rbErr)
		}
		fmt.Println("  已回滚到保存点 sp1,Bob 的数据被撤销")
	}

	// 提交事务
	err = tx.Commit(ctx)
	if err != nil {
		log.Fatal(err)
	}

	// 查询结果
	var rows []struct {
		ID      int
		Name    string
		Balance float64
	}
	result, err := conn.Query(ctx, "SELECT * FROM accounts")
	if err != nil {
		log.Fatal(err)
	}
	defer result.Close()
	for result.Next() {
		var r struct {
			ID      int
			Name    string
			Balance float64
		}
		err = result.Scan(&r.ID, &r.Name, &r.Balance)
		if err != nil {
			log.Fatal(err)
		}
		rows = append(rows, r)
	}
	fmt.Println("  最终数据:", rows)
	// 输出: [{1 Alice 1000}]

	// 3. 隔离性演示
	fmt.Println("\n--- 隔离性演示 ---")

	// 清空并准备数据
	_, err = conn.Exec(ctx, "TRUNCATE accounts RESTART IDENTITY")
	if err != nil {
		log.Fatal(err)
	}
	_, err = conn.Exec(ctx, "INSERT INTO accounts (name, balance) VALUES ($1, $2)", "Charlie", 500.00)
	if err != nil {
		log.Fatal(err)
	}

	// 3.1 READ COMMITTED
	fmt.Println("\n  [READ COMMITTED]")
	txRC, err := conn.Begin(ctx)
	if err != nil {
		log.Fatal(err)
	}
	defer txRC.Rollback(ctx)

	// 设置隔离级别
	_, err = txRC.Exec(ctx, "SET TRANSACTION ISOLATION LEVEL READ COMMITTED")
	if err != nil {
		log.Fatal(err)
	}

	var balance float64
	err = txRC.QueryRow(ctx, "SELECT balance FROM accounts WHERE name = $1", "Charlie").Scan(&balance)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("    初始余额: %.2f\n", balance)

	// 模拟另一个事务修改(同一连接内用保存点模拟)
	_, err = txRC.Exec(ctx, "SAVEPOINT mock_other")
	if err != nil {
		log.Fatal(err)
	}
	_, err = txRC.Exec(ctx, "UPDATE accounts SET balance = balance + 100 WHERE name = $1", "Charlie")
	if err != nil {
		log.Fatal(err)
	}
	_, err = txRC.Exec(ctx, "RELEASE SAVEPOINT mock_other")
	if err != nil {
		log.Fatal(err)
	}

	// 再次查询 ------ READ COMMITTED 看到新值
	err = txRC.QueryRow(ctx, "SELECT balance FROM accounts WHERE name = $1", "Charlie").Scan(&balance)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("    再次查询: %.2f\n", balance) // 600.00
	txRC.Commit(ctx)

	// 3.2 REPEATABLE READ
	fmt.Println("\n  [REPEATABLE READ]")
	_, err = conn.Exec(ctx, "TRUNCATE accounts RESTART IDENTITY")
	if err != nil {
		log.Fatal(err)
	}
	_, err = conn.Exec(ctx, "INSERT INTO accounts (name, balance) VALUES ($1, $2)", "Charlie", 500.00)
	if err != nil {
		log.Fatal(err)
	}

	txRR, err := conn.Begin(ctx)
	if err != nil {
		log.Fatal(err)
	}
	defer txRR.Rollback(ctx)

	_, err = txRR.Exec(ctx, "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")
	if err != nil {
		log.Fatal(err)
	}

	err = txRR.QueryRow(ctx, "SELECT balance FROM accounts WHERE name = $1", "Charlie").Scan(&balance)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("    初始余额: %.2f\n", balance)

	// 模拟其他事务修改
	_, err = txRR.Exec(ctx, "SAVEPOINT mock_other2")
	if err != nil {
		log.Fatal(err)
	}
	_, err = txRR.Exec(ctx, "UPDATE accounts SET balance = balance + 100 WHERE name = $1", "Charlie")
	if err != nil {
		log.Fatal(err)
	}
	_, err = txRR.Exec(ctx, "RELEASE SAVEPOINT mock_other2")
	if err != nil {
		log.Fatal(err)
	}

	// 再次查询 ------ REPEATABLE READ 仍看到旧值
	err = txRR.QueryRow(ctx, "SELECT balance FROM accounts WHERE name = $1", "Charlie").Scan(&balance)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("    再次查询: %.2f\n", balance) // 500.00
	txRR.Commit(ctx)

	// 4. WAL 配置查询
	fmt.Println("\n--- 持久性说明 ---")
	fmt.Println("  PostgreSQL 通过 WAL(预写日志)保证持久性")
	var walRows []struct {
		Name    string
		Setting string
		Unit    string
	}
	wals, err := conn.Query(ctx, `
		SELECT name, setting, unit
		FROM pg_settings
		WHERE name IN ('fsync', 'synchronous_commit', 'checkpoint_timeout', 'max_wal_size')
		ORDER BY name;
	`)
	if err != nil {
		log.Fatal(err)
	}
	defer wals.Close()
	fmt.Println("  WAL 相关配置:")
	for wals.Next() {
		var name, setting, unit string
		err = wals.Scan(&name, &setting, &unit)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("    %s = %s%s\n", name, setting, unit)
	}
}

Python 示例

使用 psycopg2(最流行的 PostgreSQL 适配器)。

运行说明
  1. 安装依赖:pip install psycopg2-binary
  2. 运行:python demo.py
代码
python 复制代码
import psycopg2
from psycopg2 import sql, errors

def main():
    # 连接配置
    conn = psycopg2.connect(
        host="localhost",
        port=5432,
        database="testdb",
        user="postgres",
        password="your_password"
    )
    conn.autocommit = False
    cur = conn.cursor()

    try:
        # 1. 创建测试表
        cur.execute("DROP TABLE IF EXISTS accounts;")
        cur.execute("""
            CREATE TABLE accounts (
                id SERIAL PRIMARY KEY,
                name VARCHAR(100) NOT NULL,
                balance DECIMAL(10, 2) CHECK (balance >= 0)
            );
        """)
        conn.commit()
        print("✓ 测试表创建成功")

        # 2. 原子性演示
        print("\n--- 原子性演示:事务回滚 ---")
        cur.execute("BEGIN")
        cur.execute("INSERT INTO accounts (name, balance) VALUES (%s, %s)", ("Alice", 1000.00))
        cur.execute("SAVEPOINT sp1")
        try:
            cur.execute("INSERT INTO accounts (name, balance) VALUES (%s, %s)", ("Bob", -100.00))
        except errors.CheckViolation as e:
            print(f"  捕获错误: {e}")
            cur.execute("ROLLBACK TO SAVEPOINT sp1")
            print("  已回滚到保存点 sp1,Bob 的数据被撤销")
        cur.execute("COMMIT")
        cur.execute("SELECT * FROM accounts")
        rows = cur.fetchall()
        print("  最终数据:", rows)

        # 3. 隔离性演示
        print("\n--- 隔离性演示 ---")
        cur.execute("TRUNCATE accounts RESTART IDENTITY")
        cur.execute("INSERT INTO accounts (name, balance) VALUES (%s, %s)", ("Charlie", 500.00))
        conn.commit()

        # 3.1 READ COMMITTED
        print("\n  [READ COMMITTED]")
        cur.execute("BEGIN ISOLATION LEVEL READ COMMITTED")
        cur.execute("SELECT balance FROM accounts WHERE name = %s", ("Charlie",))
        balance = cur.fetchone()[0]
        print(f"    初始余额: {balance}")

        # 模拟其他事务
        cur.execute("SAVEPOINT mock_other")
        cur.execute("UPDATE accounts SET balance = balance + 100 WHERE name = %s", ("Charlie",))
        cur.execute("RELEASE SAVEPOINT mock_other")
        cur.execute("SELECT balance FROM accounts WHERE name = %s", ("Charlie",))
        balance = cur.fetchone()[0]
        print(f"    再次查询: {balance}")  # 600.00
        cur.execute("COMMIT")

        # 3.2 REPEATABLE READ
        print("\n  [REPEATABLE READ]")
        cur.execute("TRUNCATE accounts RESTART IDENTITY")
        cur.execute("INSERT INTO accounts (name, balance) VALUES (%s, %s)", ("Charlie", 500.00))
        conn.commit()

        cur.execute("BEGIN ISOLATION LEVEL REPEATABLE READ")
        cur.execute("SELECT balance FROM accounts WHERE name = %s", ("Charlie",))
        balance = cur.fetchone()[0]
        print(f"    初始余额: {balance}")

        cur.execute("SAVEPOINT mock_other2")
        cur.execute("UPDATE accounts SET balance = balance + 100 WHERE name = %s", ("Charlie",))
        cur.execute("RELEASE SAVEPOINT mock_other2")
        cur.execute("SELECT balance FROM accounts WHERE name = %s", ("Charlie",))
        balance = cur.fetchone()[0]
        print(f"    再次查询: {balance}")  # 500.00
        cur.execute("COMMIT")

        # 4. WAL 配置
        print("\n--- 持久性说明 ---")
        print("  PostgreSQL 通过 WAL(预写日志)保证持久性")
        cur.execute("""
            SELECT name, setting, unit
            FROM pg_settings
            WHERE name IN ('fsync', 'synchronous_commit', 'checkpoint_timeout', 'max_wal_size')
            ORDER BY name;
        """)
        print("  WAL 相关配置:")
        for name, setting, unit in cur.fetchall():
            print(f"    {name} = {setting}{unit or ''}")

    except Exception as e:
        print(f"❌ 错误: {e}")
        conn.rollback()
    finally:
        cur.close()
        conn.close()

if __name__ == "__main__":
    main()

Java 示例

使用 HikariCP 连接池和 JDBC 原生 API。

运行说明
  1. 添加依赖(Maven):

    xml 复制代码
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>42.6.0</version>
    </dependency>
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>5.0.1</version>
    </dependency>
  2. 编译并运行:javac -cp ".:postgresql-42.6.0.jar:HikariCP-5.0.1.jar:slf4j-api-*.jar" Demo.java && java -cp ".:postgresql-42.6.0.jar:HikariCP-5.0.1.jar:slf4j-api-*.jar" Demo

代码
java 复制代码
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.*;

public class Demo {
    public static void main(String[] args) {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:postgresql://localhost:5432/testdb");
        config.setUsername("postgres");
        config.setPassword("your_password");
        config.setAutoCommit(false);

        try (HikariDataSource ds = new HikariDataSource(config);
             Connection conn = ds.getConnection()) {

            // 1. 创建测试表
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("DROP TABLE IF EXISTS accounts");
                stmt.execute("""
                    CREATE TABLE accounts (
                        id SERIAL PRIMARY KEY,
                        name VARCHAR(100) NOT NULL,
                        balance DECIMAL(10, 2) CHECK (balance >= 0)
                    );
                """);
                conn.commit();
                System.out.println("✓ 测试表创建成功");
            }

            // 2. 原子性演示
            System.out.println("\n--- 原子性演示:事务回滚 ---");
            try (Statement stmt = conn.createStatement()) {
                conn.setAutoCommit(false);
                stmt.execute("INSERT INTO accounts (name, balance) VALUES ('Alice', 1000.00)");
                stmt.execute("SAVEPOINT sp1");
                try {
                    stmt.execute("INSERT INTO accounts (name, balance) VALUES ('Bob', -100.00)");
                } catch (SQLException e) {
                    System.out.println("  捕获错误: " + e.getMessage());
                    stmt.execute("ROLLBACK TO SAVEPOINT sp1");
                    System.out.println("  已回滚到保存点 sp1,Bob 的数据被撤销");
                }
                conn.commit();

                ResultSet rs = stmt.executeQuery("SELECT * FROM accounts");
                System.out.print("  最终数据: ");
                while (rs.next()) {
                    System.out.print("[id=" + rs.getInt("id") +
                                     ", name=" + rs.getString("name") +
                                     ", balance=" + rs.getBigDecimal("balance") + "] ");
                }
                System.out.println();
            }

            // 3. 隔离性演示
            System.out.println("\n--- 隔离性演示 ---");
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("TRUNCATE accounts RESTART IDENTITY");
                stmt.execute("INSERT INTO accounts (name, balance) VALUES ('Charlie', 500.00)");
                conn.commit();

                // 3.1 READ COMMITTED
                System.out.println("\n  [READ COMMITTED]");
                stmt.execute("BEGIN ISOLATION LEVEL READ COMMITTED");
                ResultSet rs = stmt.executeQuery("SELECT balance FROM accounts WHERE name = 'Charlie'");
                rs.next();
                System.out.println("    初始余额: " + rs.getBigDecimal("balance"));

                stmt.execute("SAVEPOINT mock_other");
                stmt.execute("UPDATE accounts SET balance = balance + 100 WHERE name = 'Charlie'");
                stmt.execute("RELEASE SAVEPOINT mock_other");

                rs = stmt.executeQuery("SELECT balance FROM accounts WHERE name = 'Charlie'");
                rs.next();
                System.out.println("    再次查询: " + rs.getBigDecimal("balance")); // 600.00
                stmt.execute("COMMIT");

                // 3.2 REPEATABLE READ
                System.out.println("\n  [REPEATABLE READ]");
                stmt.execute("TRUNCATE accounts RESTART IDENTITY");
                stmt.execute("INSERT INTO accounts (name, balance) VALUES ('Charlie', 500.00)");
                conn.commit();

                stmt.execute("BEGIN ISOLATION LEVEL REPEATABLE READ");
                rs = stmt.executeQuery("SELECT balance FROM accounts WHERE name = 'Charlie'");
                rs.next();
                System.out.println("    初始余额: " + rs.getBigDecimal("balance"));

                stmt.execute("SAVEPOINT mock_other2");
                stmt.execute("UPDATE accounts SET balance = balance + 100 WHERE name = 'Charlie'");
                stmt.execute("RELEASE SAVEPOINT mock_other2");

                rs = stmt.executeQuery("SELECT balance FROM accounts WHERE name = 'Charlie'");
                rs.next();
                System.out.println("    再次查询: " + rs.getBigDecimal("balance")); // 500.00
                stmt.execute("COMMIT");
            }

            // 4. WAL 配置查询
            System.out.println("\n--- 持久性说明 ---");
            System.out.println("  PostgreSQL 通过 WAL(预写日志)保证持久性");
            try (Statement stmt = conn.createStatement()) {
                ResultSet rs = stmt.executeQuery("""
                    SELECT name, setting, unit
                    FROM pg_settings
                    WHERE name IN ('fsync', 'synchronous_commit', 'checkpoint_timeout', 'max_wal_size')
                    ORDER BY name;
                """);
                System.out.println("  WAL 相关配置:");
                while (rs.next()) {
                    String name = rs.getString("name");
                    String setting = rs.getString("setting");
                    String unit = rs.getString("unit");
                    System.out.printf("    %s = %s%s%n", name, setting, unit == null ? "" : unit);
                }
            }

        } catch (SQLException e) {
            System.err.println("❌ 错误: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

多语言对比表格

维度 Node.js (pg) Go (pgx) Python (psycopg2) Java (JDBC + HikariCP)
驱动/库 pg (纯 JavaScript) pgx (纯 Go,高性能) psycopg2-binary (C 扩展) postgresql JDBC 驱动
连接管理 Pool 内置连接池 手动管理或使用 pgxpool 手动管理或使用 psycopg2.pool HikariCP (高性能连接池)
事务开始 client.query('BEGIN')client.query('START TRANSACTION') conn.Begin(ctx) cur.execute("BEGIN") conn.setAutoCommit(false)
保存点 client.query('SAVEPOINT name') tx.Exec(ctx, "SAVEPOINT name") cur.execute("SAVEPOINT name") stmt.execute("SAVEPOINT name")
回滚到保存点 ROLLBACK TO SAVEPOINT name tx.Exec(ctx, "ROLLBACK TO SAVEPOINT name") ROLLBACK TO SAVEPOINT name ROLLBACK TO SAVEPOINT name
提交 client.query('COMMIT') tx.Commit(ctx) conn.commit() conn.commit()
错误处理 try/catch,错误对象包含 SQLSTATE error 返回值,可断言 pgx.PgError try/except,具体异常类(如 CheckViolation try/catchSQLException 含 SQLState
占位符 $1, $2 (位置参数) $1, $2 (与 PostgreSQL 原生一致) %s 并用 cur.execute(sql, (val,)) ? (JDBC 标准) 或 $1 (需配置)
异步支持 原生 async/await 原生 context + 协程 psycopg2 异步版本 (psycopg2-async) 或 asyncpg CompletableFuture + 异步驱动(如 vertx-pg-client
连接字符串 postgresql://user:pass@host:port/db 相同 关键词参数或 DSN JDBC URL: jdbc:postgresql://host:port/db
设置隔离级别 SET TRANSACTION ISOLATION LEVEL ... 同 SQL 同 SQL 同 SQL 或 conn.setTransactionIsolation()
类型映射 自动映射 JavaScript 类型 自动映射 Go 类型,支持 pgx 自定义 映射 Python 类型(Decimal -> decimal.Decimal 通过 ResultSet.getXxx() 映射 Java 类型

技术点总结(多语言通用)

核心技术 各语言实现方式
BEGIN / COMMIT / ROLLBACK 所有驱动均支持标准 SQL 或 API 方法
SAVEPOINT / ROLLBACK TO SAVEPOINT 均支持标准 SQL,部分驱动(如 JDBC)也提供 Savepoint 对象
隔离级别设置 可通过 SQL SET TRANSACTION 或驱动 API
错误处理 各语言都有对应的异常/错误类型,可识别约束违例等
连接池 Node.js 用 pg.Pool,Go 用 pgxpool,Python 用 psycopg2.pool,Java 用 HikariCP
参数化查询 防止 SQL 注入,所有驱动均支持

官方文档

参考链接

总结

PostgreSQL 通过 MVCCWAL 两项核心技术全面保证了 ACID 特性。原子性由事务日志与 CLOG 保证,一致性由数据类型、约束和触发器保证,隔离性由 MVCC、锁机制、SSI 和快照视图保证,持久性由 WALfsynccheckpoint 保证。

子事务(SAVEPOINT)虽提供了细粒度的错误处理能力,但每个子事务都会消耗事务 ID,且超过 64 个时会引发性能问题,生产环境应谨慎使用。ON_ERROR_ROLLBACKJDBCautosave 本质上也是子事务机制的应用,需注意其性能代价。

相关推荐
u0103055271 小时前
正则表达式find与matches区别详解
笔记
勇往直前plus1 小时前
Docker PostgreSQL 16 部署要点(Ubuntu 22)
ubuntu·docker·postgresql
浪兎兎1 小时前
Nginx 笔记
运维·笔记·nginx
湖南源点调研咨询2 小时前
湖南(源点)市场调研 服务标准化建立指南与切入方式(中篇)
大数据·产品运营·用户运营·用户体验
_Narcissus_2 小时前
二分算法笔记及例题
数据结构·c++·笔记·算法·蓝桥杯·查找·二分算法
Hammer_Hans2 小时前
DFT笔记112
笔记
FAREWELL000752 小时前
学习笔记-热更新导航
笔记·学习
AI_Auto3 小时前
中小企业数字化转型指南 | 拆解中小企业数字化四大转型特征
大数据·人工智能·制造
资深低代码开发平台专家3 小时前
2026企业级AI编程平台厂推荐:行业趋势、评估维度与主流品牌对比解析
大数据·人工智能·ai编程