事务自动提交
自动提交(autocommit)指「每条语句被自动包进一个独立事务,执行完立即提交」。PostgreSQL 服务端默认就是这种模式:你敲一条 UPDATE,它就自动 BEGIN; UPDATE; COMMIT;,无需手动提交。所有客户端驱动与 ORM 的「事务开关」都建立在服务端这个行为之上——理解它才能明白为什么 psql 里一条语句失败了不用清理、而 JDBC 里忘记 commit() 数据就消失了。
前置知识
阅读本节前,建议先了解:BEGIN / COMMIT / ROLLBACK、事务的 ACID 特性
基础概念
「自动提交」不是 PostgreSQL 的一个参数,而是服务端在没有显式事务块时的默认语义:
| 事实 | 说明 |
|---|---|
服务端没有 autocommit 参数 | 客户端协议层也不存在自动提交开关,它是「隐式事务」的通俗叫法 |
| 每条语句自带事务边界 | 语句前隐式 BEGIN,成功后隐式 COMMIT,失败则隐式 ROLLBACK |
BEGIN 一旦发出就进入显式事务 | 此后语句不再立即提交,直到 COMMIT / ROLLBACK |
| 自动提交是语句级原子性的来源 | 单条语句内的部分修改不会「写一半」 |
| 事务块内失败会污染整个事务 | 与自动提交模式不同,此时需 ROLLBACK 或保存点 |
| DDL 也遵循同样规则 | CREATE TABLE / ALTER TABLE 可回滚(与 MySQL 的隐式提交不同) |
| 部分命令禁止在事务块中执行 | VACUUM、CREATE INDEX CONCURRENTLY、REINDEX CONCURRENTLY 等 |
客户端可用 AUTOCOMMIT 变量接管 | psql 用 \set AUTOCOMMIT off,驱动用 setAutoCommit(false) |
一个关键区分:服务端没有「关闭自动提交」的说法,只有「我开一个显式事务」。客户端所谓的「关闭自动提交」,本质是驱动在语句前替你发 BEGIN,在 commit() 时替你发 COMMIT:
服务端视角(客户端「关闭自动提交」时的实际报文流):
客户端 setAutoCommit(false)
│
├─ 调 exec("UPDATE ...") → 驱动先发 BEGIN,再发 UPDATE
├─ 再调 exec("UPDATE ...") → 直接发 UPDATE(已在事务内)
├─ 调 commit() → 驱动发 COMMIT
└─ 若中途异常未回滚 → 连接上残留一个「悬挂事务」语法与用法
服务端层面没有 autocommit 语法,相关控制都发生在客户端:
-- psql:关闭自动提交(等价于在需要时自动 BEGIN)
\set AUTOCOMMIT off
\set AUTOCOMMIT on
-- psql:语句报错时自动建立隐式保存点,使单条语句失败不毁掉整个事务
\set ON_ERROR_ROLLBACK on
\set ON_ERROR_ROLLBACK interactive
\set ON_ERROR_ROLLBACK off
-- psql:遇错即停(脚本模式下非常重要)
\set ON_ERROR_STOP on最简可运行示例——同一批语句,在自动提交开/关两种模式下的差别:
-- 自动提交状态:每条语句独立提交
INSERT INTO t (id) VALUES (1);
INSERT INTO t (id) VALUES (2);
INSERT INTO t (id) VALUES (1); -- 主键冲突,只回滚这一条
INSERT INTO t (id) VALUES (3); -- 仍能成功INSERT 0 1
INSERT 0 1
ERROR: duplicate key value violates unique constraint "t_pkey"
DETAIL: Key (id)=(1) already exists.
INSERT 0 1-- 显式事务中:一条失败会让整个事务进入 aborted 状态
BEGIN;
INSERT INTO t (id) VALUES (4);
INSERT INTO t (id) VALUES (1); -- 冲突
INSERT INTO t (id) VALUES (5); -- 错误!整个事务已作废
COMMIT; -- 实际等价于 ROLLBACKBEGIN
INSERT 0 1
ERROR: duplicate key value violates unique constraint "t_pkey"
DETAIL: Key (id)=(1) already exists.
ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACK详细说明
服务端的隐式事务:每条语句都是一个事务
PostgreSQL 服务端没有 autocommit 参数,可在 pg_settings 里搜不到它。它的实现方式是:当一个简单查询(simple query)到达且当前不处于事务块中时,后端把它包成「隐式事务」——执行前分配事务上下文,语句成功后提交。
-- 确认服务端没有 autocommit 这个参数
SELECT name, setting FROM pg_settings WHERE name LIKE '%autocommit%'; name | setting
------+---------
(0 rows)-- 观察隐式事务的效果:单条语句多行写入,只要一行失败整句都不生效
CREATE TABLE t (id int PRIMARY KEY);
INSERT INTO t VALUES (1), (2), (1), (3);CREATE TABLE
ERROR: duplicate key value violates unique constraint "t_pkey"
DETAIL: Key (id)=(1) already exists.SELECT * FROM t ORDER BY id; -- 空表:整条语句被回滚 id
----
(0 rows)这就是语句级原子性:INSERT INTO t VALUES (1),(2),(1),(3) 是一句,它要么四行全进,要么一行都不进。注意它不等于多语句原子性——想让多条语句一起成功或一起失败,必须显式 BEGIN。
-- 通过协议层观察:一条扩展开的语句总在自己的事务里
SELECT pg_current_xact_id_if_assigned() AS xid; xid
-----
(1 row)(返回 NULL 是因为单纯的 SELECT 不需要分配 XID——这也是 PostgreSQL 的惰性分配优化。)
psql 的 AUTOCOMMIT:一个纯客户端的「替你做 BEGIN」
psql 提供了 AUTOCOMMIT 变量,默认 on。把它设成 off 后,psql 会在你第一次执行语句时自动发一条 BEGIN,你之后的语句都落在同一个事务里,直到你手动 COMMIT / ROLLBACK:
mydb=# \set AUTOCOMMIT off
mydb=*# INSERT INTO t VALUES (10);
INSERT 0 1
mydb=*# INSERT INTO t VALUES (11);
INSERT 0 1
mydb=*# \echo 注意提示符已变成 *,表示处在事务中
mydb=*# ROLLBACK;
ROLLBACK
mydb=# SELECT count(*) FROM t;
count
-------
0
(1 row)要点:
- 提示符
*表示「事务中」(psql 默认的%R会显示:=无事务、*事务中、!事务失败、?状态不确定)。这是判断自己是否「悬挂在事务里」的最快方式。 \set AUTOCOMMIT off不会立即发BEGIN,而是等你的下一条语句。所以在off状态下不执行任何语句,事务并未开始。AUTOCOMMIT on时手动BEGIN依然有效:显式事务优先级更高,AUTOCOMMIT只在「没有事务时」起作用。
mydb=# \set AUTOCOMMIT on
mydb=# BEGIN;
BEGIN
mydb=*# INSERT INTO t VALUES (20);
INSERT 0 1
mydb=*# COMMIT; -- 手动结束,psql 不会自动提交
COMMITON_ERROR_ROLLBACK:让自动提交模式下「单句失败不污染事务」
这是 psql 最实用的开关之一。默认情况下,一旦进入事务块,任何语句报错都会让事务变成 aborted,后续语句全部报 25P02。\set ON_ERROR_ROLLBACK on 会让 psql 在每条语句前建立隐式保存点,语句失败时自动 ROLLBACK TO SAVEPOINT,于是单句失败不影响事务其余部分:
mydb=# \set ON_ERROR_ROLLBACK on
mydb=# BEGIN;
BEGIN
mydb=*# INSERT INTO t VALUES (1);
INSERT 0 1
mydb=*# INSERT INTO t VALUES (1);
ERROR: duplicate key value violates unique constraint "t_pkey"
mydb=*# INSERT INTO t VALUES (2); -- 依然成功
INSERT 0 1
mydb=*# COMMIT;
COMMIT代价非常明确——每条语句都会创建子事务(对应一个隐式 SAVEPOINT)。在大量语句的脚本里开启它,会显著变慢,甚至触发子事务溢出。因此它的合理取值是:
| 取值 | 行为 | 适用场景 |
|---|---|---|
off(默认) | 不建立隐式保存点 | 脚本、生产批处理 |
on | 每条语句前建保存点 | 交互式调试 |
interactive | 仅在交互输入时建保存点,脚本文件不建 | 日常开发(推荐) |
mydb=# \set ON_ERROR_ROLLBACK interactiveON_ERROR_STOP:脚本模式下的必备开关
默认情况下 psql 遇到错误会继续执行剩余语句,并且最终退出码仍可能是 0。这在自动化脚本里非常危险——错误会被静默吞掉。\set ON_ERROR_STOP on 让 psql 在第一个错误时就中止并返回非零退出码:
# 未设置 ON_ERROR_STOP:错误被吞掉,脚本「假装成功」
psql -d mydb -c "INSERT INTO t VALUES (1);"
psql -d mydb -c "INSERT INTO t VALUES (1);" # 报错但退出码仍为 0
echo "exit code = $?"exit code = 0# 推荐:脚本中显式开启,或用 -v 传入
psql -d mydb -v ON_ERROR_STOP=1 -f migrate.sql
echo "exit code = $?"exit code = 1也可以用 -1 / --single-transaction 让整个脚本在一个事务里执行,天然获得「要么全成、要么全败」的语义(要求脚本里不能有 VACUUM 这类禁止在事务块中执行的命令):
psql -d mydb -v ON_ERROR_STOP=1 --single-transaction -f migrate.sqlJDBC:setAutoCommit(false) 与忘记 commit 的数据丢失
JDBC 默认 autoCommit = true,与 PostgreSQL 服务端默认行为一致:每条 executeUpdate 后立即提交。为了让多条语句组成一个事务,需要显式关闭它:
// JDBC:标准的显式事务写法
try (Connection conn = dataSource.getConnection()) {
conn.setAutoCommit(false); // 关闭自动提交
try (PreparedStatement ps = conn.prepareStatement(
"UPDATE accounts SET balance = balance - ? WHERE id = ?")) {
ps.setBigDecimal(1, new BigDecimal("100.00"));
ps.setLong(2, 1);
ps.executeUpdate();
ps.setBigDecimal(1, new BigDecimal("100.00"));
ps.setLong(2, 2);
ps.executeUpdate();
conn.commit(); // 必须显式提交
} catch (SQLException e) {
conn.rollback(); // 失败必须回滚
throw e;
} finally {
conn.setAutoCommit(true); // 归还连接前恢复默认
}
}最容易踩的坑是忘记 commit():程序没有报错,但数据没了。原因是连接关闭时(或连接池归还时)PostgreSQL 会对未结束的事务执行隐式 ROLLBACK:
客户端 服务端
│ setAutoCommit(false)
│ UPDATE ... ─────────► BEGIN; UPDATE ... (事务未结束)
│ (未 commit)
│ conn.close() ─────────► ROLLBACK (数据被丢弃)finally 中恢复 setAutoCommit(true) 同样重要:尽管 JDBC 规范说这会隐式提交当前事务,但不同连接池实现细节不一,依赖它提交是不可靠的——显式 commit() 才是唯一正确做法。
Python(psycopg):commit 是显式的,close 不等于提交
psycopg(psycopg3)与 JDBC 差异很大:它默认 autocommit = False,也就是说所有语句默认落在一个事务里,必须手动 commit():
# psycopg3:默认就在事务中,必须显式提交
import psycopg
with psycopg.connect("dbname=mydb") as conn:
with conn.cursor() as cur:
cur.execute("UPDATE accounts SET balance = balance - %s WHERE id = %s", (100, 1))
cur.execute("UPDATE accounts SET balance = balance + %s WHERE id = %s", (100, 2))
conn.commit() # 关键:不写这一句,with 块退出时会回滚with psycopg.connect(...) 的语义是「退出时若未提交则回滚,然后关闭连接」——它不是自动提交。这与很多人从「with 就是自动保存」的直觉相反:
# 想用自动提交语义,必须显式开启
with psycopg.connect("dbname=mydb", autocommit=True) as conn:
with conn.cursor() as cur:
cur.execute("INSERT INTO t VALUES (1)") # 立即生效| 驱动 | 默认提交行为 | 提交方式 |
|---|---|---|
| psql | 每条语句自动提交 | 手动 BEGIN / COMMIT |
| JDBC | autoCommit = true | setAutoCommit(false) + commit() |
| psycopg3 | autocommit = False | conn.commit() |
| asyncpg | 隐式事务,需用 transaction() 上下文 | async with conn.transaction(): |
| SQLAlchemy Core/ORM | 连接级 begin(),Session 管理事务 | session.commit() |
# asyncpg:用事务上下文管理器,退出时自动 COMMIT / ROLLBACK
# async with conn.transaction():
# await conn.execute("UPDATE accounts SET balance = balance - $1 WHERE id = $2", 100, 1)
# await conn.execute("UPDATE accounts SET balance = balance + $1 WHERE id = $2", 100, 2)ORM 与连接池:最常见的「悬挂事务」来源
ORM(如 SQLAlchemy、Hibernate、Django ORM)通常在首次执行语句时开启事务,并依赖 commit() / rollback() / 会话关闭来结束它。连接池让问题变得更隐蔽:连接被归还到池里时事务未必已结束,下一个借用者就可能继承一个未结束的事务。
连接池中的悬挂事务(非常常见):
线程 1 借用连接 → UPDATE(事务开始)→ 业务异常 → 未 rollback → 归还连接
线程 2 借用同一连接 → SELECT(落在线程 1 的旧事务里!)
→ 看到的是旧快照,且持有旧锁 → 诡异 bug排查与治理手法:
-- 查出所有「事务已开始但空闲」的后端
SELECT pid, state, xact_start, now() - xact_start AS xact_age,
left(query, 60) AS last_query
FROM pg_stat_activity
WHERE state IN ('idle in transaction', 'idle in transaction (aborted)')
ORDER BY xact_start; pid | state | xact_start | xact_age | last_query
------+---------------------+-------------------------------+----------+----------------
8421 | idle in transaction | 2026-09-28 10:00:01.123456+08 | 00:03:21 | UPDATE accounts SET ...
(1 row)-- 兜底:超时自动断开长时间空闲的事务
SET idle_in_transaction_session_timeout = '60s';SET-- 也可以在 postgresql.conf / ALTER SYSTEM 中全局设置
-- ALTER SYSTEM SET idle_in_transaction_session_timeout = '5min';要点:连接池归还连接前必须 rollback() 或 commit(),并对应用侧配置 idle_in_transaction_session_timeout 作为最后一道保险。
DDL 与自动提交:PostgreSQL 的优势
这是 PostgreSQL 与 MySQL 的重要差异。MySQL 的 DDL 会隐式提交当前事务,导致「DDL 无法回滚」。PostgreSQL 的 DDL 是事务性的:CREATE TABLE、ALTER TABLE、DROP INDEX 等都可以放在事务里,失败或主动回滚时整体撤销。
-- PostgreSQL:DDL 可以回滚
BEGIN;
CREATE TABLE tmp_orders (id bigint PRIMARY KEY);
ALTER TABLE tmp_orders ADD COLUMN amount numeric(12,2);
ROLLBACK; -- 上面两张改动全部撤销,tmp_orders 不存在BEGIN
CREATE TABLE
ALTER TABLE
ROLLBACKSELECT to_regclass('tmp_orders') IS NULL AS rolled_back; rolled_back
-------------
t
(1 row)这让「迁移脚本」可以做成单事务、失败自动回滚,是 PostgreSQL 运维上的明显优势。
不能在事务块里跑的命令
有少数命令被明确禁止出现在事务块中(包括自动提交模式下的隐式事务不算问题,但显式 BEGIN 后执行会报错):
| 命令 | 限制原因 |
|---|---|
VACUUM | 不能在事务中运行(VACUUM 必须在顶层执行) |
CREATE INDEX CONCURRENTLY | 需要多阶段、非阻塞的独立提交点 |
DROP INDEX CONCURRENTLY | 同上 |
REINDEX CONCURRENTLY | 同上 |
CREATE DATABASE / DROP DATABASE | 不能在被访问的库的事务里执行 |
ALTER SYSTEM | 不能位于事务块或函数中 |
COMMIT PREPARED 等 2PC 命令 | 需在事务外执行 |
-- 错误!VACUUM cannot run inside a transaction block
BEGIN;
VACUUM t;ERROR: VACUUM cannot run inside a transaction block-- 错误!CREATE INDEX CONCURRENTLY cannot run inside a transaction block
BEGIN;
CREATE INDEX CONCURRENTLY t_id_idx ON t (id);ERROR: CREATE INDEX CONCURRENTLY cannot run inside a transaction block# 正确做法:让 psql 用自动提交模式逐条执行(不加 --single-transaction)
psql -d mydb -c "VACUUM (ANALYZE) t;"
psql -d mydb -c "CREATE INDEX CONCURRENTLY t_id_idx ON t (id);"批处理的事务边界:批次大小怎么定
批处理(bulk load、数据迁移、批量计算)的核心权衡是「事务大小」:
| 事务粒度 | 优点 | 缺点 |
|---|---|---|
| 每行一个事务(自动提交) | 无锁堆积、失败只影响一行 | 每次都要等 WAL 刷盘,极慢 |
| 每 N 行一个事务 | 吞吐与资源占用平衡 | 需要选合适的 N |
| 整个任务一个事务 | 语义最简,天然全成或全败 | 长事务:锁多、WAL 大、VACUUM 被阻塞、失败要全部重来 |
-- 推荐:分批提交,兼顾吞吐与可控性
DO $$
DECLARE
batch_size int := 10000;
i int;
BEGIN
FOR i IN 0..(SELECT count(*) / batch_size FROM staging) LOOP
INSERT INTO target (id, payload)
SELECT id, payload
FROM staging
ORDER BY id
LIMIT batch_size OFFSET i * batch_size;
COMMIT; -- 每批提交
END LOOP;
END $$;DO如果导入过程中允许跳过坏行,则把「分批提交」与保存点(或 PL/pgSQL 异常处理)结合起来:
-- 分批 + 逐行容错:坏行写入错误表,好行正常提交
DO $$
DECLARE r record;
BEGIN
FOR r IN SELECT * FROM staging LOOP
BEGIN
INSERT INTO target (id, payload) VALUES (r.id, r.payload);
EXCEPTION WHEN others THEN
INSERT INTO import_errors (row_data, reason)
VALUES (r::text, SQLERRM);
END;
END LOOP;
COMMIT;
END $$;DO选择批次大小的经验法则:让单个事务在 1 秒到几十秒量级完成,且占用内存可控;一般在数千到几十万行之间试跑,用实际耗时与 pg_stat_activity 的 xact_start 时长来定。
实战示例
场景:一条批量导入脚本,要求「坏行跳过、好行入库、出错即停、单批可回滚」。
第 1 步:准备环境与数据
DROP TABLE IF EXISTS import_errors, target, staging;
CREATE TABLE staging (
id bigint PRIMARY KEY,
payload text NOT NULL
);
CREATE TABLE target (
id bigint PRIMARY KEY,
payload text NOT NULL
);
CREATE TABLE import_errors (
id bigserial PRIMARY KEY,
row_data text NOT NULL,
reason text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO staging (id, payload) VALUES
(1, 'a'), (2, 'b'), (3, 'c');DROP TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
INSERT 0 3第 2 步:用自动提交模式试跑单条语句
# 自动提交模式:每条 -c 是独立事务,一条失败不影响其它
psql -d mydb -v ON_ERROR_STOP=1 \
-c "INSERT INTO target SELECT id, payload FROM staging WHERE id = 1;" \
-c "INSERT INTO target SELECT id, payload FROM staging WHERE id = 1;" \
-c "INSERT INTO target SELECT id, payload FROM staging WHERE id = 2;"INSERT 0 1
ERROR: duplicate key value violates unique constraint "target_pkey"-- 注意:因为设置了 ON_ERROR_STOP=1,第三条不会执行第 3 步:用显式事务 + 保存点做逐行容错
BEGIN;
-- 第 1 行:正常
SAVEPOINT sp_row;
INSERT INTO target (id, payload) VALUES (1, 'a');
RELEASE SAVEPOINT sp_row;
-- 第 2 行:主键冲突,写入错误表后跳过
SAVEPOINT sp_row;
INSERT INTO target (id, payload) VALUES (1, 'a-dup');
ROLLBACK TO SAVEPOINT sp_row;
INSERT INTO import_errors (row_data, reason)
VALUES ('(1, a-dup)', 'duplicate key');
COMMIT;BEGIN
SAVEPOINT
INSERT 0 1
RELEASE
SAVEPOINT
ERROR: duplicate key value violates unique constraint "target_pkey"
DETAIL: Key (id)=(1) already exists.
ROLLBACK
INSERT 0 1
COMMIT第 4 步:验证结果
SELECT * FROM target ORDER BY id;
SELECT row_data, reason FROM import_errors ORDER BY id; id | payload
----+---------
1 | a
(1 row)
row_data | reason
------------+--------------
(1, a-dup) | duplicate key
(1 row)第 5 步:清理
DROP TABLE import_errors, target, staging;DROP TABLE注意事项
- 服务端没有 autocommit 参数,别去
pg_settings找它。 自动提交是「无显式事务时每条语句独立成事务」这一语义的俗称。错误!认为「PG 支持 SET autocommit = 0」(MySQL 才有这个会话变量)。
-- 错误!PostgreSQL 没有 autocommit 参数
-- SET autocommit = 0;
-- ERROR: unrecognized configuration parameter "autocommit"- 自动提交模式下单句失败会回滚该句,但显式事务中会污染整个事务。 混用两者是 bug 温床。错误!在事务块里直接继续执行后续语句。
BEGIN;
INSERT INTO t VALUES (1);
INSERT INTO t VALUES (1); -- 冲突
INSERT INTO t VALUES (2); -- 错误!事务已 aborted
COMMIT;\set ON_ERROR_ROLLBACK on有性能代价。 它给每条语句加一个隐式保存点(子事务),脚本里大量语句时开销显著;日常开发用interactive,批处理保持off。
mydb=# \set ON_ERROR_ROLLBACK on -- 不推荐用于大批量脚本
mydb=# \set ON_ERROR_ROLLBACK interactive -- 推荐- 忘记提交/回滚就会留下悬挂事务。 客户端断开或连接归还时 PostgreSQL 会隐式
ROLLBACK,所以「没报错但数据没了」几乎总是漏写commit()。
-- 排查悬挂事务
SELECT pid, state, now() - xact_start AS age, left(query, 60)
FROM pg_stat_activity
WHERE state LIKE 'idle in transaction%';VACUUM与* CONCURRENTLY命令不能放进事务块,包括 psql 的--single-transaction模式。
# 错误!--single-transaction 会让 VACUUM 失败
psql --single-transaction -c "VACUUM t;"- 不要依赖 JDBC 的
setAutoCommit(true)来隐式提交。 规范上它有提交语义,实践上不可靠,显式commit()才是正解。
最佳实践
- 应用层统一用「显式事务」:驱动里明确
setAutoCommit(false)/autocommit=False,配合 try-catch-finally 保证提交或回滚,绝不留悬挂事务。
conn.setAutoCommit(false);
try {
// ... 业务语句 ...
conn.commit();
} catch (Exception e) {
conn.rollback();
throw e;
} finally {
conn.setAutoCommit(true); // 归还连接前恢复
}- 批处理用「固定批次大小 + 定期提交」,让每个事务在秒级完成;需要跳坏行时叠加保存点或 PL/pgSQL 异常处理。
-- 每 10000 行提交一次
BEGIN;
INSERT INTO target SELECT id, payload FROM staging ORDER BY id LIMIT 10000;
COMMIT;- 脚本与迁移一律加
ON_ERROR_STOP=1,迁移脚本再叠加--single-transaction(前提是脚本内无VACUUM/CONCURRENTLY)。
psql -v ON_ERROR_STOP=1 --single-transaction -f migration.sql- 给数据库加兜底超时,避免任何来源的悬挂事务长期持锁:
idle_in_transaction_session_timeout+ 语句超时statement_timeout。
ALTER SYSTEM SET idle_in_transaction_session_timeout = '5min';
ALTER SYSTEM SET statement_timeout = '30s';
SELECT pg_reload_conf();- 交互调试时用 psql 提示符确认自己是否在事务里(
=无事务、*事务中、!已失败),避免中途 CTRL-C 后残留事务。
mydb=# ← 无事务(=)
mydb=*# ← 事务中(*),离开前记得 COMMIT 或 ROLLBACK
mydb=!# ← 事务已失败(!),只能 ROLLBACK 或 ROLLBACK TO SAVEPOINT下一节
继续学习:隐式与显式事务
参考链接
- PostgreSQL 官方文档:psql 变量(AUTOCOMMIT / ON_ERROR_ROLLBACK)
- PostgreSQL 官方文档:事务管理(BEGIN / COMMIT)
- PostgreSQL 官方文档:BEGIN
- PostgreSQL 官方文档:SET TRANSACTION
- PostgreSQL 官方文档:客户端连接默认值(idle_in_transaction_session_timeout)
- PostgreSQL 官方文档:VACUUM
- PostgreSQL 官方文档:CREATE INDEX(CONCURRENTLY)
- PostgreSQL 官方文档:监控统计(pg_stat_activity)