【计算基础|MySQL11】事务与隔离级别

【计算基础|MySQL11】事务与隔离级别

转账这个例子谁都听过:A 给 B 转 100 块,要扣 A 的钱、加 B 的钱,两步要么都成、要么都不成------不能扣了 A 的钱、B 却没收到。这就是事务。但真正难的是:两个事务同时改同一条数据时会发生什么?脏读、不可重复读、幻读到底怎么区分?为什么 MySQL 默认隔离级别下,别的事务都提交了,你在事务里还能读到旧值?

这一篇把这些问题在 MySQL 8.0 上用两个真实会话跑给你看。机制图用 Mermaid,结尾配面试高频。

环境:Docker MySQL 8.0.46,InnoDB,默认隔离级别 REPEATABLE-READ,自动提交 autocommit=1。


一、什么是事务

事务就是一组 SQL 语句的执行单元,要么全成功、要么全失败回滚。

sql 复制代码
START TRANSACTION;          -- 或 BEGIN,开启事务
UPDATE account SET balance=balance-100 WHERE id=1;  -- A扣100
UPDATE account SET balance=balance+100 WHERE id=2;  -- B加100
COMMIT;                     -- 全成功才提交
-- 中间任何一步出错,执行 ROLLBACK; 撤销整个事务

⚠️ 两个新手必踩的点:

  1. MySQL 默认 autocommit=ON,意味着你每条 SQL 自动是一个事务,写完就提交。不显式开事务,多条语句是不原子的。
  2. CREATE/ALTER/DROP 这类 DDL 语句会隐式提交 当前事务,且 DDL 本身不能回滚。别指望 BEGIN; DROP TABLE t; ROLLBACK; 能救回来。
sql 复制代码
SELECT @@autocommit;   -- 1 = 自动提交开
SELECT @@transaction_isolation;  -- REPEATABLE-READ(默认)

二、ACID:四个特性各自靠什么实现

事务的四个特性是面试必问,但更值得问的是每个特性底层靠什么实现。结论先列:
#mermaid-svg-nK0qybgMqb0Tudy8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nK0qybgMqb0Tudy8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nK0qybgMqb0Tudy8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nK0qybgMqb0Tudy8 .error-icon{fill:#552222;}#mermaid-svg-nK0qybgMqb0Tudy8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nK0qybgMqb0Tudy8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nK0qybgMqb0Tudy8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nK0qybgMqb0Tudy8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nK0qybgMqb0Tudy8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nK0qybgMqb0Tudy8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nK0qybgMqb0Tudy8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nK0qybgMqb0Tudy8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nK0qybgMqb0Tudy8 .marker.cross{stroke:#333333;}#mermaid-svg-nK0qybgMqb0Tudy8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nK0qybgMqb0Tudy8 p{margin:0;}#mermaid-svg-nK0qybgMqb0Tudy8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-nK0qybgMqb0Tudy8 .cluster-label text{fill:#333;}#mermaid-svg-nK0qybgMqb0Tudy8 .cluster-label span{color:#333;}#mermaid-svg-nK0qybgMqb0Tudy8 .cluster-label span p{background-color:transparent;}#mermaid-svg-nK0qybgMqb0Tudy8 .label text,#mermaid-svg-nK0qybgMqb0Tudy8 span{fill:#333;color:#333;}#mermaid-svg-nK0qybgMqb0Tudy8 .node rect,#mermaid-svg-nK0qybgMqb0Tudy8 .node circle,#mermaid-svg-nK0qybgMqb0Tudy8 .node ellipse,#mermaid-svg-nK0qybgMqb0Tudy8 .node polygon,#mermaid-svg-nK0qybgMqb0Tudy8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nK0qybgMqb0Tudy8 .rough-node .label text,#mermaid-svg-nK0qybgMqb0Tudy8 .node .label text,#mermaid-svg-nK0qybgMqb0Tudy8 .image-shape .label,#mermaid-svg-nK0qybgMqb0Tudy8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-nK0qybgMqb0Tudy8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nK0qybgMqb0Tudy8 .rough-node .label,#mermaid-svg-nK0qybgMqb0Tudy8 .node .label,#mermaid-svg-nK0qybgMqb0Tudy8 .image-shape .label,#mermaid-svg-nK0qybgMqb0Tudy8 .icon-shape .label{text-align:center;}#mermaid-svg-nK0qybgMqb0Tudy8 .node.clickable{cursor:pointer;}#mermaid-svg-nK0qybgMqb0Tudy8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nK0qybgMqb0Tudy8 .arrowheadPath{fill:#333333;}#mermaid-svg-nK0qybgMqb0Tudy8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nK0qybgMqb0Tudy8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nK0qybgMqb0Tudy8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nK0qybgMqb0Tudy8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nK0qybgMqb0Tudy8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nK0qybgMqb0Tudy8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nK0qybgMqb0Tudy8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nK0qybgMqb0Tudy8 .cluster text{fill:#333;}#mermaid-svg-nK0qybgMqb0Tudy8 .cluster span{color:#333;}#mermaid-svg-nK0qybgMqb0Tudy8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nK0qybgMqb0Tudy8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nK0qybgMqb0Tudy8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-nK0qybgMqb0Tudy8 .icon-shape,#mermaid-svg-nK0qybgMqb0Tudy8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nK0qybgMqb0Tudy8 .icon-shape p,#mermaid-svg-nK0qybgMqb0Tudy8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nK0qybgMqb0Tudy8 .icon-shape .label rect,#mermaid-svg-nK0qybgMqb0Tudy8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nK0qybgMqb0Tudy8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nK0qybgMqb0Tudy8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nK0qybgMqb0Tudy8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 原子性 Atomicity
undo log

回滚日志
一致性 Consistency
由 A + I + D 共同保证
隔离性 Isolation
锁 + MVCC
持久性 Durability
redo log

重做日志

特性 含义 实现机制
原子性 Atomicity 事务里的 SQL 要么全做要么全不做 undo log:记录修改前的旧值,回滚时用它复原
一致性 Consistency 数据从一个一致状态转到另一个一致状态 目的,由原子性/隔离性/持久性共同保障
隔离性 Isolation 并发事务互不干扰 锁 + MVCC(下一篇详讲 MVCC)
持久性 Durability 提交后即使宕机也不丢 redo log:提交前先把修改记到重做日志,崩溃后重放

🔴 一句话记:原子性靠 undo log(后悔药),持久性靠 redo log(存档点),隔离性靠锁+MVCC,一致性是最终目标。undo/redo 这两对日志后续日志篇会专门展开。


三、并发事务会出什么问题

两个事务同时读写,按严重程度有三种现象。用账户表 account(id, balance) 举例,初始 id=1 余额 100。

3.1 脏读(Dirty Read)

事务 A 读到了事务 B还没提交的修改,结果 B 回滚了,A 读到的就是"脏数据"。
事务B 事务A 事务B 事务A #mermaid-svg-jbJc0juNL8PEspLW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jbJc0juNL8PEspLW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jbJc0juNL8PEspLW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jbJc0juNL8PEspLW .error-icon{fill:#552222;}#mermaid-svg-jbJc0juNL8PEspLW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jbJc0juNL8PEspLW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jbJc0juNL8PEspLW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jbJc0juNL8PEspLW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jbJc0juNL8PEspLW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jbJc0juNL8PEspLW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jbJc0juNL8PEspLW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jbJc0juNL8PEspLW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jbJc0juNL8PEspLW .marker.cross{stroke:#333333;}#mermaid-svg-jbJc0juNL8PEspLW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jbJc0juNL8PEspLW p{margin:0;}#mermaid-svg-jbJc0juNL8PEspLW .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jbJc0juNL8PEspLW text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-jbJc0juNL8PEspLW .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-jbJc0juNL8PEspLW .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-jbJc0juNL8PEspLW .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-jbJc0juNL8PEspLW .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-jbJc0juNL8PEspLW #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-jbJc0juNL8PEspLW .sequenceNumber{fill:white;}#mermaid-svg-jbJc0juNL8PEspLW #sequencenumber{fill:#333;}#mermaid-svg-jbJc0juNL8PEspLW #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-jbJc0juNL8PEspLW .messageText{fill:#333;stroke:none;}#mermaid-svg-jbJc0juNL8PEspLW .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jbJc0juNL8PEspLW .labelText,#mermaid-svg-jbJc0juNL8PEspLW .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-jbJc0juNL8PEspLW .loopText,#mermaid-svg-jbJc0juNL8PEspLW .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-jbJc0juNL8PEspLW .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-jbJc0juNL8PEspLW .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-jbJc0juNL8PEspLW .noteText,#mermaid-svg-jbJc0juNL8PEspLW .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-jbJc0juNL8PEspLW .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jbJc0juNL8PEspLW .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jbJc0juNL8PEspLW .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jbJc0juNL8PEspLW .actorPopupMenu{position:absolute;}#mermaid-svg-jbJc0juNL8PEspLW .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-jbJc0juNL8PEspLW .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jbJc0juNL8PEspLW .actor-man circle,#mermaid-svg-jbJc0juNL8PEspLW line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-jbJc0juNL8PEspLW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 真实值其实是100,A刚才读的200是脏数据 UPDATE account SET balance=200 (未提交)SELECT balance 读到 200 (脏)ROLLBACK

3.2 不可重复读(Non-Repeatable Read)

事务 A 内两次读同一行,中间事务 B 修改并提交了,A 两次读到的值不一样。重点是同一行被 UPDATE 了。
事务B 事务A 事务B 事务A #mermaid-svg-IiPwpDTMgCBAcbzx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IiPwpDTMgCBAcbzx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IiPwpDTMgCBAcbzx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IiPwpDTMgCBAcbzx .error-icon{fill:#552222;}#mermaid-svg-IiPwpDTMgCBAcbzx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IiPwpDTMgCBAcbzx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IiPwpDTMgCBAcbzx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IiPwpDTMgCBAcbzx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IiPwpDTMgCBAcbzx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IiPwpDTMgCBAcbzx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IiPwpDTMgCBAcbzx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IiPwpDTMgCBAcbzx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IiPwpDTMgCBAcbzx .marker.cross{stroke:#333333;}#mermaid-svg-IiPwpDTMgCBAcbzx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IiPwpDTMgCBAcbzx p{margin:0;}#mermaid-svg-IiPwpDTMgCBAcbzx .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IiPwpDTMgCBAcbzx text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-IiPwpDTMgCBAcbzx .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-IiPwpDTMgCBAcbzx .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-IiPwpDTMgCBAcbzx .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-IiPwpDTMgCBAcbzx .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-IiPwpDTMgCBAcbzx #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-IiPwpDTMgCBAcbzx .sequenceNumber{fill:white;}#mermaid-svg-IiPwpDTMgCBAcbzx #sequencenumber{fill:#333;}#mermaid-svg-IiPwpDTMgCBAcbzx #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-IiPwpDTMgCBAcbzx .messageText{fill:#333;stroke:none;}#mermaid-svg-IiPwpDTMgCBAcbzx .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IiPwpDTMgCBAcbzx .labelText,#mermaid-svg-IiPwpDTMgCBAcbzx .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-IiPwpDTMgCBAcbzx .loopText,#mermaid-svg-IiPwpDTMgCBAcbzx .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-IiPwpDTMgCBAcbzx .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-IiPwpDTMgCBAcbzx .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-IiPwpDTMgCBAcbzx .noteText,#mermaid-svg-IiPwpDTMgCBAcbzx .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-IiPwpDTMgCBAcbzx .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IiPwpDTMgCBAcbzx .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IiPwpDTMgCBAcbzx .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IiPwpDTMgCBAcbzx .actorPopupMenu{position:absolute;}#mermaid-svg-IiPwpDTMgCBAcbzx .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-IiPwpDTMgCBAcbzx .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IiPwpDTMgCBAcbzx .actor-man circle,#mermaid-svg-IiPwpDTMgCBAcbzx line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-IiPwpDTMgCBAcbzx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 第一次读 balance = 100UPDATE balance=200 并 COMMIT第二次读 balance = 200 (两次不一样)

3.3 幻读(Phantom Read)

事务 A 内两次范围查询 ,中间事务 B 插入/删除了符合条件的行,A 第二次查到的行数变了。重点是行数"幻影般"多了或少了。
事务B 事务A 事务B 事务A #mermaid-svg-owL5ftk4Ys9MmdfP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-owL5ftk4Ys9MmdfP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-owL5ftk4Ys9MmdfP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-owL5ftk4Ys9MmdfP .error-icon{fill:#552222;}#mermaid-svg-owL5ftk4Ys9MmdfP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-owL5ftk4Ys9MmdfP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-owL5ftk4Ys9MmdfP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-owL5ftk4Ys9MmdfP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-owL5ftk4Ys9MmdfP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-owL5ftk4Ys9MmdfP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-owL5ftk4Ys9MmdfP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-owL5ftk4Ys9MmdfP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-owL5ftk4Ys9MmdfP .marker.cross{stroke:#333333;}#mermaid-svg-owL5ftk4Ys9MmdfP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-owL5ftk4Ys9MmdfP p{margin:0;}#mermaid-svg-owL5ftk4Ys9MmdfP .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-owL5ftk4Ys9MmdfP text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-owL5ftk4Ys9MmdfP .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-owL5ftk4Ys9MmdfP .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-owL5ftk4Ys9MmdfP .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-owL5ftk4Ys9MmdfP .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-owL5ftk4Ys9MmdfP #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-owL5ftk4Ys9MmdfP .sequenceNumber{fill:white;}#mermaid-svg-owL5ftk4Ys9MmdfP #sequencenumber{fill:#333;}#mermaid-svg-owL5ftk4Ys9MmdfP #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-owL5ftk4Ys9MmdfP .messageText{fill:#333;stroke:none;}#mermaid-svg-owL5ftk4Ys9MmdfP .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-owL5ftk4Ys9MmdfP .labelText,#mermaid-svg-owL5ftk4Ys9MmdfP .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-owL5ftk4Ys9MmdfP .loopText,#mermaid-svg-owL5ftk4Ys9MmdfP .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-owL5ftk4Ys9MmdfP .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-owL5ftk4Ys9MmdfP .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-owL5ftk4Ys9MmdfP .noteText,#mermaid-svg-owL5ftk4Ys9MmdfP .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-owL5ftk4Ys9MmdfP .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-owL5ftk4Ys9MmdfP .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-owL5ftk4Ys9MmdfP .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-owL5ftk4Ys9MmdfP .actorPopupMenu{position:absolute;}#mermaid-svg-owL5ftk4Ys9MmdfP .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-owL5ftk4Ys9MmdfP .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-owL5ftk4Ys9MmdfP .actor-man circle,#mermaid-svg-owL5ftk4Ys9MmdfP line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-owL5ftk4Ys9MmdfP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} SELECT * WHERE id>10 第一次: 5行INSERT id=15 并 COMMITSELECT * WHERE id>10 第二次: 6行 (多了一行幻影)

🔴 三者区别一句话 :脏读 = 读到别人没提交 的;不可重复读 = 同一行被别人改过提交了 ;幻读 = 范围里多/少了行。幻读下一篇用 MVCC 专门演示。


四、四种隔离级别

SQL 标准定义了四种隔离级别,从上到下隔离越来越强、性能越来越低、能解决的问题越来越多:

隔离级别 脏读 不可重复读 幻读 说明
读未提交 Read Uncommitted 可能 可能 可能 基本没人用
读已提交 Read Committed (RC) ✅解决 可能 可能 Oracle/PostgreSQL 默认
可重复读 Repeatable Read (RR) ✅解决 ✅解决 ✅基本解决 MySQL/InnoDB 默认
串行化 Serializable ✅解决 ✅解决 ✅解决 全加锁,性能最差
sql 复制代码
-- 查看
SELECT @@global.transaction_isolation, @@session.transaction_isolation;
-- REPEATABLE-READ  REPEATABLE-READ

-- 设置会话级
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 设置全局级(对新连接生效)
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 配置文件写死:my.cnf 里 transaction-isolation = READ-COMMITTED

⚠️ MySQL 8 变量名是 transaction_isolation,老版本(5.7 及以前)叫 tx_isolation,别照着老文章抄错。

4.1 实测:RR 下的可重复读

我开两个会话。会话 A 在 RR 下开事务读余额,会话 B 趁机把 id=1 余额改成 200 并提交。会话 A 的输出:

text 复制代码
A-第一次读(事务开始后)                100
B-更新后提交前自己看                  200
A-第二次读(B已提交,RR应仍为100)       100   ← 还是100!
A-当前读FOR UPDATE(应看到最新200)     200   ← 当前读才看到200

🔴 这就是 RR 的可重复读 :会话 A 第二次普通读,明明 B 已经提交了改动,A 还是读到 100。它靠的是 MVCC 的一致性视图(下一篇细讲)。而 SELECT ... FOR UPDATE 这种"当前读"会读最新值,所以看到 200。

4.2 实测:RC 下的不可重复读

同样的实验,把会话 A 设成 RC:

text 复制代码
A(RC)-第一次读                        100
A(RC)-第二次读(B已提交,RC应看到200)   200   ← 变了,不可重复读

✅ 对照清楚了:RC 下每次 SELECT 都生成新的一致性视图,所以能读到别人刚提交的修改(不可重复读);RR 下视图复用,所以读一致(可重复读)。


五、隔离级别到底怎么实现的

为什么 RR 和 RC 行为不同?核心是 ReadView(一致性视图)的创建时机不同:

隔离级别 普通 SELECT 怎么读 ReadView 创建时机
RU 读未提交 不加锁,直接读最新 不生成 ReadView
RC 读已提交 每次 SELECT 都生成新 ReadView 每条 SELECT 都新建
RR 可重复读 复用第一次的 ReadView 事务第一次 SELECT 时创建,后续复用
Serializable 所有读加共享锁、写加排他锁 不依赖 MVCC,全靠锁

🔴 关键冷知识 :BEGIN/START TRANSACTION 不会立刻创建 ReadView ,而是事务里第一条 SELECT 才创建 。这就是为什么你 BEGIN 之后,别的事务改了数据提交了,你接着 SELECT 仍读到旧值------你的 ReadView 在第一次查询那一刻就定格了。

想立刻定格?用 START TRANSACTION WITH CONSISTENT SNAPSHOT;,它会马上建 ReadView。


六、事务的其他关键点

6.1 自动提交与隐式提交

autocommit=ON 时,每条独立 SQL 都是一个自动提交的小事务。执行 LOCK TABLES、START TRANSACTION、DDL 等语句时,会隐式提交当前未提交的事务。

6.2 保存点 SAVEPOINT

事务太大想部分回滚?用保存点:

sql 复制代码
BEGIN;
INSERT INTO t VALUES (1);
SAVEPOINT sp1;
INSERT INTO t VALUES (2);
ROLLBACK TO sp1;   -- 撤销到 sp1,只回滚第二条插入
RELEASE SAVEPOINT sp1;
COMMIT;

6.3 长事务的危害

一个事务开着不提交,会带来一堆问题:

  • 持有锁不释放,阻塞别的事务。
  • undo log 堆积:老版本可能被这个长事务的 ReadView 引用,purge 线程不敢清理,磁盘暴涨。
  • 主从延迟:从库回放慢。

监控长事务:

sql 复制代码
SELECT trx_id, trx_started, trx_query
FROM information_schema.innodb_trx
ORDER BY trx_started;   -- trx_started 越老越是长事务

可以用 SET MAX_EXECUTION_TIME 限制查询时长,超时主动打断。


七、面试怎么考

面试题 一句话答
ACID 及实现 原子性 undo log、持久性 redo log、隔离性 锁+MVCC、一致性是目的
四种隔离级别 RU/RC/RR/Serializable,逐级解决脏读、不可重复读、幻读
MySQL 默认隔离级别 可重复读 RR(InnoDB)
脏读 vs 不可重复读 vs 幻读 读未提交 / 同行被改 / 范围多了行
RR 怎么解决幻读 快照读靠 MVCC 复用 ReadView,当前读靠 Next-Key Lock
RC 和 RR 区别 ReadView 创建时机:RC 每条 SELECT 新建,RR 第一次 SELECT 后复用
长事务危害 锁不释放、undo log 堆积、主从延迟

总结:你真正需要记住的 7 件事

  1. 事务是一组 SQL 的原子单元,BEGIN/COMMIT/ROLLBACK;DDL 会隐式提交不能回滚。
  2. ACID:原子性靠 undo log,持久性靠 redo log,隔离性靠锁+MVCC,一致性是目标。
  3. 三种并发问题:脏读(读未提交)、不可重复读(同行被改)、幻读(范围多行)。
  4. 四种隔离级别逐级加强,MySQL InnoDB 默认 RR,RC 是 Oracle/PG 默认。
  5. RR 和 RC 的本质区别是 ReadView 创建时机------RR 复用,RC 每次新建。
  6. BEGIN 不立刻建 ReadView,第一条 SELECT 才建;想立刻定格用 START TRANSACTION WITH CONSISTENT SNAPSHOT。
  7. 长事务要监控(innodb_trx),它会撑大 undo log、阻塞锁、拖慢主从。

验证清单

  • 用 SELECT @@transaction_isolation 确认默认是 REPEATABLE-READ
  • 亲手复现 RR 下"两次读同一行都是旧值"的实验
  • 切到 RC 复现不可重复读,对比两者 ReadView 时机差异
  • 写出脏读/不可重复读/幻读的区别并各举一个例子
  • 解释 undo log 和 redo log 分别保障 ACID 的哪一项
  • 查一次 information_schema.innodb_trx 认识长事务监控视图
  • 说出为什么 BEGIN 后别的事务提交了你还读到旧值

参考资源


下篇预告:《MVCC 原理与幻读》------上一篇反复提到"ReadView""快照读",这一篇就把 MVCC 拆到底:每行记录背后那三个隐藏列、串联历史版本的 undo log 版本链、ReadView 四个字段的可见性判断规则,以及为什么 RR 下快照读不幻读、当前读却可能幻读。全部用真实事务 ID 走一遍判断流程。

相关推荐
eleven_4u2 小时前
MySQL引擎层是如何工作的
数据库·mysql
张彦峰ZYF2 小时前
借与还之间:连接池设计的技术难点与实现推演
后端·架构·连接池·服务端与协议·无锁借还与尾延迟·失效模式与探活时机·状态污染与泄漏检测
第404号工位2 小时前
“已彻底删除”的照片与文件到底去哪了?我用底层扇区扫描,扒出手机和电脑的幽灵数据
linux·python·架构
love静思冥想2 小时前
CLOB中JSON查询agent字段值(Oracle11g)
sql·oracle·json
appsvip2 小时前
给社区系统加一套 AI 助手:从架构到落地的思考
大数据·人工智能·架构
喵个咪3 小时前
GoWind Admin 拆解:一个 Sender 接口接 39 个通知渠道
后端·架构·go
程序员IT行业3 小时前
2026云进销存技术选型:共享云、独享实例云、私有化部署架构差异与落地核验清单
架构
liuyanqun-parbie3 小时前
从零设计一个通用智能电话平台:FreeSWITCH + ESL + AI 实战架构
人工智能·架构
vivo互联网技术4 小时前
记忆不是存历史,而是治理未来行动|KDC 系列 04
人工智能·架构