MySQL 事务深度解析:从 ACID 原理到实战避坑(附可视化指南)
想象一个场景:你在电商平台下单,扣了库存却没生成订单,或者付了钱却显示 “未支付”—— 这些令人崩溃的情况,本质都是事务未生效导致的数据不一致。今天我们用「原理 + 图解 + 代码」的方式,彻底搞懂 MySQL 事务的底层逻辑。
一、事务是什么?一句话讲清核心
事务(Transaction) 是数据库中「一组不可分割的 SQL 操作」,它遵循 “要么全部成功,要么全部失败” 的原则,就像生活中 “打包快递”:要么所有商品都装袋寄出,要么一件不寄(中途出错就重新打包)。
🌰 经典案例:电商下单的 3 步事务
一个完整的下单流程,必须作为事务执行:
- 扣减商品库存(UPDATE stock SET num = num - 1 WHERE id = 101);
- 创建用户订单(INSERT INTO order (user_id, goods_id) VALUES (1001, 101));
- 扣减用户余额(UPDATE user SET balance = balance - 99 WHERE id = 1001);
如果步骤 2 失败(比如订单表字段缺失),必须回滚步骤 1 的库存扣减,避免 “库存少了但没订单” 的混乱 —— 这就是事务的核心价值。
⚠️ 关键前提:只有支持事务的存储引擎才能用!MySQL 中InnoDB支持事务,而MyISAM/MEMORY不支持(这也是InnoDB成为主流的核心原因)。
二、事务的 “生死线”:ACID 四大性质(附原理图解)
事务能保障数据可靠,全靠 ACID 四大性质支撑。每个性质都不是 “口号”,而是有具体的技术实现 —— 我们逐个拆解:
1. 原子性(Atomicity):“要么全做,要么全不做”
核心目标:事务中的 SQL 操作,绝不存在 “部分执行” 的中间态。
比如转账时 “A 扣钱成功,B 加钱失败” 的情况,必须通过原子性回滚到初始状态。
🔧 MySQL 如何实现原子性?——undo log(回滚日志)
undo log就像 “事务的后悔药”,记录了数据修改前的状态:
- 执行UPDATE account SET balance = 900 WHERE id=1(原余额 1000)时,undo log会记录:id=1的balance应恢复为1000;
- 若事务提交(COMMIT):undo log标记为 “可清理”,后续由后台线程删除;
- 若事务失败(ROLLBACK/ 崩溃):MySQL 读取undo log,将数据恢复到修改前状态。

2. 一致性(Consistency):数据永远 “合规”
核心目标:事务执行前后,数据的完整性约束(如主键唯一、业务规则)不被破坏。
比如转账前 A+B 余额 = 2000,转账后必须还是 2000;订单表的status字段,绝不能出现 “已支付” 却没有pay_time的情况。
🛡️ 一致性是如何被保障的?
它是原子性、隔离性、持久性的 “合力结果”,再加上应用层逻辑:
- 原子性:避免 “部分修改” 导致数据断裂;
- 隔离性:避免其他事务的 “脏数据” 混入;
- 持久性:避免提交后数据丢失;
- 应用层:提前定义规则(如转账前判断 A 余额≥转账金额)。
📌 示例:如果应用层没判断 A 余额,直接执行转账 SQL,即使事务有原子性,也会出现 “余额为负” 的一致性问题 —— 所以一致性需要 “数据库 + 应用” 共同保障!
隔离性(Isolation):事务之间 “互不打扰”
当多个事务同时操作同一份数据时,会出现 3 类问题:
|
问题类型 |
场景示例(事务 A = 查询余额,事务 B = 修改余额) |
|
脏读 |
事务 B 修改余额为 900(未提交),事务 A 读到 900;随后 B 回滚,A 读的是 “脏数据” |
|
不可重复读 |
事务 A 第一次读余额 = 1000,事务 B 修改为 900 并提交;A 再次读 = 900,两次结果不同 |
|
幻读 |
事务 A 查询 “余额> 500 的用户”(10 人),事务 B 新增 1 个余额 600 的用户;A 再次查询 = 11 人,像出现 “幻觉” |
MySQL 通过「隔离级别」解决这些问题,4 个级别从低到高如下:
|
隔离级别 |
脏读 |
不可重复读 |
幻读 |
适用场景 |
实现原理 |
|
读未提交(Read Uncommitted) |
✅ |
✅ |
✅ |
临时统计(如粗糙的 PV 计数) |
直接读未提交数据,无隔离 |
|
读已提交(Read Committed) |
❌ |
✅ |
✅ |
电商详情页(实时价格) |
MVCC(多版本并发控制) |
|
可重复读(Repeatable Read) |
❌ |
❌ |
❌¹ |
订单创建(避免价格变动) |
MVCC+Next-Key Lock(间隙锁) |
|
串行化(Serializable) |
❌ |
❌ |
❌ |
财务对账(绝对一致性) |
表锁(禁止并发修改) |
注 ¹:MySQL 的InnoDB在 “可重复读” 级别下,通过Next-Key Lock解决了幻读 —— 这是它区别于其他数据库(如 PostgreSQL)的核心优势!
📊 MVCC 原理简化:为什么 “读已提交” 能避免脏读?
MVCC 通过 “数据版本链” 让不同事务看到不同版本的数据:
- 每行数据都有DB_TRX_ID(修改它的事务 ID)和DB_ROLL_PTR(指向 undo log 的指针);
- 事务读取数据时,会根据 “可见性规则” 选择合适的版本(如 “读已提交” 只看已提交事务的版本);
- 示例:事务 B 未提交的修改,事务 A 看不到,从而避免脏读。
持久性(Durability):提交后数据 “永不丢失”
核心目标:事务一旦提交(COMMIT),即使数据库崩溃,数据也不会丢失。
比如你支付成功后,就算服务器突然断电,重启后仍能看到 “已支付” 状态。
🔋 MySQL 如何实现持久性?——redo log(重做日志)
redo log是 “事务的保险箱”,它的工作流程如下:
1.执行UPDATE时,先把修改写入redo log buffer(内存);
2.事务提交时,redo log buffer的数据刷到磁盘的redo log file(持久化);
3.后台线程定期将redo log的修改合并到表文件(ibd文件)
4.若数据库崩溃,重启时会读取redo log,恢复已提交的事务。

1.事务的支持版本
我们可以查看哪些数据库引擎支持事务

其中transaction就是事务
2.事务提交方式
1.手动提交
查看事务提交方式

我们发现这里我们的自动提交是打开的
当然这个地方是可以关闭自动提交的 我只演示一下 但是后续操作还是在环境是自动提交打开的环境下进行的


要退出mysql后重新进入才能生效

我们先来创建一个表

手动开启事务的方式有两种

当然如果只开启一个事务可以用begin


我们可以创建保存点save1和save2 同时我们可以回滚到任意一个保存点 当我们直接rollback不指定保存点时 它就会直接回滚到最开始
我们还发现当commit时 此时保存的会被持久化 不会被回滚


我们发现读未提交的隔离性 也就是 当我没有commit持久化的数据 另一个机器也能读到
同时也意味着我一旦rollback 另一个机器就无法读到了



及不管你的autocommit是开还是关 对手动提交无影响 手动提交要想提交只能commit


实验2

通过实验一实验二我们可以知道
show variables like 'autocommit';
--查看的是单条sql的事务的提交方式
--而不影响star trasaction和begin
结论
1215

被折叠的 条评论
为什么被折叠?



