PostgreSQL TRANSACTION(事务)(一文讲透)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观
前言
在数据库开发中,事务(Transaction)是确保数据操作可靠性和一致性的核心机制。无论是电商系统的订单支付、银行转账,还是库存管理,事务都能通过 ACID 特性保障操作的完整性。本文以 PostgreSQL 为例,系统性讲解事务的基本原理、操作方法及常见场景,帮助开发者理解如何在实际项目中正确使用事务,避免数据异常。
核心概念:事务的定义与 ACID 特性
什么是事务?
事务是数据库管理系统(DBMS)执行的一组逻辑操作单元。它将多个数据库操作视为一个整体,要么全部成功执行(提交),要么全部失败回滚,从而保证数据的 一致性。
例如:在银行转账场景中,从账户 A 转账到账户 B 的操作包含两个步骤:
- 从 A 账户减去金额
- 向 B 账户增加相同金额
若步骤 1 成功而步骤 2 失败,会导致数据不一致(A 账户金额减少,但 B 账户未增加)。事务的作用正是避免此类问题。
ACID 四大特性
事务的可靠性依赖于以下四个特性,简称 ACID:
特性 | 含义 | 比喻 |
---|---|---|
Atomicity(原子性) | 事务中的所有操作要么全部完成,要么全部不执行。 | 如拼图游戏,若某块缺失则整个拼图失败。 |
Consistency(一致性) | 事务执行前后,数据库始终处于合法的约束状态(如主键唯一、外键关联)。 | 如账本记录,金额总和始终平衡。 |
Isolation(隔离性) | 并发事务之间互不干扰,避免“脏读”“幻读”等问题。 | 如银行柜员处理不同客户的账户,互不影响。 |
Durability(持久性) | 已提交的事务对数据的修改永久保存,即使系统崩溃也能恢复。 | 如刻在石板上的记录,不可轻易擦除。 |
PostgreSQL 中的事务管理基础
事务的生命周期
在 PostgreSQL 中,事务的执行流程如下:
BEGIN; -- 显式开启事务
-- 执行多个 SQL 操作
COMMIT; -- 提交事务,所有修改永久生效
-- 或者
ROLLBACK; -- 回滚事务,撤销所有未提交的修改
示例:银行转账事务
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 'A';
UPDATE accounts SET balance = balance + 100 WHERE account_id = 'B';
COMMIT;
如果上述任意一步失败(如账户 B 不存在),则 ROLLBACK
可撤销所有操作。
隐式事务与显式事务
PostgreSQL 默认采用 隐式事务模式,即每个独立的 SQL 语句自动构成一个事务。例如:
-- 隐式事务示例
INSERT INTO users (name, age) VALUES ('Alice', 30);
-- 该语句自动提交
而显式事务通过 BEGIN
和 COMMIT
明确界定范围,适合多步骤操作。
隔离性:事务的并发控制
隔离级别与问题分类
PostgreSQL 支持四种隔离级别,通过 SET TRANSACTION
语句设置。不同级别解决不同的并发问题:
隔离级别 | 允许的异常现象 | 推荐场景 |
---|---|---|
READ UNCOMMITTED | 脏读、不可重复读、幻读 | 极少使用,需谨慎 |
READ COMMITTED | 不可重复读、幻读 | 大多数 OLTP 场景的默认选择 |
REPEATABLE READ | 幻读 | 需要高度一致性的情况 |
SERIALIZABLE | 禁用所有并发问题,但可能引发锁冲突 | 高并发且严格一致的场景 |
常见并发问题示例
-
脏读(Dirty Read)
- 事务 A 读取了事务 B 未提交的数据,随后 B 回滚,导致 A 读取了无效数据。
- 类比:阅读同事草稿箱的邮件,但对方最终删除了该邮件。
-
幻读(Phantom Read)
- 事务 A 多次查询同一条件,结果集因其他事务插入新数据而变化。
- 类比:清点仓库库存时,中途有新货物被其他同事入库。
实战:设置隔离级别
-- 设置事务为 REPEATABLE READ
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM orders WHERE status = 'pending'; -- 第一次查询
-- 其他事务可能在此期间插入新数据
SELECT * FROM orders WHERE status = 'pending'; -- 可能出现幻读
COMMIT;
事务的高级用法与注意事项
嵌套事务:SAVEPOINT 的作用
在长事务中,可通过 SAVEPOINT
创建临时提交点,实现部分回滚。例如:
BEGIN;
-- 步骤 1:更新用户信息
UPDATE users SET email = 'new@example.com' WHERE id = 1;
SAVEPOINT user_update; -- 创建保存点
-- 步骤 2:更新订单状态(假设此处出错)
UPDATE orders SET status = 'failed' WHERE user_id = 1;
-- 发现错误,回滚到保存点
ROLLBACK TO SAVEPOINT user_update;
-- 步骤 3:重新尝试更新订单
UPDATE orders SET status = 'completed' WHERE user_id = 1;
COMMIT;
死锁与超时处理
事务在并发场景中可能因资源竞争导致 死锁。PostgreSQL 会自动检测并回滚其中一个事务,但开发者可通过以下方式优化:
- 设置事务超时:
SET statement_timeout = '5s'; -- 超过 5 秒自动回滚
- 按顺序加锁:确保多个事务以相同顺序访问资源,减少锁冲突。
实战案例:电商订单系统的事务设计
场景描述
用户下单时需执行以下操作:
- 减少商品库存
- 插入订单记录
- 扣除用户余额
代码实现
BEGIN;
-- 1. 减少商品库存
UPDATE products
SET stock = stock - 1
WHERE product_id = 101 AND stock > 0;
-- 检查库存是否充足
IF FOUND THEN
-- 2. 插入订单
INSERT INTO orders (user_id, product_id, amount)
VALUES (1, 101, 50.00);
-- 3. 扣除用户余额
UPDATE users
SET balance = balance - 50.00
WHERE id = 1 AND balance >= 50.00;
COMMIT;
ELSE
ROLLBACK;
RAISE EXCEPTION '库存不足';
END IF;
此案例通过事务确保所有步骤要么全部成功,要么全部失败,避免“已扣款但未减库存”等问题。
总结与扩展
本文通过理论结合代码示例,系统讲解了 PostgreSQL 事务的核心概念、操作方法及常见问题。开发者需注意以下要点:
- 合理使用事务:避免过长事务导致锁竞争,也避免因缺少事务导致数据不一致。
- 隔离级别选择:根据业务需求权衡一致性与性能。
- 错误处理:通过
SAVEPOINT
和异常捕获增强代码健壮性。
PostgreSQL 事务机制是构建可靠系统的基石,深入理解其原理和实践技巧,能显著提升开发效率与数据安全性。对于更复杂的场景(如分布式事务),可进一步研究两阶段提交(2PC)或第三方事务管理工具。
希望本文能帮助读者在 PostgreSQL 事务领域迈出扎实的一步,后续可结合具体项目场景深入探索!