数据库事务(Transaction)是一组不可分割的 SQL 执行单元,要么全部执行成功(提交),要么全部执行失败(回滚),本质是 "原子性的操作集合",用于保证数据一致性。
事务的核心是保证数据一致性 ,通过 ACID 特性实现;基本用法:开启事务 → 执行SQL → 提交/回滚;实际开发中,优先用框架的声明式事务(如 Spring 的@Transactional),减少手动编码;不同数据库的事务隔离级别是关键差异,需结合业务场景选择。
目录
[3.事务的基本操作(SQL 层面)](#3.事务的基本操作(SQL 层面))
[3.1 开启事务](#3.1 开启事务)
[3.2 执行 SQL(核心业务逻辑)](#3.2 执行 SQL(核心业务逻辑))
[3.3 提交事务(成功时)](#3.3 提交事务(成功时))
[3.4 回滚事务(失败时)](#3.4 回滚事务(失败时))
[3.5 保存点(进阶:部分回滚)](#3.5 保存点(进阶:部分回滚))
[5.实际开发中的使用(以 Java 为例)](#5.实际开发中的使用(以 Java 为例))
[5.1 原生 JDBC 实现事务](#5.1 原生 JDBC 实现事务)
[5.2 框架层面(Spring Boot)](#5.2 框架层面(Spring Boot))
1.事务的核心特性(ACID)
事务的可靠性由 ACID 保证,这是数据库设计的基石:
| 特性 | 含义 | 例子(转账:A 转 100 给 B) |
|---|---|---|
| 原子性(Atomicity) | 事务是最小执行单位,不可拆分,要么全成,要么全败。 | A 扣 100 和 B 加 100 必须同时成功;若 B 加 100 失败,A 的扣款需回滚。 |
| 一致性(Consistency) | 事务执行前后,数据总状态保持合法(符合业务规则)。 | 转账前 A+B 总余额 = 1000,转账后仍 = 1000,不会出现 "只扣不加"。 |
| 隔离性(Isolation) | 多个事务并发执行时,彼此的操作互不干扰,事务看到的数据是 "隔离" 的。 | 事务 1(A 转 B)和事务 2(查询 A 余额)并发时,事务 2 看不到事务 1 未提交的中间状态。 |
| 持久性(Durability) | 事务提交后,修改永久生效,即使数据库崩溃(如断电),数据也不会丢失。 | 事务提交后,A 的余额 - 100、B 的 + 100 永久保存,重启数据库后仍有效。 |
2.事务的使用场景
只要涉及 "多步操作需保证一致性" 的场景,都需要事务:
- 金融转账(核心场景)
- 订单创建(扣库存 + 生成订单 + 扣余额)
- 账户注册(创建用户 + 初始化账户 + 发送通知)
- 数据批量更新(如批量调整价格,需全部成功或全部失败)
3.事务的基本操作(SQL 层面)
事务的生命周期:开启事务 → 执行 SQL → 提交事务(成功)/ 回滚事务(失败)
3.1 开启事务
不同数据库语法略有差异,但核心一致:
sql
-- MySQL
START TRANSACTION; -- 或 BEGIN;
-- PostgreSQL
BEGIN; -- 或 START TRANSACTION;
-- Oracle
BEGIN; -- 或 SET TRANSACTION;
3.2 执行 SQL(核心业务逻辑)
执行 1 条或多条 SQL(增删改查,主要是增删改):
sql
-- 示例:A(id=1)转100给B(id=2)
UPDATE account SET balance = balance - 100 WHERE id = 1; -- A扣钱
UPDATE account SET balance = balance + 100 WHERE id = 2; -- B加钱
3.3 提交事务(成功时)
事务中所有 SQL 执行无误后,提交事务,修改永久生效:
sql
COMMIT;
3.4 回滚事务(失败时)
若执行过程中出现错误(如 SQL 语法错、业务逻辑不满足),回滚事务,所有修改撤销:
sql
ROLLBACK;
3.5 保存点(进阶:部分回滚)
复杂事务中,可设置 "保存点",仅回滚到指定保存点,而非整个事务:
sql
-- 1. 开启事务
START TRANSACTION;
-- 2. 执行第一步SQL
UPDATE account SET balance = balance - 100 WHERE id = 1; -- A扣钱
-- 3. 设置保存点
SAVEPOINT sp1;
-- 4. 执行第二步SQL(假设此处可能失败)
UPDATE account SET balance = balance + 100 WHERE id = 2; -- B加钱
-- 5. 若第二步失败,回滚到保存点(仅撤销第二步,第一步仍保留)
ROLLBACK TO sp1;
-- 6. 若后续无错误,提交事务(仅A扣钱生效,需根据业务调整)
COMMIT;
4.不同数据库的事务差异(重点)
之前对话中提到的 PostgreSQL 与 MySQL,在事务上的核心差异的是 默认隔离级别:
| 特性 | MySQL(InnoDB 引擎) | PostgreSQL |
|---|---|---|
| 默认隔离级别 | Repeatable Read(可重复读) | Read Committed(读已提交) |
| 并发性能 | 略高(可重复读隔离级别下,锁竞争相对少) | 略低(读已提交隔离级别下,锁粒度更细) |
| 幻读问题 | 可重复读级别下,通过 MVCC 避免幻读 | 读已提交级别下,可能出现幻读 |
| 事务块语法 | 支持 BEGIN/START TRANSACTION + COMMIT/ROLLBACK | 支持 BEGIN + COMMIT/ROLLBACK,也支持事务块 |
| 保存点支持 | 支持 | 支持 |
5.实际开发中的使用(以 Java 为例)
5.1 原生 JDBC 实现事务
java
Connection conn = null;
try {
// 1. 获取连接(关闭自动提交)
conn = DriverManager.getConnection(url, user, pwd);
conn.setAutoCommit(false); // 关键:关闭自动提交,开启手动事务
// 2. 执行SQL
String sql1 = "UPDATE account SET balance = balance - 100 WHERE id = 1";
String sql2 = "UPDATE account SET balance = balance + 100 WHERE id = 2";
conn.prepareStatement(sql1).executeUpdate();
conn.prepareStatement(sql2).executeUpdate();
// 3. 提交事务
conn.commit();
} catch (Exception e) {
// 4. 异常回滚
if (conn != null) conn.rollback();
e.printStackTrace();
} finally {
// 5. 释放资源
if (conn != null) conn.close();
}
5.2 框架层面(Spring Boot)
使用@Transactional注解,声明式事务(推荐):
java
@Service
public class TransferService {
@Autowired
private JdbcTemplate jdbcTemplate;
// 关键:添加@Transactional注解,Spring自动管理事务
@Transactional(rollbackFor = Exception.class) // 出现任何异常都回滚
public void transfer(int fromId, int toId, int amount) {
// 执行转账逻辑
jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE id = ?", amount, fromId);
jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE id = ?", amount, toId);
}
}
6.使用事务的注意事项
- 避免长事务:长事务会占用数据库连接,阻塞其他操作,甚至导致锁超时(如转账事务不要包含 "等待用户输入" 的逻辑)。
- 事务粒度适中:不要把无关操作放进同一个事务(如 "转账 + 日志记录" 可拆分,日志失败不影响转账),也不要把需要一致性的操作拆成多个事务。
- 注意隔离级别:根据业务选择隔离级别(如金融场景用 "可重复读" 保证数据一致性,普通查询场景用 "读已提交" 提升并发)。
- 异常必须回滚 :开发中需确保 "任何业务异常都触发回滚",避免出现 "部分提交" 的情况(如 Spring 的
@Transactional需指定rollbackFor = Exception.class,默认仅回滚运行时异常)。 - 锁冲突处理 :高并发场景下,事务可能因锁等待超时失败,需添加重试机制(如用 Spring 的
@Retryable注解)。