【1】哪怕服务器当场爆炸,你的钱也丢不了!一文带你理清MySQL事务原理

news2026/5/1 19:06:48
写在前面设想一个很日常的场景手机银行里点了一次转账页面转了几秒最后弹出来一句“系统繁忙请稍后再试”。这时候脑子里最先冒出来的往往不是“重试一下就行”而是更具体也更扎心的那句钱到底扣了没有对方到底收到了没有MySQL事务要解决的就是这类“看起来只是一次失败提示但背后可能留下半成品”的问题。用最普通的银行转账把这个问题立住再往下讲ACID、隔离级别、MVCC、锁、undo/redo会顺很多。先把例子定死账户A有5000元账户B有3000元。现在要从A给B转1000元。账户表idownerbalancestatus1A5000active2B3000active转账流水表txn_idfrom_accountto_accountamountstatecreated_at9001121000init2026-04-23 10:00:00业务上看这次转账至少有三步从A账户扣掉1000给B账户加上1000把这笔转账流水记为完成如果把它们当作三条彼此独立的SQL去执行中间任何一步失败数据库都可能留下一个不该存在的中间状态。比如先扣款成功程序还没来得及给B加钱就崩了时刻A 余额B 余额说明初始50003000转账前扣款后40003000第一步已经执行崩溃后40003000第二步、第三步没执行完这时候最严重的问题不是“程序报错了”而是数据库已经把错误状态保存下来了。A的钱少了B没收到总额凭空少了1000。事务就是为这种场景存在的。它要解决的不是“让多条SQL写在一起更整齐”而是让一组业务动作要么一起成功要么一起失败。先看BEGIN到COMMIT包住了什么把刚才的转账写成事务大概会是这样STARTTRANSACTION;UPDATEaccountSETbalancebalance-1000WHEREid1;UPDATEaccountSETbalancebalance1000WHEREid2;UPDATEtransfer_logSETstatedoneWHEREtxn_id9001;COMMIT;下面这张示意图把这条链路放在同一张图里事务从哪里开始哪里可能回滚提交之后又靠什么保证结果不丢。如果中途发现任何问题比如余额不足、网络异常、业务校验失败也可以直接回滚STARTTRANSACTION;UPDATEaccountSETbalancebalance-1000WHEREid1;-- 这里发现异常ROLLBACK;事务最朴素的语义可以先记成下面这张表操作是否算最终生效作用START TRANSACTION否开启一个事务上下文中间若干条INSERT/UPDATE/DELETE暂时不算最终完成这些修改后面还可能回滚COMMIT是把整组修改一起提交ROLLBACK否撤销整组尚未提交的修改也就是说事务真正保护的是业务动作的整体性。转账不是“扣款语句”和“加款语句”的简单相加而是一个不可拆开的业务单元。用银行转账把ACID四个字母落地讲MySQL事务时最常见的四个字母是ACID。这四个字母经常被背下来但没有落到业务里就会显得很空。还是回到这笔转账属性中文名普通解释在转账里的意思如果没有会怎样Atomicity原子性一组操作要么一起成功要么一起失败扣款、加款、记流水要么都成功要么都失败只扣不加留下半成品Consistency一致性事务前后数据和约束都要保持正确转账前后约束和业务规则仍成立总金额失真余额可能出现非法值Isolation隔离性并发事务之间不要互相看见半成品或互相干扰并发事务之间尽量互不干扰别人可能看到未完成状态Durability持久性一旦提交成功结果就不能因为宕机丢掉一旦提交成功宕机后结果也不能丢刚转成功机器一掉电就没了这四个词里最容易让人误会的是Consistency。它不是说“数据库自己替业务做所有正确性判断”而是说事务执行前后数据库约束和业务要求不能被破坏。比如转账前后总余额应该守恒比如余额字段不能写成非法值比如流水状态不能既是done又没完成加款。四个字母里最有工程味道的两个其实是后面的Isolation和Durability。前者主要处理并发后者主要处理宕机恢复。MySQL事务真正复杂的部分大多都藏在这里。两个人同时操作时问题才真正开始单线程世界里的事务并不难理解。复杂度来自并发。设想两个柜员同时操作同一个账户事务T1执行A - B转1000事务T2在T1还没提交时查询A的余额或者也从A再扣一笔钱这时数据库要回答的问题就不再只是“要不要回滚”而是T2能不能看到T1还没提交的扣款结果T2在同一个事务里查两次余额结果能不能不同T2做范围查询时会不会突然多出一行刚插入的数据把几个典型并发时点压成一张示意表大概是这样时间T1T2风险10:00扣掉 A 的 1000尚未提交-A 出现未提交版本10:01未提交第一次查询 A 的余额在Read Uncommitted下可能脏读10:02提交第二次查询 A 的余额在Read Committed下可能前后不一致10:03若另一事务插入新待处理转账并提交再次做范围统计可能出现幻读这就是隔离级别要解决的问题。它不是一个抽象开关而是数据库在“并发性能”和“读写正确性”之间做的不同取舍。隔离级别在防什么InnoDB支持四种标准隔离级别隔离级别脏读不可重复读幻读特点Read Uncommitted可能可能可能几乎不做隔离能读到未提交数据Read Committed避免可能可能每次一致性读都拿新快照Repeatable Read避免避免对普通一致性读固定快照对锁定读依靠锁控制范围InnoDB默认级别Serializable避免避免避免最强但并发代价最高先把三个常见问题说清1. 脏读T1扣了A的钱但还没提交T2已经把这个结果读走了。后来T1回滚说明T2刚才看到的是一份从未真正成立过的数据。2. 不可重复读T2在同一个事务里查了两次A的余额。第一次是5000第二次变成4000。中间自己没改过任何东西但读取结果变了。3. 幻读T2先查“金额大于5000的待处理转账有几笔”结果是3笔。另一个事务插入一条新数据并提交后T2再查一次变成4笔。不是某一行变了而是结果集合里“多出了一行幻影”。从银行系统的角度看这三种问题都很糟。差别只是糟的方式不同。为什么InnoDB不是所有读取都直接加锁很多人第一次接触并发控制时会自然想到一个简单方案既然并发会出问题那就把正在读写的数据都锁起来。这个方案能工作但扩展性很差。银行系统有大量查询请求查账户余额查最近流水查某个时间段内的转账记录如果一笔转账开始后所有相关查询都必须等它彻底结束系统吞吐会迅速下降。数据库不能只追求“绝对安全”还得考虑“可并发地安全”。InnoDB的核心思路是把读分成两类读取方式典型语句特点一致性读普通SELECT不加锁基于快照读当前读SELECT ... FOR UPDATE、SELECT ... FOR SHARE、UPDATE、DELETE读取当前最新可操作版本并配合加锁这个区分很重要。查询余额这种场景很多时候只需要一个逻辑自洽的快照并不一定要把别人拦住。真正需要锁的是那些会参与后续修改、必须和最新状态对齐的操作。于是InnoDB没有走“所有读都加锁”的路线而是引入了MVCC。MVCC先解决的是“读不要总跟写打架”MVCC是多版本并发控制。它的核心不是一句“一个数据有多个版本”就结束了而是一行数据被修改时旧版本不会立刻消失不同事务可以按自己的可见性规则看到不同版本普通SELECT尽量不阻塞写写也尽量不阻塞普通SELECT这一点用一张图会直观很多同一行有版本链普通SELECT更像是在读“对自己可见的快照”而FOR UPDATE / UPDATE这类操作则需要盯住“最新且可修改”的版本。如果更想直接看“快照读”和“当前读”分别盯住哪个版本下面这张对照图会更直白一些还是看账户A的余额。初始是5000。事务T1发起转账把它改成4000但尚未提交。可以把版本关系理解成下面这样版本balance创建该版本的事务 id是否已提交备注V15000100是初始版本V24000101否T1扣款后产生这时另一个事务T2如果执行普通SELECT在默认的Repeatable Read下它通常不会直接看到V2而是看到对自己可见的旧版本V1。因为V2还没提交。等T1提交之后后续新事务再来读取就可以看到4000这个新版本。这就是MVCC最直接的收益转账事务还在执行查余额的请求不必全部堵住查询拿的是一个一致的历史快照而不是半完成现场。快照为什么能成立旧版本要能找得回来只说“有多个版本”还不够数据库还得真的能把旧版本找出来。这就是undo log的第一个作用。当事务修改一行数据时InnoDB不只是写新值还会把回退所需的信息记下来。对于账户A来说如果余额从5000改成4000数据库得能在需要的时候知道这行原来是5000。可以把这个过程简化成下面的样子时刻当前可写版本undo log中保存的旧值说明转账前5000-还没发生修改扣款后40005000已经具备回滚能力提交后40005000 的历史信息仍可能被旧快照访问服务MVCC所以undo log不只是为了“出错时回滚”它还支撑了历史版本读取。快照读之所以能看到旧值不是因为数据库凭空记得住过去而是因为这些过去被保存在版本链上。Read Committed和Repeatable Read真正差在哪里很多人知道InnoDB默认是Repeatable Read但不一定知道它和Read Committed在转账场景里到底差在哪。差别先记成一句话Read Committed每次一致性读都拿一个新的已提交快照Repeatable Read同一个事务里的普通一致性读通常沿用第一次读建立的快照还是看同一个查询事务T2时间T1T2在Read Committed下看到什么T2在Repeatable Read下看到什么10:00T1开始转账扣掉 A 的 1000未提交第一次查余额看到 5000第一次查余额看到 500010:01T1提交第二次查余额看到 4000第二次普通SELECT仍看到 5000这就是为什么Read Committed可以避免脏读但不能保证同一事务中两次普通读取结果一致Repeatable Read可以让同一事务里的普通SELECT保持稳定这里有一个容易混淆的点。Repeatable Read下稳定的是普通一致性读不是所有操作都永远固定在旧世界里。像UPDATE、DELETE、SELECT ... FOR UPDATE这类当前读面对的是最新的可操作记录并会参与加锁。也正因为如此在同一个事务里把普通快照读和锁定读混在一起看经常会觉得“怎么像是看到了两套世界”因为它们本来就是两种不同读取语义。锁不是被MVCC取代了而是被放到了更该出现的地方MVCC解决的是普通读和写之间的很多冲突但真正修改数据时锁仍然不可少。在转账里下面这种操作显然不能让两个事务随便同时写UPDATEaccountSETbalancebalance-1000WHEREid1;如果两个事务同时修改账户A的余额却没有合适的锁保护就可能出现覆盖更新、余额判断失效等问题。InnoDB里和这条主线最相关的几种锁可以先这样理解锁类型锁住什么在转账主线里的作用行锁某条记录防止两个事务同时改同一账户余额间隙锁记录之间的区间防止别人在范围里插入新记录临键锁行锁间隙锁处理范围条件下的并发插入问题如果按主键精确更新一个账户比如WHERE id 1通常关注的重点是行锁这条账户记录在当前事务完成前不希望被另一个事务随意改动。如果是范围条件比如查询并锁定“所有待处理的大额转账记录”数据库就不只是要锁住现有行还可能要把相关区间一起保护起来否则别的事务可能趁空隙插入一条新记录前后两次范围结果就变了。这也是间隙锁、临键锁存在的原因。回滚为什么真的能把现场撤回去再回到最开始那笔出错的转账。假设事务已经执行了扣款UPDATEaccountSETbalancebalance-1000WHEREid1;结果在给B加钱之前应用发现异常决定执行ROLLBACK;数据库之所以能撤回不是因为它“记得刚才做过什么”而是因为修改时已经把反向恢复所需的信息写到了undo log。过程可以理解成这样时刻A 余额undo log中的信息结果转账前5000-原始状态扣款后4000A 原余额是 5000可以回滚执行回滚后5000已完成恢复状态撤回这也是事务原子性的一个底层支撑不是说数据库抽象上承诺“要么都成功”而是说它真的准备好了恢复路径。提交成功后为什么宕机也不怕事务还有另一个问题回滚讲的是“没做完怎么办”持久化讲的是“已经说做完了机器突然挂了怎么办”。设想这样一个场景A扣了1000B加了1000应用收到“提交成功”机器立刻断电如果数据库只是把修改先放在内存里还没来得及可靠地落到磁盘那么重启之后这笔已经告诉业务“成功”的转账就可能消失。这就是redo log的作用。redo log可以先理解成“重做这次已确认修改所需的信息”。一旦系统在数据页完全落盘之前崩溃重启恢复时就可以依据redo log把那些已经确认过的修改重新补上。把它放进转账里看场景需要undo log吗需要redo log吗目标转账做到一半决定撤销是否撤回未完成修改转账已经提交随后宕机否是恢复已确认修改所以undo和redo分别解决的是两类不同失败undo面对的是事务内部失败或主动撤销redo面对的是提交之后的崩溃恢复~这两个问题都跟“失败”有关但不是同一种失败。为什么事务日志要分成undo和redo把两者放在一张表里看区别会更清楚先把它们分别在解决什么问题讲清楚会更不容易混日志主要用途面向什么失败在转账中的作用undo log回滚、提供旧版本事务没做完或者需要撤销把 A 从 4000 恢复到 5000redo log崩溃恢复、保证持久化已提交但数据页还没完全落盘时宕机保证 A 减 1000、B 加 1000 不丢可以把它们分别看成两个问题的答案“如果现在不想要这次修改了怎么退回去”靠undo“如果已经确认这次修改有效了机器挂掉后怎么补回来”靠redo没有undo事务很难真正做到原子回滚没有redo提交成功就不够可靠。把同一笔转账从头到尾再串一遍到这里再把那笔A - B转1000的过程完整走一遍事务的几个关键机制就能串起来了。第一步开启事务数据库知道接下来这些操作属于同一个业务单元而不是三条彼此独立的SQL。第二步修改账户余额和流水UPDATE账户时InnoDB会针对当前读和写入需要处理锁修改前后的版本关系会进入MVCC体系回退所需的信息会写入undo log。第三步并发查询同时发生其他事务如果执行普通SELECT通常走一致性读不一定被这次转账完全阻塞。它们看到的是对自己可见的快照版本而不是随手读到半成品。第四步决定提交还是回滚如果业务失败用undo log把已经做过的修改撤回如果业务成功进入提交流程第五步提交后的持久化保障一旦提交确认redo log负责保证这次修改在崩溃后仍然可以恢复出来。提交成功就不再只是“当前内存里看起来成功”而是“系统出故障后也能重建结果”。压成一张表就是阶段关键机制它解决的问题执行中锁、当前读防止并发写把同一账户改乱读取中一致性读、MVCC让普通查询尽量不和写全面冲突失败时undo log把半完成事务撤回提交后redo log保证宕机后结果不丢整体上事务把多步业务变成一个可靠整体最后只留下三件事第一事务不是给数据库语法加层包装而是把多步业务动作捆成一个不可随意拆开的整体。银行转账这种场景如果没有事务最容易留下半完成状态。第二MySQL事务的难点不在BEGIN和COMMIT这两个关键字而在并发和恢复。隔离级别、锁、MVCC、undo log、redo log其实都在解决这两件事。第三MVCC和锁不是对立关系。普通查询尽量靠多版本快照减少冲突真正要修改当前数据时仍然需要锁事务失败靠undo撤回提交后宕机靠redo恢复。把这些机制一起看MySQL事务原理才算真正闭环。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2572823.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…