点击进入作者专栏《Java 技术精讲并发与核心组件》 ✅《零基础学 MySQL》 ✅文章目录一、先用转账理解事务二、事务的基本操作开始、提交、回滚与保存点1. 开始事务2. 提交事务3. 回滚事务4. 使用保存点进行部分回滚5. 别忘了 autocommit三、ACID 到底是什么四、原子性不能只扣钱不加钱五、一致性ACID 最终想守住的结果六、隔离性并发事务不能随意干扰七、持久性COMMIT 后为什么不怕突然宕机八、并发事务会产生哪些问题1. 脏读读到了别人尚未提交的数据2. 不可重复读同一行前后两次读取不同3. 幻读同一范围前后出现了“新成员”4. 丢失更新后一次写入覆盖了前一次结果九、MySQL 的四种事务隔离级别十、四种隔离级别分别做了什么1. READ UNCOMMITTED读未提交2. READ COMMITTED读已提交3. REPEATABLE READ可重复读4. SERIALIZABLE串行化十一、MVCC 是什么在这里插入图片描述十二、READ COMMITTED 与 REPEATABLE READ 的核心区别READ COMMITTED每次查询使用新快照REPEATABLE READ同一事务复用首次一致性读的快照十三、快照读与当前读不能混为一谈1. 快照读2. 锁定读与当前数据十四、InnoDB 在可重复读下怎样处理幻读对普通快照读对锁定范围读和写操作十五、动手实验用两个窗口观察不可重复读窗口 A窗口 B回到窗口 A十六、怎样查看和设置隔离级别十七、隔离级别越高越好吗十八、事务开发中的八条实用建议1. 事务尽量短2. 修改数据前设计好访问顺序3. 为查询和更新条件建立合适索引4. 正确处理死锁与锁等待超时5. 不要只靠“先查再改”保证库存6. 使用唯一约束保证幂等7. 注意 DDL 的隐式提交8. 确认表使用支持事务的存储引擎十九、一个更可靠的库存扣减示例二十、常见面试题快速回答1. 什么是事务2. ACID 分别是什么3. undo log 和 redo log 有什么区别4. 脏读、不可重复读、幻读有什么区别5. RC 和 RR 的核心区别是什么6. MVCC 是不是完全不加锁7. InnoDB 的 RR 怎样避免幻读8. MySQL 默认隔离级别是什么二十一、总结参考资料本文适合谁看会写INSERT、UPDATE、DELETE但不清楚事务为什么重要知道COMMIT和ROLLBACK却分不清 ACID 四个特性总把脏读、不可重复读、幻读混在一起正在准备 Java 后端或 MySQL 面试想弄懂 MVCC、快照读和当前读在实际项目中一次业务操作往往不只执行一条 SQL。例如用户下单时系统可能需要创建订单、扣减库存、记录支付信息银行转账时需要先扣除付款人的余额再增加收款人的余额。只要其中一步失败数据就可能进入错误状态。事务解决的正是这个问题把一组相关操作当成一个不可随意拆开的整体执行。本文从转账开始逐步讲清事务、ACID、并发异常、四种隔离级别以及 InnoDB 中的 MVCC 和锁。一、先用转账理解事务假设张三账户有 1000 元李四账户有 500 元张三要给李四转账 100 元。数据库至少要执行两步UPDATEaccountSETbalancebalance-100WHEREname张三;UPDATEaccountSETbalancebalance100WHEREname李四;正常执行后张三1000 → 900 李四 500 → 600 总额1500 → 1500但如果第一条 SQL 成功第二条 SQL 因为网络、程序或数据库异常而失败就会变成张三1000 → 900 李四 500 → 500 总额1500 → 1400张三的钱扣了李四却没有收到。这显然不是一个可以接受的结果。事务要求这两步要么一起成功要么一起撤销STARTTRANSACTION;UPDATEaccountSETbalancebalance-100WHEREname张三;UPDATEaccountSETbalancebalance100WHEREname李四;COMMIT;如果中途发生异常则执行ROLLBACK;一句话理解事务事务是具有明确开始和结束边界的一组数据库操作这组操作作为一个整体提交或回滚。图 1 正常转账与异常转账对比二、事务的基本操作开始、提交、回滚与保存点1. 开始事务下面两种写法都可以显式开启事务STARTTRANSACTION;BEGIN;2. 提交事务COMMIT;提交表示事务中的修改被确认。其他事务在隔离规则允许的情况下可以看到这些修改。3. 回滚事务ROLLBACK;回滚会撤销当前事务中尚未提交的修改。4. 使用保存点进行部分回滚有时并不想撤销整个事务可以先设置保存点STARTTRANSACTION;UPDATEaccountSETbalancebalance-100WHEREid1;UPDATEaccountSETbalancebalance100WHEREid2;SAVEPOINTtransfer_finished;-- 假设后续写入一条非核心备注现在只想撤销保存点之后的操作INSERTINTOtransfer_note(content)VALUES(临时备注);ROLLBACKTOSAVEPOINTtransfer_finished;COMMIT;ROLLBACK TO SAVEPOINT只回到指定位置不等于结束整个事务。5. 别忘了autocommitMySQL 默认通常开启自动提交SELECTautocommit;如果结果为1说明在没有显式开启事务时每条可提交的 SQL 通常会作为一个独立事务自动提交。因此连续执行两条 SQL如果没有放入同一个显式事务就可能各自提交无法保证一起成功或一起失败。常见误区连续执行多条 SQL不代表它们天然属于同一个事务。必须明确事务边界或由应用框架正确管理事务。三、ACID 到底是什么可靠事务通常用 ACID 四个特性描述特性英文核心问题通俗理解原子性Atomicity操作能否只完成一半要么全做要么全不做一致性Consistency业务规则是否仍成立事务前后数据都正确隔离性Isolation并发事务是否互相干扰别人的中间状态不能随便看持久性Durability提交后数据是否会丢提交成功就要可靠保存这四个特性不是互相独立的四个按钮而是数据库日志、锁、MVCC、约束和业务代码共同实现的结果。图 2 ACID 四大特性总览四、原子性不能只扣钱不加钱原子性强调一个事务是不可随意拆分的整体。转账中的扣款和收款必须绑定在一起。如果第二步失败第一步也要撤销数据库恢复到事务开始前的状态。InnoDB 的回滚能力与undo log回滚日志密切相关。可以先把它理解为数据库保存了构造旧版本所需的信息修改前balance 1000 修改后balance 900如果事务需要回滚InnoDB 可以利用 undo 信息撤销尚未提交的修改。undo 还会服务于后面要讲的 MVCC用来构造某些事务需要看到的旧版本。但不能把原子性简单背成“完全由 undo log 单独实现”。事务管理、提交与回滚机制也共同参与其中。五、一致性ACID 最终想守住的结果一致性表示事务执行前后数据都应满足系统规定的约束与业务规则。例如转账业务可能有这些规则两个账户的资金总额不能凭空增加或减少账户余额不能低于业务允许的下限转账记录必须能对应真实账户同一笔支付不能被重复处理转账前后张三和李四的总余额都应是 1500 元。这就是一种业务一致性。一致性不能只靠数据库中的某一个日志实现而是共同结果原子性 隔离性 持久性 主键、唯一约束、外键、检查约束 正确的业务代码和事务边界例如数据库即使保证事务完整提交也不知道“每个用户每天最多领取一次优惠券”这条业务规则仍然需要唯一索引或业务逻辑进行约束。一句话理解一致性是目标原子性、隔离性和持久性是帮助实现这个目标的重要保证。六、隔离性并发事务不能随意干扰数据库不可能永远只服务一个用户。当事务 A 正在读取余额时事务 B 可能正在修改同一行事务 C 还可能插入一条符合 A 查询条件的新记录。如果完全没有隔离机制一个事务就可能读到其他事务未完成的中间结果。InnoDB 主要结合以下机制处理并发MVCC让许多普通查询通过数据版本读取合适的快照减少读写之间的相互阻塞锁在修改数据或执行锁定读时控制其他事务对相关记录或范围的操作隔离级别决定事务可以看到哪些数据版本以及使用怎样的锁策略隔离越强结果通常越稳定但事务之间等待的机会也可能增加。因此数据库不会只提供一个固定强度而是提供多种隔离级别。七、持久性COMMIT后为什么不怕突然宕机持久性表示事务一旦成功提交修改就应该能够在故障后恢复而不是只存在于内存中。InnoDB 的redo log重做日志是实现崩溃恢复的重要基础。数据页的修改可以先在内存中完成再按照机制写入数据文件redo log 记录的是重做相关修改所需的信息。如果数据库在数据页完全写回磁盘前宕机重启时可以利用 redo log 恢复已提交事务的修改。可以先这样区分日志主要用途记忆方式undo log回滚与构造旧版本“怎样回到以前”redo log崩溃恢复已完成的修改“怎样把修改再做一遍”不过持久性的强弱还会受到刷盘参数、操作系统、存储设备和备份策略等因素影响。不能把它简单理解为“只要有 redo log任何硬件故障都绝对不会丢数据”。图 3 undo log 与 redo log 对比八、并发事务会产生哪些问题脏读、不可重复读和幻读是最常见的三种并发读取异常。此外业务开发中还应注意丢失更新。1. 脏读读到了别人尚未提交的数据事务 A把余额从 1000 改成 900尚未提交 事务 B读到余额为 900 事务 A发生异常并回滚余额恢复为 1000事务 B 读到的 900 从未真正提交这就是脏数据。关键词读到了别人未提交、以后可能被撤销的数据。2. 不可重复读同一行前后两次读取不同事务 A第一次读取余额得到 1000 事务 B把余额修改为 900并提交 事务 A第二次读取余额得到 900事务 A 在同一个事务中两次读取同一行却得到不同结果。关键词同一行被其他事务修改或删除前后读取结果不同。3. 幻读同一范围前后出现了“新成员”事务 A查询 age 18 的用户共 10 条 事务 B插入一名 age 20 的用户并提交 事务 A再次执行相同范围查询变成 11 条第二次查询多出一行好像出现了“幻影”。关键词关注符合某个范围条件的记录集合发生变化典型原因是插入。4. 丢失更新后一次写入覆盖了前一次结果事务 A 读到库存 10准备减 1 事务 B 也读到库存 10准备减 1 事务 A 写入 9 事务 B 也写入 9实际卖出两件库存却只减少一件。隔离级别并不能自动替所有业务消灭这类错误通常还要使用原子更新、锁或乐观锁。例如库存扣减可以尽量写成UPDATEproductSETstockstock-1WHEREid1001ANDstock0;然后检查受影响行数是否为1。图 4 四种并发问题对比九、MySQL 的四种事务隔离级别InnoDB 支持四种标准隔离级别默认级别是REPEATABLE READ。隔离级别中文脏读不可重复读幻读并发能力READ UNCOMMITTED读未提交可能可能可能较高READ COMMITTED读已提交避免可能可能较高REPEATABLE READ可重复读避免避免标准定义下可能InnoDB 有额外处理较均衡SERIALIZABLE串行化避免避免避免较低这个表适合建立整体认识但不能只靠打勾和打叉理解 InnoDB。尤其是可重复读与幻读需要区分快照读和锁定读后面会专门解释。图 5 四种隔离级别阶梯图十、四种隔离级别分别做了什么1. READ UNCOMMITTED读未提交普通查询可能看到其他事务尚未提交的修改因此可能发生脏读。这个级别的读取结果不够稳定实际业务中很少采用。它并不等于“所有操作都完全不加锁”写操作仍然要遵守 InnoDB 的并发控制。2. READ COMMITTED读已提交一个事务只能通过普通一致性读看到其他事务已经提交的数据因此避免了脏读。但在同一个事务中每次普通一致性读都会建立新的数据快照。如果其他事务在两次查询之间提交了修改第二次查询就可能看到新结果因此可能发生不可重复读和幻读。3. REPEATABLE READ可重复读这是 InnoDB 的默认隔离级别。同一事务内的普通一致性读通常使用第一次一致性读建立的快照。即使其他事务之后提交了修改当前事务再次执行普通SELECT时通常仍看到原快照中的数据。因此第一次普通 SELECTbalance 1000 其他事务修改并提交balance 900 第二次普通 SELECT仍可能看到 balance 10004. SERIALIZABLE串行化这是隔离性最强的级别。事务尽量表现得像按顺序执行。在关闭自动提交的情况下InnoDB 会把普通SELECT隐式转换为共享锁定读从而增加事务之间的等待降低并发能力。因此它一般用于对并发一致性要求极高的特殊场景而不是看到“最安全”就直接用于所有系统。十一、MVCC 是什么MVCC 的全称是Multi-Version Concurrency Control中文叫多版本并发控制。它的核心思想不是让所有事务排队而是让同一行数据可以根据 undo 信息构造出多个历史版本不同事务按照可见性规则读取适合自己的版本。InnoDB 记录中有用于版本管理的隐藏信息其中常见的两个概念是DB_TRX_ID最近一次插入或更新该记录的事务标识DB_ROLL_PTR指向 undo 记录可沿它寻找更早版本一行数据被多次修改后可以把版本关系理解成当前版本balance 700事务 30 修改 ↓ DB_ROLL_PTR 旧版本balance 800事务 20 修改 ↓ DB_ROLL_PTR 更旧版本balance 1000事务 10 创建事务执行普通一致性读时会根据Read View读视图判断哪些事务版本对自己可见。如果当前版本不可见就沿版本链寻找符合规则的旧版本。这使许多普通查询无需阻塞正在修改数据的事务也无需直接读取尚未提交的中间状态。注意MVCC 不是“复制很多完整数据表”也不是“有 MVCC 就完全不加锁”。它主要服务于一致性非锁定读修改操作和锁定读仍然会使用锁。图 6 MVCC 版本链与 Read View十二、READ COMMITTED 与 REPEATABLE READ 的核心区别对普通一致性读来说两者最关键的区别是 Read View 的建立时机。READ COMMITTED每次查询使用新快照事务 A 第一次 SELECT → 建立快照 1 → 看到 1000 事务 B UPDATE 并 COMMIT → 改成 900 事务 A 第二次 SELECT → 建立快照 2 → 看到 900因此同一事务前后查询可能不一致。REPEATABLE READ同一事务复用首次一致性读的快照事务 A 第一次 SELECT → 建立快照 → 看到 1000 事务 B UPDATE 并 COMMIT → 改成 900 事务 A 第二次 SELECT → 沿用快照 → 仍看到 1000当前事务自己执行的修改仍然可以被自己看到并不是永远停留在事务开始时的画面。对比项READ COMMITTEDREPEATABLE READ普通一致性读的快照每次读取重新建立通常沿用首次一致性读的快照能否看到其他事务新提交的数据后续查询可以同一快照内通常不可以不可重复读可能发生普通一致性读中可避免InnoDB 默认级别否是图 7 RC 与 RR 快照差异时间线十三、快照读与当前读不能混为一谈1. 快照读普通SELECT通常属于一致性非锁定读也常被称为快照读SELECTbalanceFROMaccountWHEREid1;它主要通过 MVCC 读取符合可见性规则的数据版本一般不会为了读取而给目标记录加行锁。2. 锁定读与当前数据下面的查询要读取适合后续修改的较新状态并对记录加锁SELECTbalanceFROMaccountWHEREid1FORUPDATE;或者SELECTbalanceFROMaccountWHEREid1FORSHARE;UPDATE和DELETE等写操作也需要基于当前可修改的数据进行处理。因此在可重复读事务中可能出现这种现象普通SELECT仍看到快照中的旧值而SELECT ... FOR UPDATE读取并锁定的是较新的当前记录。两种读取服务的目的不同不能因为结果不同就断定数据库出错。类型常见语句是否主要依赖 MVCC是否加锁快照读普通SELECT是通常不加记录锁锁定读SELECT ... FOR UPDATE否读取用于锁定的当前记录是锁定读SELECT ... FOR SHARE否读取用于锁定的当前记录是写操作UPDATE、DELETE要处理当前数据是十四、InnoDB 在可重复读下怎样处理幻读“可重复读是否会发生幻读”不能只回答一个“会”或“不会”。对普通快照读在同一个可重复读事务中普通SELECT通常沿用同一个快照。其他事务后来插入并提交的新行对这个快照通常不可见所以重复执行相同范围查询时一般不会看到新行。对锁定范围读和写操作如果执行SELECT*FROMuserWHEREageBETWEEN18AND25FORUPDATE;InnoDB 在可重复读下可能使用next-key lock临键锁。它可以简单理解为记录锁 记录前面的间隙锁这样不仅锁住已经存在的索引记录还会限制其他事务向被锁定的索引范围间隙插入新记录从而防止符合条件的新行突然出现。索引会直接影响锁定范围。如果查询缺少合适索引InnoDB 可能扫描并锁住更大的范围增加阻塞风险。因此并发控制和索引设计往往要一起考虑。准确记法InnoDB 的可重复读通过 MVCC 快照让普通一致性读避免看到新的幻影记录并通过 next-key lock 等机制处理锁定范围读中的幻读问题。图 8 next-key lock 防止幻读十五、动手实验用两个窗口观察不可重复读可以打开两个 MySQL 客户端窗口分别作为事务 A 和事务 B。先准备数据CREATETABLEaccount(idBIGINTPRIMARYKEY,nameVARCHAR(50)NOTNULL,balanceDECIMAL(10,2)NOTNULL)ENGINEInnoDB;INSERTINTOaccount(id,name,balance)VALUES(1,张三,1000.00);窗口 ASETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED;STARTTRANSACTION;SELECTbalanceFROMaccountWHEREid1;-- 第一次通常读到 1000.00窗口 BSTARTTRANSACTION;UPDATEaccountSETbalance900.00WHEREid1;COMMIT;回到窗口 ASELECTbalanceFROMaccountWHEREid1;-- READ COMMITTED 下通常读到 900.00COMMIT;把窗口 A 的隔离级别改成REPEATABLE READ将余额恢复为 1000 后重新实验。同一事务内第二次普通查询通常仍会读到第一次快照中的 1000。实验提醒两个窗口必须使用两个独立数据库连接每轮实验前恢复初始数据并确认事务已经提交或回滚。十六、怎样查看和设置隔离级别查看当前会话使用的隔离级别SELECTtransaction_isolation;也可以查看变量SHOWVARIABLESLIKEtransaction_isolation;设置当前会话后续事务的隔离级别SETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED;只设置下一个事务的隔离级别SETTRANSACTIONISOLATIONLEVELREPEATABLEREAD;STARTTRANSACTION;隔离级别应在事务开始前正确设置。生产环境中不要为了测试随意修改全局隔离级别否则可能影响新建立的其他连接。十七、隔离级别越高越好吗不是。隔离级别的选择是在一致性需求和并发成本之间取平衡。业务场景常见考虑普通互联网业务结合框架与现有架构选择 RC 或 RR同一事务需要稳定快照RR 更容易提供重复读取的一致视图报表和批量读取关注快照稳定性、长事务和历史版本堆积资金、库存、订单不能只依赖隔离级别还需锁、约束、幂等和业务校验极强串行要求的特殊场景才考虑 Serializable并评估阻塞和吞吐量不要因为SERIALIZABLE最严格就直接选择它也不要因为READ COMMITTED并发较好就认为它一定更快。实际效果与 SQL、索引、事务长度、冲突比例和数据库配置都有关系。十八、事务开发中的八条实用建议1. 事务尽量短不要在事务中长时间等待用户输入、调用缓慢的外部接口或执行无关逻辑。事务越长锁持有时间越长undo 历史版本也可能保留更久。2. 修改数据前设计好访问顺序多个事务以不同顺序锁定相同资源容易形成死锁。业务中尽量按照统一顺序访问表和记录。3. 为查询和更新条件建立合适索引缺少索引可能导致扫描和锁定范围扩大增加并发冲突。4. 正确处理死锁与锁等待超时死锁并不一定代表数据库故障。InnoDB 检测到死锁后会选择一个事务回滚应用应捕获异常并在确保幂等的前提下重试完整事务。5. 不要只靠“先查再改”保证库存优先使用带条件的原子更新并检查受影响行数确实需要读取后决策时再考虑SELECT ... FOR UPDATE。6. 使用唯一约束保证幂等订单号、支付流水号、业务请求号等可以建立唯一索引避免网络重试导致重复写入。7. 注意 DDL 的隐式提交许多 DDL 语句会导致隐式提交。不要想当然地认为把CREATE TABLE、ALTER TABLE与普通 DML 放在一起后都能通过一次ROLLBACK撤销。8. 确认表使用支持事务的存储引擎本文讨论的是 InnoDB。事务能力与存储引擎有关不能看到 MySQL 就默认所有表都具有完全相同的事务行为。十九、一个更可靠的库存扣减示例需求商品1001每次下单扣减一件库存库存不足时不能下单。STARTTRANSACTION;UPDATEproductSETstockstock-1WHEREid1001ANDstock0;-- 应用程序检查受影响行数-- 1扣减成功继续创建订单-- 0库存不足执行 ROLLBACKINSERTINTOorders(order_no,product_id,quantity)VALUES(ORDER_202609060001,1001,1);COMMIT;为了避免同一请求重复创建订单还可以为order_no建立唯一索引ALTERTABLEordersADDUNIQUEINDEXuk_order_no(order_no);这个例子说明可靠业务通常需要多层配合事务保证整体提交或回滚 原子更新防止库存超卖 唯一索引保证请求幂等 异常处理负责回滚和重试事务很重要但事务并不会自动理解全部业务规则。二十、常见面试题快速回答1. 什么是事务事务是一组具有明确边界的数据库操作它们作为一个整体提交或回滚目的是保证复杂业务操作的数据正确性。2. ACID 分别是什么原子性事务中的操作要么全部完成要么全部撤销一致性事务执行前后数据都满足约束与业务规则隔离性并发事务按照隔离规则互相隔离持久性提交后的修改能够在故障后恢复3. undo log 和 redo log 有什么区别undo log 主要用于事务回滚和 MVCC 旧版本构造redo log 主要用于崩溃恢复帮助恢复已经提交但尚未完全写回数据文件的修改。4. 脏读、不可重复读、幻读有什么区别脏读读到其他事务未提交的数据不可重复读同一行前后两次读取结果不同幻读相同范围查询前后出现或消失了符合条件的记录5. RC 和 RR 的核心区别是什么在 InnoDB 的普通一致性读中RC 通常每次查询建立新快照RR 通常在同一事务中沿用首次一致性读建立的快照。6. MVCC 是不是完全不加锁不是。MVCC 主要让普通一致性读读取合适的数据版本写操作、锁定读以及部分约束检查仍然需要锁。7. InnoDB 的 RR 怎样避免幻读普通一致性读利用同一快照避免看到其他事务后来提交的新行锁定范围读则可通过 next-key lock 等机制限制范围内插入。8. MySQL 默认隔离级别是什么InnoDB 默认是REPEATABLE READ但项目仍应通过配置和实际连接确认不要只靠背诵。二十一、总结学习 MySQL 事务可以按照下面这条主线记忆多条 SQL 需要作为整体执行 ↓ 事务通过 COMMIT 或 ROLLBACK 确定结果 ↓ ACID 描述可靠事务应具备的特性 ↓ 并发执行产生脏读、不可重复读和幻读等问题 ↓ 隔离级别决定事务之间能看到什么 ↓ InnoDB 使用 MVCC、undo、redo 和锁共同支撑事务参考资料MySQL 8.4 Reference ManualInnoDB and the ACID ModelMySQL 8.4 Reference ManualTransaction Isolation LevelsMySQL 8.4 Reference ManualConsistent Nonlocking ReadsMySQL 8.4 Reference ManualPhantom RowsMySQL 8.4 Reference ManualInnoDB Multi-VersioningMySQL 8.4 Reference ManualRedo LogMySQL 8.4 Reference ManualUndo Logs前路漫漫同频的人终会奔赴同一个方向✨如果这篇内容对你有所启发麻烦点个赞、关注博主干货建议先收藏避免之后找不到。后续还会持续输出更多实用教程创作不易还请多多支持