什么是 PostgreSQL TRANSACTION(事务)?
在数据库的世界里,事务就像一次“银行转账”操作。你从 A 账户转 100 元到 B 账户,这个操作必须同时满足两个条件:A 账户扣款成功,B 账户加款成功。如果其中任何一个步骤失败,整个操作就得撤销,不能只完成一半。
在 PostgreSQL 中,PostgreSQL TRANSACTION(事务) 就是用来保证这类操作“要么全部成功,要么全部失败”的机制。它确保数据库在并发访问和异常情况下依然保持数据的一致性。
举个例子:你在网上买一本书,系统要完成以下几步:
- 扣除你的账户余额;
- 增加商家的收款金额;
- 更新库存数量。
如果第 2 步失败,比如网络中断,那第 1 步的扣款就不该生效。否则你钱没了,书也没到,岂不是亏大了?
这就是事务的核心价值:原子性(Atomicity)。事务中的所有操作要么全部执行,要么一个都不执行。
事务的四大特性(ACID)
PostgreSQL TRANSACTION(事务) 的强大之处,源于它严格遵循 ACID 原则。这四个字母代表了事务的四大基本特性:
-
A:原子性(Atomicity)
事务是一个不可分割的最小工作单元。要么全部完成,要么全部回滚。 -
C:一致性(Consistency)
事务执行前后,数据库必须保持一致状态。比如余额总和不能凭空消失或增加。 -
I:隔离性(Isolation)
多个事务并发执行时,彼此之间互不干扰。即使同时操作同一张表,也不会出现“脏读”或“幻读”。 -
D:持久性(Durability)
一旦事务提交,其结果就会永久保存在数据库中,即使系统崩溃也不会丢失。
这就像一场精心编排的舞台剧:每个演员(操作)都必须准确完成自己的台词(动作),不能半途而废;剧终后,所有情节都必须被完整记录下来。
实际案例演示
我们来创建一个简单的账户表,模拟转账操作:
-- 创建账户表
CREATE TABLE accounts (
id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL,
balance DECIMAL(10,2) DEFAULT 0.00
);
插入初始数据:
-- 插入两个账户
INSERT INTO accounts (name, balance) VALUES ('Alice', 1000.00);
INSERT INTO accounts (name, balance) VALUES ('Bob', 500.00);
现在我们开始一个事务,实现从 Alice 转 200 元给 Bob。
-- 开始事务
BEGIN;
-- 从 Alice 账户扣款
UPDATE accounts SET balance = balance - 200.00 WHERE name = 'Alice';
-- 向 Bob 账户加款
UPDATE accounts SET balance = balance + 200.00 WHERE name = 'Bob';
-- 提交事务,所有操作生效
COMMIT;
执行后,Alice 剩下 800,Bob 变成 700,一切正常。
但如果中途出错,比如 Bob 的账户名拼错:
BEGIN;
UPDATE accounts SET balance = balance - 200.00 WHERE name = 'Alice';
UPDATE accounts SET balance = balance + 200.00 WHERE name = 'Bod'; -- 错误:拼写错误
-- 这里没有提交,而是回滚
ROLLBACK;
此时,Alice 的钱没被扣,Bob 的账户也没加钱。事务被完整撤销,数据库状态未改变。
事务的控制语句详解
PostgreSQL 提供了几个关键的命令来控制事务流程:
BEGIN或START TRANSACTION:开启一个新事务。COMMIT:提交事务,永久保存更改。ROLLBACK:回滚事务,撤销所有未提交的操作。SAVEPOINT:设置保存点,允许部分回滚。
保存点(Savepoint):事务中的“撤销点”
想象你在写一篇论文,写到一半发现前面某段写错了,但不想重头再来。你可以先“保存”当前进度,等修改完再决定是否回退到这个点。
在 PostgreSQL 中,SAVEPOINT 就是这个功能。
BEGIN;
UPDATE accounts SET balance = balance - 100.00 WHERE name = 'Alice';
SAVEPOINT sp1; -- 设置保存点
UPDATE accounts SET balance = balance + 50.00 WHERE name = 'Bob';
SAVEPOINT sp2; -- 再设一个保存点
-- 发现第二个操作有问题,只想回滚到 sp1
ROLLBACK TO SAVEPOINT sp1;
-- 此时 Alice 减了 100,Bob 没变
COMMIT;
这个例子中,我们通过 SAVEPOINT 实现了局部回滚,避免了整个事务被放弃。
事务隔离级别与并发控制
在高并发场景下,多个事务可能同时操作同一张表。这时就需要隔离级别来控制事务之间的可见性。
PostgreSQL 支持以下几种隔离级别(从低到高):
| 隔离级别 | 描述 |
|---|---|
| READ UNCOMMITTED | 读取未提交的数据,可能出现脏读,PostgreSQL 不支持此级别 |
| READ COMMITTED | 默认级别,只能读取已提交的数据,避免脏读 |
| REPEATABLE READ | 保证同一事务内多次读取相同数据结果一致 |
| SERIALIZABLE | 最高隔离级别,完全避免并发问题,但性能最低 |
PostgreSQL 默认使用 READ COMMITTED,这意味着每个语句看到的都是最新已提交的数据。
实际测试:脏读与不可重复读
我们用两个终端(或两个 psql 会话)来模拟并发:
会话 A:
BEGIN;
UPDATE accounts SET balance = balance - 100.00 WHERE name = 'Alice';
-- 不提交,保持事务打开
会话 B:
-- 在会话 B 中查询 Alice 的余额
SELECT balance FROM accounts WHERE name = 'Alice';
-- 结果会显示 900(因为 A 未提交)
此时会话 B 读到了未提交的数据——这就是“脏读”。但在 PostgreSQL 中,如果你用 READ COMMITTED,它会自动避免这种情况。
如果使用 REPEATABLE READ,同一事务内多次查询结果一致,即使其他事务已提交修改。
事务的最佳实践建议
1. 尽量缩短事务时间
长事务会占用锁资源,影响其他用户的操作。尽量将事务控制在几秒内完成。
-- ❌ 不推荐:长时间操作在事务中
BEGIN;
-- 处理大量数据,耗时 30 秒
UPDATE big_table SET status = 'processed';
-- 期间无法访问该表
COMMIT;
-- ✅ 推荐:拆分事务
BEGIN;
UPDATE big_table SET status = 'processed' WHERE id < 10000;
COMMIT;
BEGIN;
UPDATE big_table SET status = 'processed' WHERE id >= 10000 AND id < 20000;
COMMIT;
2. 使用 SAVEPOINT 处理复杂逻辑
当一个事务包含多个步骤,其中某一步可能失败时,使用保存点可以避免全盘回滚。
3. 避免在事务中执行耗时操作
如文件 I/O、网络请求、复杂的计算等,这些操作不应放在事务内,否则可能阻塞数据库。
4. 明确使用 COMMIT 或 ROLLBACK
不要让事务“悬而未决”。程序结束前,必须显式提交或回滚。
-- 错误示例:忘记提交
BEGIN;
UPDATE accounts SET balance = balance + 100 WHERE name = 'Alice';
-- 程序结束,事务未提交,资源被锁定
总结
PostgreSQL TRANSACTION(事务) 是保障数据完整性和一致性的核心机制。它不仅帮助我们处理“转账”这类关键操作,还在高并发环境下维护了系统的稳定性。
通过理解事务的 ACID 特性、掌握 BEGIN / COMMIT / ROLLBACK / SAVEPOINT 等命令,以及合理设置隔离级别,我们可以编写出更健壮、更可靠的数据库应用。
记住:事务不是“魔法”,而是责任。每一次 COMMIT 都意味着对数据完整性的承诺。每一次 ROLLBACK 都是对错误的纠正。
在实际开发中,养成“事务最小化、显式控制、及时提交”的习惯,是每个合格开发者的基本素养。当你能熟练驾驭 PostgreSQL TRANSACTION(事务)时,你就真正掌握了数据库操作的本质。