【MySQL】数据库事务详解:ACID、隔离级别、Redo/Undo Log底层原理

📅 发布时间:2026/7/16 23:06:37 👁️ 浏览次数:
【MySQL】数据库事务详解:ACID、隔离级别、Redo/Undo Log底层原理
目录一、事务是什么一句话定义二、为什么需要事务从问题出发三、事务的四大特性ACID1. 原子性Atomicity2. 隔离性Isolation隔离级别读未提交、读已提交、可重复读、串行化3. 持久性Durability4. 一致性Consistency四、事务怎么用事务的基本操作autocommit 和单条 UPDATE 的自动 COMMIT五、事务的底层实现含MVCC底层原理1. 写操作UPDATE2. 读操作SELECT在可重复读下六、引擎支持七、一句话总结一、事务是什么一句话定义事务是一组 SQL 语句的打包这个包要么全部执行成功要么全部不执行不存在“执行一半”的状态。用代码表达BEGIN;-- 打包开始INSERTINTO...-- 操作1UPDATE...-- 操作2DELETE...-- 操作3COMMIT;-- 打包结束全部生效-- 或者 ROLLBACK; -- 全部撤销二、为什么需要事务从问题出发场景银行转账-- 从 A 账户扣 100 元UPDATEaccountSETmoneymoney-100WHEREnameA;-- 往 B 账户加 100 元UPDATEaccountSETmoneymoney100WHEREnameB;没有事务时的问题情况结果执行完第1条后断电A 扣了 100B 没收到 → 钱丢了 ❌执行完第1条后第2条出错A 扣了 100B 没加上 → 钱丢了 ❌两个客户端同时转账数据错乱 ❌有事务时BEGIN;UPDATEaccountSETmoneymoney-100WHEREnameA;UPDATEaccountSETmoneymoney100WHEREnameB;COMMIT;-- 要么全成功要么全失败事务的本质简化编程模型让你不用手动处理断电、并发、部分失败等问题。三、事务的四大特性ACID特性含义实现机制一句话原子性要么全做要么全不做Undo Log 回滚日志“后悔药”一致性数据始终符合规则约束 其他三个特性“不违法”隔离性事务之间互不干扰锁 MVCC“不打架”持久性提交后永久保存Redo Log重做日志“不丢失”Redo Log 与 Undo Log 对比关键区别特性Redo LogUndo Log记录内容修改后的数据新值修改前的数据旧值核心作用崩溃恢复、保证持久性事务回滚、保证原子性、MVCC时机事务提交前写提交时落盘事务修改前写随事务结束清理操作重做重放已提交修改撤销恢复未提交修改顺序顺序写随机写逻辑日志1. 原子性AtomicityBEGIN;UPDATEaccountSETmoneymoney-100WHEREid1;-- 第1步UPDATEaccountSETmoneymoney100WHEREid2;-- 第2步这里出错了-- 如果第2步失败第1步会被自动撤销ROLLBACK;-- 或者自动回滚底层执行 UPDATE 前旧值被写入 Undo Log。出错时从 Undo Log 读回旧值恢复。2. 隔离性Isolation我们花了最多时间讲这个。核心是多个事务同时执行时每个事务感觉不到其他事务的存在。问题不隔离会怎样并发问题含义例子脏读读到未提交的数据B 读到 A 还没确认的修改不可重复读同一事务内两次读同一行结果不同第一次读 100第二次读 200幻读同一事务内两次查询行数不同第一次 2 行第二次 3 行解决方案隔离级别隔离级别读未提交、读已提交、可重复读、串行化隔离级别脏读不可重复读幻读性能读未提交✅ 可能✅ 可能✅ 可能最高读已提交❌ 不可能✅ 可能✅ 可能高可重复读❌ 不可能❌ 不可能⚠️ 部分可能中串行化❌ 不可能❌ 不可能❌ 不可能极低读未提交read uncommitted含义一个事务可以读到另一个还没提交的事务修改的数据。例子事务abegin; update account set money0 where id1; -- 还没commit 事务bselect money from account where id1; -- 读到0 事务arollback; -- 实际上钱没变事务b读到了“最终没发生”的数据0这就是脏读。特点什么都不防什么都不隔离性能最高因为不加任何锁、不检查版本生产环境几乎不用读已提交read committed含义只能读到已经提交的数据读不到未提交的。解决了脏读但还有两个问题① 不可重复读事务a开始 事务b begin; select money from account where id1; -- 读到100 update account set money200 where id1; commit; select money from account where id1; -- 读到200变了同一个事务内两次读同一行结果不同 →不可重复读② 幻读事务a开始 事务b begin; select * from account; -- 查到2行 insert into account values (3, 王五, 500); commit; select * from account; -- 查到3行多了同一个事务内两次查询行数不同 →幻读特点这是 oracle、sql server 的默认隔离级别读的是“当前已提交的最新值”可重复读repeatable read含义同一个事务内多次读同一行结果始终一样。mysql 默认级别。解决了脏读 不可重复读但幻读在部分场景仍可能发生。如何解决不可重复读mvcc read view事务开始时拍快照之后所有读都读这个快照其他事务的修改对你不可见幻读的“部分可能”是什么意思普通 select快照读→ 没有幻读select ... for update当前读→ 可能产生幻读插入/更新时发现数据被其他事务插入 → 可能报错特点平衡了性能和隔离性mysql 选它作为默认是有道理的串行化serializable含义事务完全串行执行一个做完另一个才能做。效果所有并发问题全部解决每个读操作都隐式加锁行锁 间隙锁特点性能极低几乎等于单线程只在极严格场景使用比如金融对账一句话总结级别一句话读未提交“我不管我直接读最新的” → 脏读读已提交“我只读别人确认过的” → 但会读到别人的新提交可重复读“我只读我刚开始时看到的” → 别人改的我看不见串行化“你们都排队一次只跑一个事务” → 像单线程一样选择建议场景推荐级别普通业务系统可重复读mysql或 读已提交oracle对一致性要求不高、追求性能读已提交对一致性要求极高且并发低串行化几乎不用读未提交3. 持久性DurabilityBEGIN;UPDATE...COMMIT;-- COMMIT 后即使立刻断电数据也不会丢底层COMMIT 时修改先写入 Redo Log磁盘之后慢慢刷回数据文件。崩溃重启时从 Redo Log 重放。4. 一致性Consistency这是“结果性”特性事务执行前后数据必须符合所有规则主键、外键、唯一约束、业务规则等。例子转账后总额应该不变。事务前A100B100总额200事务后A0B200总额200 ✅如果出现 A0B100总额100 ❌违反一致性四、事务怎么用事务的基本操作-- 1. 开始事务begin;-- 或starttransaction;-- 2. 执行sqlinsertintoaccountvalues(1,张三,100);updateaccountsetmoney200whereid1;-- 3. 设置保存点可选savepointsp1;deletefromaccountwhereid2;-- 发现删错了rollbacktosp1;-- 回滚到保存点insert 和 update 还在-- 4. 提交或回滚commit;-- 全部生效-- 或rollback;-- 全部撤销注意事项情况结果没 COMMIT客户端崩溃自动回滚已经 COMMIT客户端崩溃数据已持久化不丢失COMMIT 后想回滚❌ 不可以单条 UPDATE 没写 BEGIN自动 COMMIT如果 autocommitONautocommit 和单条 UPDATE 的自动 COMMIT什么是 autocommitMySQL 中有一个系统变量叫autocommit它决定每条 SQL 语句是否自动成为一个独立的事务。-- 查看当前设置SHOWVARIABLESLIKEautocommit;-- 默认是 ONautocommit ON默认-- 不需要写 BEGINUPDATEaccountSETmoney200WHEREid1;-- 执行完这一条MySQL 自动执行了 COMMIT-- 数据立刻持久化无法回滚流程UPDATE 语句 │ ▼ MySQL 自动BEGIN隐式 │ ▼ 执行 UPDATE │ ▼ MySQL 自动COMMIT隐式 │ ▼ 数据持久化 ✅autocommit OFFSETautocommit0;UPDATEaccountSETmoney200WHEREid1;-- 这一条执行完了但并没有自动 COMMIT-- 数据只改了内存还没持久化-- 如果这时崩溃数据丢失-- 需要手动 COMMIT 才会持久化COMMIT;BEGIN 会覆盖 autocommit-- 不管 autocommit 是 ON 还是 OFFBEGIN;UPDATEaccountSETmoney200WHEREid1;-- 这里不会自动 COMMIT-- 必须手动 COMMIT 或 ROLLBACKCOMMIT;五、事务的底层实现含MVCC底层原理1. 写操作UPDATE执行 UPDATE id1 SET money200原值100 步骤1将旧值100写入 Undo Log 步骤2修改 Buffer Pool 中的值为200 步骤3在行记录中设置指针指向 Undo Log 中的旧版本2. 读操作SELECT在可重复读下事务A开始拍快照Read View 事务B修改并提交内存变成200 事务A再次SELECT 1. 看到内存中的200 2. 检查Read View事务B的提交时间 事务A的开始时间 3. 判断不能看200 4. 顺着版本链到Undo Log找到100 5. 返回100这就是 MVCC多版本并发控制读不阻塞写写不阻塞读读的是快照历史版本不是内存最新值六、引擎支持引擎支持事务原因InnoDB✅有 Undo Log、Redo Log、MVCC、行锁MyISAM❌没有这些机制MEMORY❌数据在内存不支持结论想用事务必须用ENGINEInnoDB七、一句话总结事务是数据库为了解决并发和数据一致性问题提供给应用层的“安全包装”。它用 ACID 保证可靠性用隔离级别平衡性能用 MVCC 实现高并发。你只需要 BEGIN/COMMIT剩下的数据库帮你搞定。