MySQL InnoDB事务完整教程|ACID、MVCC、隔离级别、事务控制

文章来源声明: 原文作者:PCC; 来源站点:掘金; 原文链接:https://juejin.cn/post/7684659924174553114; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

教学向长文,结构清晰、SQL 示例可直接运行,适合零基础建立事务认知,也适合有经验的后端开发者查漏补缺、备战面试。

> 教学向博客:零基础可以建立完整认知,有开发经验的人用来巩固知识、备战面试。全部是后端开发高频核心知识点,剔除冷门边角内容。 实验环境:MySQL8.0,InnoDB引擎,默认隔离级别 REPEATABLE‑READ(可重复读)。

前言

本文完整讲解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>
    }
}

小节小结&坑点

  1. autocommit开启时,每条SQL就是一个独立小事务;
  2. 手动开启事务后,忘记commit,事务会一直存活,持续持有锁,引发锁等待、连接占用问题
  3. 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底层依赖
AAtomicity 原子性事务全部成功或全部回滚undo log回滚日志
CConsistency 一致性事务前后数据业务状态合法正确原子性+隔离性+持久性+业务约束
IIsolation 隔离性并发事务之间数据互相隔离锁 + MVCC
DDurability 持久性提交后修改永久保存,宕机不丢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;

小节坑点

  1. RR是MySQL特有行为,标准SQL中RR隔离级别不能解决幻读,InnoDB依靠临键锁实现;
  2. RC隔离级别没有临键锁,幻读会出现。

第五部分:MVCC 多版本并发控制(重点章节)

MVCC Multi‑Version Concurrency Control,多版本并发控制,是InnoDB实现高并发读写的核心机制。

5.1 什么是MVCC

核心思想:写操作加锁,快照读不加锁;数据库保存数据的多个历史版本,读操作读取历史快照版本,做到读写互不阻塞

5.2 为什么要有MVCC,解决什么

如果没有MVCC,为了保证隔离性,读数据也需要加锁。写的时候读被阻塞,读的时候写被阻塞,数据库并发能力会大幅下降。 MVCC实现:写加锁,快照读不加锁,读写不冲突,极大提升并发性能。

区分两个概念:快照读、当前读

  1. 快照读:普通select * from table,走MVCC,读取快照版本,不加行锁。
  2. 当前读select ... for updatelock 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核心组件

  1. 行隐藏字段:InnoDB每一行数据有隐藏列
  • DB_TRX_ID:最后修改这条记录的事务ID
  • DB_ROLL_PTR:回滚指针,指向undo log里面上一个历史版本
  1. undo log:保存数据修改的历史版本,多个版本形成版本链表
  2. ReadView读视图:快照读的时候生成,一套判断规则,用来判断:这条历史版本对当前事务是否可见。

Mermaid:数据版本链 + ReadView判断逻辑

5.4 MVCC在RC、RR下核心差异

  • RC(读已提交):每一次快照读,都会生成新ReadView;可以看到其他事务已经提交的修改。
  • RR(可重复读):事务第一次快照读时生成ReadView,整个事务复用同一个ReadView,所以同一个事务多次查询结果不变。

小节坑点小结

  1. MVCC只针对快照读普通select生效;当前读(update、for update)不走快照,直接读最新数据,加锁;
  2. 长事务会导致undo log版本链无法回收,磁盘暴涨,数据库性能下降。

第六部分:事务常见线上问题与最佳实践

6.1 长事务的巨大危害

  • 长事务不提交,行锁持续持有,容易发生锁等待、死锁;
  • MDL锁跟随事务,长事务会直接造成DDL阻塞,引发业务雪崩;
  • MVCC版本链不能回收,undo log持续膨胀,磁盘占用高,查询性能下降。

最佳实践:尽量缩小事务范围,事务内不要执行网络调用、http请求、大量耗时业务逻辑。

6.2 避免大事务

不要把大量insert/update放在同一个事务;可以拆分成多个小事务分批提交。

6.3 Spring事务高频坑

  1. try‑catch捕获异常后,异常不会抛出,事务不会自动回滚,需要手动TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
  2. 非public方法、同类内部调用,@Transactional失效。

6.4 隔离级别选型建议

  1. 绝大多数业务直接使用MySQL默认RR;
  2. 追求更高并发、可以接受同一个事务内数据变化,选择RC;
  3. 串行化隔离级别,业务开发尽量避免。

文末综合总结

  1. undo log:实现原子性回滚,同时提供MVCC历史数据版本;
  2. redo log:实现持久性,保证宕机事务数据不丢失;
  3. 锁:解决写‑写并发冲突;
  4. MVCC:解决读写并发冲突,快照读不加锁,提升并发;
  5. RR可重复读是MySQL默认隔离级别,依靠MVCC+临键锁,同时解决不可重复读和幻读。

课后思考题(巩固)

  1. RC和RR隔离级别,MVCC ReadView生成时机有什么区别,造成什么现象?
  2. 快照读会不会被行锁阻塞?为什么?
  3. 为什么线上业务严禁运行长时间不提交的长事务?分别从锁、MDL、MVCC角度说明。
  4. ACID中的C一致性,是由哪几部分共同保障?