数据库事务原理与回滚机制

一、什么是数据库事务

事务是一组被当作单个逻辑工作单元执行的数据库操作序列------这组操作要么全部成功,要么全部失败回滚,不存在"执行了一半"的中间状态。

最经典的例子是银行转账:A 给 B 转 100 元,包含两步:① A 扣 100,② B 加 100。如果第 1 步成功后系统崩溃、第 2 步没执行,A 的钱就凭空消失了。事务的作用就是保证这两步要么都成功,要么都不发生。

sql 复制代码
BEGIN;                                              -- 开启事务
UPDATE account SET balance = balance - 100 WHERE id = 'A';
UPDATE account SET balance = balance + 100 WHERE id = 'B';
COMMIT;                                             -- 提交(全部生效)
-- 出错则 ROLLBACK(全部撤销)

二、ACID 四大特性

特性 名称 含义 底层机制
A 原子性 操作不可分割,全成功或全失败 undo log 回滚日志
C 一致性 事务前后满足业务规则/约束(最终目的) 其他三性 + 约束共同保证
I 隔离性 并发事务互不干扰 锁 + MVCC
D 持久性 提交后数据永久保存,断电不丢 redo log 重做日志

三、为什么需要事务

  1. 故障恢复:异常/断电/崩溃时,保证数据不停在错误的中间状态。
  2. 并发控制:多用户同时读写同一数据时避免相互干扰(否则会超卖、重复扣款、账目不平)。

四、常见使用方式

原生 SQL:

sql 复制代码
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 'A';
UPDATE account SET balance = balance + 100 WHERE id = 'B';
COMMIT;   -- 或 ROLLBACK

Spring 声明式事务(Java 最常用):

java 复制代码
@Transactional(rollbackFor = Exception.class)
public void transfer(String from, String to, int amount) {
    accountMapper.decrease(from, amount);
    accountMapper.increase(to, amount);   // 抛异常时上面也会回滚
}

Node.js(Sequelize 托管事务):

javascript 复制代码
await sequelize.transaction(async (t) => {
  await Account.decrement('balance', { by: amount, where: { id: fromId }, transaction: t });
  await Account.increment('balance', { by: amount, where: { id: toId }, transaction: t });
  // 正常返回自动 commit,抛异常自动 rollback
});

(Python SQLAlchemy 用 with session.begin():,JDBC 用 conn.setAutoCommit(false) + commit/rollback,原理一致。)

五、隔离级别与并发问题

三类并发读问题:

  • 脏读:读到其他事务未提交的数据(对方回滚后就是脏数据)
  • 不可重复读:同一事务两次读同一行,值不同(针对 UPDATE)
  • 幻读:同一事务两次范围查询,行数不同(针对 INSERT/DELETE)

四种隔离级别(越高越安全、越慢):

隔离级别 脏读 不可重复读 幻读
读未提交 Read Uncommitted 可能 可能 可能
读已提交 Read Committed(Oracle 默认) 避免 可能 可能
可重复读 Repeatable Read(MySQL 默认) 避免 避免 可能*
串行化 Serializable 避免 避免 避免

MySQL InnoDB 靠 MVCC + 间隙锁,可重复读级别下大多能避免幻读。

六、实践避坑

  1. 事务要短小:内部别放 RPC、发消息等耗时操作,会长期占用连接和锁。
  2. Spring @Transactional 失效场景:非 public 方法、同类内部自调用、异常被 try-catch 吞掉、默认只回滚 RuntimeException(需加 rollbackFor)。
  3. 高并发扣库存:优先用乐观锁替代长事务:
sql 复制代码
UPDATE account SET balance = balance - 100
WHERE id = 'A' AND balance >= 100;   -- 靠 WHERE 防超卖,判断影响行数

七、回滚是自动行为吗

回滚不是数据库"自动"帮你兜底的行为,而是需要有人(数据库自己或应用程序)显式或隐式地触发它。

数据库会自动回滚的情况:

  1. 语句执行出错 / 违反约束(主键冲突、外键约束、字段超长等)。注意:MySQL 默认只回滚出错的那一条语句,而不是整个事务,事务仍处于打开状态。
  2. 死锁:数据库检测到死锁时,自动选一个事务作为牺牲者强制回滚。
  3. 连接中断 / 会话超时 / 崩溃恢复:未提交的事务会被自动回滚(靠 undo log 恢复)。
  4. 锁等待超时。

需要程序主动回滚的情况:

  • 业务规则失败(如"余额不足"):SQL 本身没报错,数据库不知道这是"失败",得靠代码判断并调用 ROLLBACK。
  • 框架帮你做的"自动"回滚(Spring @Transactional、Sequelize 托管事务):本质是框架在捕获到异常时替你调用了 rollback,仍是"程序触发"。
触发方 场景
数据库自动回滚 死锁牺牲者、崩溃/断连恢复未提交事务、(部分)约束错误
程序/框架触发回滚 业务校验失败、捕获到异常、主动调用 ROLLBACK

八、rollback 的逻辑定义在哪

应用层调用的 rollback() 只是一个"发起指令",真正的回滚逻辑不在应用代码里,而在数据库引擎内部。

调用链:

复制代码
你的代码 conn.rollback()
   │  (JDBC/驱动层:只是发一条命令)
   ▼
数据库客户端驱动  →  把 "ROLLBACK" 命令通过网络协议发给数据库服务器
   │
   ▼
数据库服务器(真正的回滚逻辑在这里!)
   │  读取 undo log,逆向撤销本事务所有已改动的数据
   ▼
数据恢复到事务开始前的状态

真正的回滚逻辑靠 undo log(撤销日志)实现(以 MySQL InnoDB 为例):

  • 事务每做一次修改,引擎会先记录一条"反向操作"到 undo log:
    • INSERT 一行 → undo log 记录一条对应的 DELETE
    • UPDATE 把 A 改成 B → undo log 记录"把 B 改回 A"
    • DELETE 一行 → undo log 记录一条对应的 INSERT
  • 收到 ROLLBACK 命令时,引擎从后往前依次执行 undo log 里的反向操作,把数据还原到事务开始前。
  • 这段逻辑由数据库引擎内核源码实现(如 InnoDB 的 trx_rollback 相关函数),应用层看不到也改不了。

各层"rollback 定义"对照表:

层次 "rollback" 是什么 定义/实现在哪
业务代码 try { ... } catch { conn.rollback(); } 你自己写,决定"何时"回滚
框架层 Spring @Transactional 捕获异常后调用回滚 框架源码帮你调 rollback()
JDBC/ORM 接口 Connection.rollback() / session.rollback() JDBC 规范定义接口,数据库驱动实现:发送 ROLLBACK 命令
数据库协议 一条 ROLLBACK 命令 驱动按数据库通信协议发送
数据库引擎 真正撤销数据的逻辑 数据库内核源码,基于 undo log 逆向回放

一句话概括:你(或框架)负责"喊一声回滚",数据库引擎负责"真正把数据倒回去",而它能倒回去是因为提前把每一步的反悔操作都记在了 undo log 里。

相关推荐
WikiLeake12 分钟前
一个健壮且灵活的大语言模型(LLM)API 配置加载器
数据库·人工智能
瑞克-桑切斯2 小时前
企业生产环境:https部署+nginx高可用(业务不中断)
linux·服务器·数据库
zyseo810 小时前
谷歌SEO 准首页词挖掘脚本:找出排在第 8-20 名的隐藏流量
大数据·数据库·人工智能
xiaogai_gai12 小时前
仅查询策略实战:从金蝶云·星空旗舰版定时拉取收料通知单
大数据·数据库·python
数据工匠老o13 小时前
异构数据库DDL同步:260条链路的结构变更自动适配方案
数据库·架构
这个DBA有点耶14 小时前
银行业市占率最高的国产数据库是谁?先搞清楚“市占率”的四种口径
数据库·程序员·架构
用户62799471826214 小时前
南大通用GBase 8a之基于审计日志的冷温热数据分析
数据库
数据库小学妹14 小时前
数据库运维五级成熟度:监控、备份、高可用怎么串起来
运维·数据库·监控
一个天蝎座的程序猿14 小时前
为了给报表提速,我把金仓KES的列存储折腾了个遍:聊聊KES的技术能力
数据库
飞越未来云城的霓虹飞猪14 小时前
AI 数据库地图:从 PostgreSQL 到向量数据库,一次看懂 AI 时代的数据底座
数据库