前言
本文完整讲解InnoDB事务整套核心概念:事务基础、ACID四大特性、并发事务问题、四大隔离级别、MVCC多版本并发控制、事务控制与线上最佳实践。 文中配有可直接复制运行的SQL示例、Mermaid示意图、小节坑点总结,文末附带汇总表格与课后思考题,方便复盘。
第一部分:SQL事务基础
1.1 什么是事务
事务是一组逻辑相关的SQL操作单元,这一组操作是一个不可分割整体。事务内的所有修改,要么全部执行成功提交,要么全部失败回滚,不会停留在中间状态。
最经典业务场景:账户转账。A扣钱、B加钱,这两步必须作为一个事务。不能出现A扣了钱,B却没有收到钱的异常数据。
1.2 为什么需要事务,没有事务会发生什么
如果没有事务机制,当程序崩溃、数据库宕机、代码抛出异常时,就会出现部分SQL执行成功、部分SQL没有执行的半完成状态,产生脏数据。 转账案例中就会出现:A余额减少,B余额不变,资金凭空消失,业务数据彻底错乱。
1.3 事务控制:SQL手动事务语法
核心命令:
BEGIN/START TRANSACTION:开启一个新事务COMMIT:提交事务,所有修改持久化到数据库ROLLBACK:回滚事务,撤销本次事务内全部修改autocommit:自动提交参数,MySQL默认开启,每执行一条SQL自动提交事务
实操SQL示例,模拟转账:
<span>-- 关闭自动提交方式1:手动开启事务</span>
<span>BEGIN</span>;
<span>-- A账户扣100</span>
<span>UPDATE</span> account <span>SET</span> balance <span>=</span> balance <span>-</span> <span>100</span> <span>WHERE</span> id <span>=</span> <span>1</span>;
<span>-- B账户加100</span>
<span>UPDATE</span> account <span>SET</span> balance <span>=</span> balance <span>+</span> <span>100</span> <span>WHERE</span> id <span>=</span> <span>2</span>;
<span>-- 一切正常则提交</span>
<span>COMMIT</span>;
<span>-- 如果中间出错,执行回滚,撤销上面两条update</span>
<span>-- ROLLBACK;</span>
查看与修改自动提交:
<span>-- 查看自动提交状态,1代表开启</span>
<span>SHOW</span> VARIABLES <span>LIKE</span> <span>'autocommit'</span>;
<span>-- 关闭自动提交,当前会话生效</span>
<span>SET</span> autocommit <span>=</span> <span>0</span>;
Mermaid流程图:事务正常提交与异常回滚
1.4 Java业务层事务演示(Spring @Transactional)
在Spring开发中,一般不手写BEGIN/COMMIT,使用注解声明式事务。
<span>@Service</span>
<span>public</span> <span>class</span> <span>TransferService</span> {
<span>@Autowired</span>
<span>private</span> AccountMapper accountMapper;
<span>/**
* 声明式事务:方法执行完成正常退出自动commit;抛出异常自动rollback
*/</span>
<span>@Transactional</span>
<span>public</span> void transfer(<span>Long</span> fromId, <span>Long</span> toId, Integer money){
accountMapper.deduct(fromId, money);
accountMapper.add(toId, money);
<span>// 方法结束,事务提交</span>
}
}
小节小结&坑点
autocommit开启时,每条SQL就是一个独立小事务;- 手动开启事务后,忘记commit,事务会一直存活,持续持有锁,引发锁等待、连接占用问题;
- Spring事务只对public方法生效,内部调用会出现事务失效。
第二部分:ACID 四大特性
ACID是事务的四个核心特性,是判断数据库是否支持事务的标准。
重点:理解每个特性的含义,以及InnoDB依靠什么底层技术实现。
2.1 A 原子性 Atomicity
是什么:事务是不可分割原子单元,事务内操作要么全部成功,要么全部失败回滚,不存在中间状态。 解决什么:防止出现部分执行的脏数据。 底层实现:undo log 回滚日志。
执行DML修改数据时,InnoDB会把修改前的数据写入undo log;当事务执行ROLLBACK回滚时,依靠undo log把数据恢复成修改之前的样子。
Mermaid:undo log回滚流程
2.2 C 一致性 Consistency
是什么:事务执行前后,数据库业务数据完整性约束不会被破坏,数据始终处于合法状态。
⚠️高频误区:一致性不是数据库底层某一个组件直接实现,它是最终目标结果。 原子性、隔离性、持久性 + 业务代码约束、数据库约束(主键、唯一索引、非空)共同保障一致性。
举例转账:转账前后A+B总余额必须不变,这个业务上的一致性,需要原子性保证不会半更新,同时业务代码也要写正确。数据库不能自动帮你实现业务逻辑层面的一致性。
2.3 I 隔离性 Isolation
是什么:数据库允许多个事务并发执行,隔离性保证各个事务之间互相不可随意看见对方未提交的数据,避免并发互相干扰。 解决什么:并发事务互相读取到对方中间状态数据,产生脏读等问题。 底层依赖:锁 + MVCC多版本并发控制。多个事务并发读写时,通过锁控制写冲突,MVCC控制读写冲突。
隔离性有强弱之分,也就是后面讲解的四大隔离级别。
2.4 D 持久性 Durability
是什么:事务一旦执行commit提交成功,修改就永久生效,即使数据库宕机、断电重启,修改的数据也不会丢失。 解决什么:内存数据断电丢失问题。 底层实现:redo log 重做日志。
MySQL修改数据优先修改内存页,不会立刻刷入磁盘数据文件。事务提交时,把修改记录写入redo log持久化到磁盘;就算宕机重启,MySQL读取redo log把已经提交的变更恢复到数据文件。
Mermaid:redo log持久化机制
ACID汇总表格
| 特性 | 全称 | 含义 | InnoDB底层依赖 |
|---|---|---|---|
| A | Atomicity 原子性 | 事务全部成功或全部回滚 | undo log回滚日志 |
| C | Consistency 一致性 | 事务前后数据业务状态合法正确 | 原子性+隔离性+持久性+业务约束 |
| I | Isolation 隔离性 | 并发事务之间数据互相隔离 | 锁 + MVCC |
| D | Durability 持久性 | 提交后修改永久保存,宕机不丢 | redo log重做日志 |
第三部分:并发事务会产生的三类问题
当多个事务同时读写同一份数据,如果隔离做得不够,就会出现三类经典现象:脏读、不可重复读、幻读。
注意:这三类是现象,不一定是bug,不同业务可以容忍不同现象。
3.1 脏读 Dirty Read
现象:一个事务读到了另一个事务还没有commit提交的数据;对方事务后续回滚,当前事务读到的数据就是无效脏数据。
时序示意Mermaid
业务危害:读到最终不会生效的数据,业务逻辑出错。只有读未提交隔离级别才会出现脏读。
3.2 不可重复读 Non‑Repeatable Read
现象:同一个事务内部,对同一条记录执行两次相同查询,中间被其他事务修改并且提交,两次查询返回结果不一样。 重点:针对同一条行记录,其他事务做update/update。
Mermaid时序示意
业务危害:同一个事务内,同一数据前后不一致,统计、报表逻辑出错。读已提交级别会出现;可重复读级别解决该现象。
3.3 幻读 Phantom Read
现象:同一个事务内,相同条件执行两次范围查询,其他事务执行insert/delete并提交,两次查询返回的行数发生变化,仿佛出现幻影行。 重点:针对范围查询,新增或者删除行,不是修改已有行。
Mermaid时序示意
业务危害:范围统计、批量更新,同一个事务内前后行数不一致。
注意:MySQL InnoDB RR(可重复读)级别,依靠临键锁Next‑Key Lock解决了幻读问题。
第四部分:InnoDB四大事务隔离级别
隔离级别由低到高,隔离越强,并发性能越低。每一级别对应允许或者禁止上面三种并发现象。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 实现手段 | 性能 |
|---|---|---|---|---|---|
| READ UNCOMMITTED 读未提交 | 允许 | 允许 | 允许 | 无隔离,直接读内存数据 | 最高 |
| READ COMMITTED 读已提交 RC | 禁止 | 允许 | 允许 | MVCC,每次select生成新ReadView | 高 |
| REPEATABLE READ 可重复读 RR(MySQL默认) | 禁止 | 禁止 | 禁止(InnoDB) | MVCC+临键锁Next‑Key Lock | 中等 |
| SERIALIZABLE 串行化 | 禁止 | 禁止 | 禁止 | 全部select隐式转当前读,加共享锁 | 最低 |
4.1 READ UNCOMMITTED 读未提交
- 现象:可以读到其他事务未提交的数据,会发生脏读。
- 业务场景:几乎不会使用。
- 设置语法:
SET SESSION TRANSACTION <span>ISOLATION</span> LEVEL READ UNCOMMITTED;
4.2 READ COMMITTED 读已提交(RC)
- 只能读到其他事务已经提交的数据,解决脏读;但是会出现不可重复读、幻读。
- MVCC行为:事务内每一次select查询,都会生成全新ReadView,所以可以看到别的事务已经提交的修改。
- 适用场景:大部分互联网业务,追求高并发,能够接受同一个事务内数据变化。
4.3 REPEATABLE READ 可重复读 RR(MySQL默认)
- 解决脏读、不可重复读;InnoDB通过临键锁解决幻读。
- MVCC行为:事务内第一次执行select的时候生成ReadView,整个事务复用同一个ReadView快照,保证同一个事务多次查询结果不变。
- 适用场景:MySQL默认,绝大多数业务直接使用该级别。
4.4 SERIALIZABLE 串行化
- 隔离级别最高,全部并发事务串行执行,所有问题全部解决。
- 实现:普通select语句会隐式转换为
SELECT ... LOCK IN SHARE MODE,读也加共享行锁,读写互相阻塞,并发性能很差。 - 适用场景:对数据一致性要求极高、并发很小的业务,极少使用。
Mermaid对比RC与RR ReadView生成时机
查看与设置隔离级别语法:
<span>-- 查看当前会话隔离级别</span>
<span>SELECT</span> @<span>@transaction</span>_isolation;
<span>-- 设置会话级别隔离级别</span>
<span>SET</span> SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
小节坑点
- RR是MySQL特有行为,标准SQL中RR隔离级别不能解决幻读,InnoDB依靠临键锁实现;
- RC隔离级别没有临键锁,幻读会出现。
第五部分:MVCC 多版本并发控制(重点章节)
MVCC Multi‑Version Concurrency Control,多版本并发控制,是InnoDB实现高并发读写的核心机制。
5.1 什么是MVCC
核心思想:写操作加锁,快照读不加锁;数据库保存数据的多个历史版本,读操作读取历史快照版本,做到读写互不阻塞。
5.2 为什么要有MVCC,解决什么
如果没有MVCC,为了保证隔离性,读数据也需要加锁。写的时候读被阻塞,读的时候写被阻塞,数据库并发能力会大幅下降。 MVCC实现:写加锁,快照读不加锁,读写不冲突,极大提升并发性能。
区分两个概念:快照读、当前读
- 快照读:普通
select * from table,走MVCC,读取快照版本,不加行锁。 - 当前读:
select ... for update、lock in share mode、update、delete。读取数据库最新版本,会加行锁。
SQL演示快照读和当前读区别:
<span>-- 快照读,不加锁,走MVCC</span>
<span>SELECT</span> <span>*</span> <span>FROM</span> account <span>WHERE</span> id <span>=</span> <span>1</span>;
<span>-- 当前读,加X排他锁,读取最新数据</span>
<span>SELECT</span> <span>*</span> <span>FROM</span> account <span>WHERE</span> id <span>=</span><span>1</span> <span>FOR</span> <span>UPDATE</span>;
5.3 MVCC核心组件
- 行隐藏字段:InnoDB每一行数据有隐藏列
DB_TRX_ID:最后修改这条记录的事务IDDB_ROLL_PTR:回滚指针,指向undo log里面上一个历史版本
- undo log:保存数据修改的历史版本,多个版本形成版本链表
- ReadView读视图:快照读的时候生成,一套判断规则,用来判断:这条历史版本对当前事务是否可见。
Mermaid:数据版本链 + ReadView判断逻辑
5.4 MVCC在RC、RR下核心差异
- RC(读已提交):每一次快照读,都会生成新ReadView;可以看到其他事务已经提交的修改。
- RR(可重复读):事务第一次快照读时生成ReadView,整个事务复用同一个ReadView,所以同一个事务多次查询结果不变。
小节坑点小结
- MVCC只针对快照读普通select生效;当前读(update、for update)不走快照,直接读最新数据,加锁;
- 长事务会导致undo log版本链无法回收,磁盘暴涨,数据库性能下降。
第六部分:事务常见线上问题与最佳实践
6.1 长事务的巨大危害
- 长事务不提交,行锁持续持有,容易发生锁等待、死锁;
- MDL锁跟随事务,长事务会直接造成DDL阻塞,引发业务雪崩;
- MVCC版本链不能回收,undo log持续膨胀,磁盘占用高,查询性能下降。
最佳实践:尽量缩小事务范围,事务内不要执行网络调用、http请求、大量耗时业务逻辑。
6.2 避免大事务
不要把大量insert/update放在同一个事务;可以拆分成多个小事务分批提交。
6.3 Spring事务高频坑
try‑catch捕获异常后,异常不会抛出,事务不会自动回滚,需要手动TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();- 非public方法、同类内部调用,@Transactional失效。
6.4 隔离级别选型建议
- 绝大多数业务直接使用MySQL默认RR;
- 追求更高并发、可以接受同一个事务内数据变化,选择RC;
- 串行化隔离级别,业务开发尽量避免。
文末综合总结
- undo log:实现原子性回滚,同时提供MVCC历史数据版本;
- redo log:实现持久性,保证宕机事务数据不丢失;
- 锁:解决写‑写并发冲突;
- MVCC:解决读写并发冲突,快照读不加锁,提升并发;
- RR可重复读是MySQL默认隔离级别,依靠MVCC+临键锁,同时解决不可重复读和幻读。
课后思考题(巩固)
- RC和RR隔离级别,MVCC ReadView生成时机有什么区别,造成什么现象?
- 快照读会不会被行锁阻塞?为什么?
- 为什么线上业务严禁运行长时间不提交的长事务?分别从锁、MDL、MVCC角度说明。
- ACID中的C一致性,是由哪几部分共同保障?
教学向长文,结构清晰、SQL 示例可直接运行,适合零基础建立事务认知,也适合有经验的后端开发者查漏补缺、备战面试。