纲要
ACID事务特性概述Atomicity(原子性)Consistency(一致性)Isolation(隔离性)Durability(持久性)
原子性(Atomicity)与子事务(Subtransaction)SAVEPOINT与子事务机制psql的ON_ERROR_ROLLBACK行为- 子事务的性能代价与资源消耗
JDBC中的autosave参数
一致性(Consistency)的保障机制隔离性(Isolation)与MVCC- 四种标准隔离级别
- PostgreSQL 的隔离级别实现
MVCC(多版本并发控制)机制SSI(可串行化快照隔离)
持久性(Durability)与WALWAL(预写日志)机制fsync与checkpoint- 流复制场景中的同步复制
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;
子事务的本质是将一个事务拆分为多个更小的事务单元,但它仍然属于同一个顶层事务,不会破坏事务的原子性。
子事务的性能代价
子事务虽然提供了灵活的细粒度错误处理能力,但其使用需要谨慎评估:
-
事务 ID 消耗 :每个子事务都会分配一个永久事务 ID(
XID)。事务 ID 在 PostgreSQL 中是一种有限资源。有实测表明,仅插入 5000 条数据,若每条都使用子事务,可能消耗 64 万个XID,这会直接导致autovacuum freeze提前到来。 -
缓存溢出 :当一个会话中的子事务超过 64 个时,会触发
overflow状态,性能会急剧下降。 -
查找性能退化 :在存在大量活跃子事务的场景中,
TransactionIdIsCurrentTransactionId函数的查找会退化为对链表的线性扫描。PostgreSQL 社区有报告显示,在包含 1000 个保存点的测试中,性能极度缓慢,优化补丁可将性能提升约 10 倍。 -
日志膨胀:子事务为记录保存点元数据,产生的日志量可能达到普通操作的数万倍。
-
锁竞争 :当并发事务较多时,子事务可能引入
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 在内部仅实现了三种不同的隔离级别:
READ COMMITTED:PostgreSQL 的默认隔离级别。事务中的每条查询只能看到查询开始前已提交的数据。同一事务中的两条SELECT语句可能看到不同的数据。
sql
-- 设置隔离级别为 READ COMMITTED
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
REPEATABLE READ:事务中的所有语句只能看到事务中第一个查询或数据修改语句执行前已提交的数据。PostgreSQL 的REPEATABLE READ不允许幻读。
sql
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SERIALIZABLE:最严格的隔离级别。并发事务的执行效果必须与串行执行的效果一致。如果并发读写模式会导致不可串行化的结果,其中一个事务将被回滚并报serialization_failure错误。
sql
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
值得注意的是,在 PostgreSQL 中请求 READ UNCOMMITTED 级别时,实际行为等同于 READ COMMITTED。
MVCC(多版本并发控制)
PostgreSQL 的隔离性主要通过 MVCC 实现。其核心原理是:
- 数据文件中存放同一逻辑行的多个版本(称为
Tuple)。 - 每个行版本的头部记录了创建和删除该版本的事务 ID(分别称为
xmin和xmax)。 - 每个事务在执行时获取一个数据库快照,根据快照中的事务状态信息决定哪些行版本可见。
- 读者不会阻塞写者,写者也不会阻塞读者,从而极大提高了并发性能。
#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 会执行 fsync 将 WAL 刷写到磁盘,然后才返回提交成功。数据文件的更新是延迟进行的,由后台的 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 特性。
运行说明
- 确保本地已安装 PostgreSQL 并创建测试数据库
- 安装依赖:
npm install pg - 运行脚本:
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 驱动)。
运行说明
- 安装依赖:
go get github.com/jackc/pgx/v5 - 运行:
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 适配器)。
运行说明
- 安装依赖:
pip install psycopg2-binary - 运行:
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。
运行说明
-
添加依赖(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> -
编译并运行:
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/catch,SQLException 含 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 官方文档 - 事务隔离
- PostgreSQL 官方文档 - SET TRANSACTION
- PostgreSQL 官方文档 - 并发控制 (MVCC)
- PostgreSQL 官方文档 - 预写日志 (WAL)
- PostgreSQL 官方文档 - psql ON_ERROR_ROLLBACK
参考链接
总结
PostgreSQL 通过 MVCC 和 WAL 两项核心技术全面保证了 ACID 特性。原子性由事务日志与 CLOG 保证,一致性由数据类型、约束和触发器保证,隔离性由 MVCC、锁机制、SSI 和快照视图保证,持久性由 WAL、fsync 和 checkpoint 保证。
子事务(SAVEPOINT)虽提供了细粒度的错误处理能力,但每个子事务都会消耗事务 ID,且超过 64 个时会引发性能问题,生产环境应谨慎使用。ON_ERROR_ROLLBACK 和 JDBC 的 autosave 本质上也是子事务机制的应用,需注意其性能代价。