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 的操作包含两个步骤:

  1. 从 A 账户减去金额
  2. 向 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);  
-- 该语句自动提交  

而显式事务通过 BEGINCOMMIT 明确界定范围,适合多步骤操作。


隔离性:事务的并发控制

隔离级别与问题分类

PostgreSQL 支持四种隔离级别,通过 SET TRANSACTION 语句设置。不同级别解决不同的并发问题:

隔离级别允许的异常现象推荐场景
READ UNCOMMITTED脏读、不可重复读、幻读极少使用,需谨慎
READ COMMITTED不可重复读、幻读大多数 OLTP 场景的默认选择
REPEATABLE READ幻读需要高度一致性的情况
SERIALIZABLE禁用所有并发问题,但可能引发锁冲突高并发且严格一致的场景

常见并发问题示例

  1. 脏读(Dirty Read)

    • 事务 A 读取了事务 B 未提交的数据,随后 B 回滚,导致 A 读取了无效数据。
    • 类比:阅读同事草稿箱的邮件,但对方最终删除了该邮件。
  2. 幻读(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 会自动检测并回滚其中一个事务,但开发者可通过以下方式优化:

  1. 设置事务超时
    SET statement_timeout = '5s';  -- 超过 5 秒自动回滚  
    
  2. 按顺序加锁:确保多个事务以相同顺序访问资源,减少锁冲突。

实战案例:电商订单系统的事务设计

场景描述

用户下单时需执行以下操作:

  1. 减少商品库存
  2. 插入订单记录
  3. 扣除用户余额

代码实现

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 事务的核心概念、操作方法及常见问题。开发者需注意以下要点:

  1. 合理使用事务:避免过长事务导致锁竞争,也避免因缺少事务导致数据不一致。
  2. 隔离级别选择:根据业务需求权衡一致性与性能。
  3. 错误处理:通过 SAVEPOINT 和异常捕获增强代码健壮性。

PostgreSQL 事务机制是构建可靠系统的基石,深入理解其原理和实践技巧,能显著提升开发效率与数据安全性。对于更复杂的场景(如分布式事务),可进一步研究两阶段提交(2PC)或第三方事务管理工具。


希望本文能帮助读者在 PostgreSQL 事务领域迈出扎实的一步,后续可结合具体项目场景深入探索!

最新发布