想象一下你在写一本书。但问题来了:事情总不会完全按计划走。有时候你写了一整章,回头一看,第 5 段写得一塌糊涂。你会怎么做?你不会把整章都删掉吧?肯定只会改那些有问题的地方。
SAVEPOINT 在 PostgreSQL 里就是这么个意思。它让你可以:
- 在事务里创建保存点——就像书里的书签一样。
- 回到这些点,只撤销一部分操作,不用把整个事务都回滚。
- 继续处理剩下的数据,不用重头再来一遍。
SAVEPOINT 的基本语法
用 SAVEPOINT 的命令其实很简单,基本就这几条:
创建保存点(SAVEPOINT):
SAVEPOINT savepoint_name;
这就像你说:“先记住这个地方,万一等会儿要回来。”
回滚到保存点(ROLLBACK TO SAVEPOINT):
ROLLBACK TO SAVEPOINT savepoint_name;
如果哪里出错了,你就能回到指定的 SAVEPOINT,撤销从那时起的所有更改。
RELEASE SAVEPOINT):
RELEASE SAVEPOINT savepoint_name;
这样就把“书签”删掉了,之后你就不能再回到这个点了。
简单例子:网店购物
假设我们在搞一个网店。客户往购物车里加了几样东西,我们想用一个事务来处理下单和库存表的更改。但如果某一步失败了,我们只想撤销那一步,而不是整个事务都作废。
BEGIN;
-- 步骤 1: 预定商品 "SQL 书"
UPDATE inventory SET stock = stock - 1 WHERE product_id = 101;
-- 创建保存点
SAVEPOINT book_reserved;
-- 步骤 2: 预定商品 "PostgreSQL 杯子"
UPDATE inventory SET stock = stock - 1 WHERE product_id = 102;
-- 哎呀,发现仓库里没杯子!
ROLLBACK TO SAVEPOINT book_reserved;
-- 只提交书的更改
COMMIT;
这个例子里发生了什么?
- 我们用
BEGIN开始了事务。 - 预定书之后,创建了保存点
book_reserved。这是我们的第一个“checkpoint”。 - 尝试预定杯子,但出错了(比如库存不足)。
- 我们回滚到
book_reserved,只撤销和杯子有关的更改。 - 最后用
COMMIT提交了书的更改。
复杂点的例子:多步骤数据处理
现在假设你在做订单管理系统,要同时更新几张表:orders(订单)、inventory(库存)和 billing(账单)。如果某一步挂了,你肯定不想把其他表的进度也丢了。这时候 SAVEPOINT 就很有用了。
BEGIN;
-- 步骤 1: 新建订单
INSERT INTO orders (order_id, customer_id, status) VALUES (1, 123, '待处理');
SAVEPOINT after_order_created;
-- 步骤 2: 更新库存
UPDATE inventory SET stock = stock - 2 WHERE product_id = 101;
SAVEPOINT after_stock_updated;
-- 步骤 3: 账单支付
INSERT INTO billing (order_id, amount, status) VALUES (1, 100, '已支付');
-- 哎呀,出错了:信用卡被拒!
ROLLBACK TO SAVEPOINT after_stock_updated;
-- 我们回到更新库存之后,但订单还是“待处理”状态。
UPDATE orders SET status = '失败' WHERE order_id = 1;
COMMIT;
注意我们怎么用 SAVEPOINT 把事务分成逻辑步骤,回到需要的点,只保留一部分更改。
用 SAVEPOINT 的小贴士
- 保存点名字要有意义。上面例子里的
after_order_created比step1这种强多了。 - 嵌套保存点没问题:你可以在回滚到某个点后再创建新的
SAVEPOINT。 - 用
RELEASE SAVEPOINT删除不需要的保存点,能释放资源,尤其是大事务里能提升性能。
实际应用场景
银行业务处理: 比如多账户之间转账时,如果某一步失败了,你可以回滚到某个阶段。
从文件导入数据: 导入大 CSV 文件时,可以一行一行检查,出错只回滚那一行,成功的都保留。
批量更新记录: 如果你有个复杂 SQL 脚本要更新成千上万行,SAVEPOINT 让你在中途出错时回到上一步。
常见错误和坑
有时候用 SAVEPOINT 会遇到意外情况,主要是你没搞懂它怎么工作。比如:
- 如果你忘了回滚或删除保存点,可能会导致资源一直被锁,直到事务结束。
SAVEPOINT不能撤销在保存点之前发生的操作。比如已经COMMIT的数据就没法再回滚了。
现在你可以放心大胆地在事务里玩 SAVEPOINT,想设哪就设哪。后面还有更多 SQL 实战,准备好迎接新一轮的挑战吧!
GO TO FULL VERSION