一文详解 MVCC/隔离级别/引入Seata的影响

发布时间:2026/10/4 23:27:07

一文详解 MVCC/隔离级别/引入Seata的影响 MVCC多版本并发控制是 MySQL InnoDB 实现并发读的底层机制事务隔离级别是数据库定义的事务之间可见性的规则标准。InnoDB 使用 MVCC 锁 共同实现 4 种隔离级别。一、MVCC 核心原理InnoDBMVCC 的核心思想每行数据保存多个版本读的时候读快照版本写的时候新建版本不加锁实现读写不阻塞。快照本质就是某一个时间点数据库的数据逻辑镜像。它并不是把整份数据复制一份存起来而是依靠Read View undo log 版本链拼接出来的虚拟视图。1. 隐藏列每条记录自带DB_TRX_ID最近修改这条记录的事务 ID新增 / 更新时写入DB_ROLL_PTR回滚指针指向undo log里的旧版本数据形成版本链DB_ROW_ID隐藏主键没有主键时使用undo log作用保存数据修改前的旧版本用于回滚 构建快照读。undo log回滚日志逻辑日志记录的是数据变更的逻辑内容不是磁盘上的物理字节。undo log 记录的是反向操作比如执行update t set nameb where id1;undo log 里存的是id1name原来的值是a当事务回滚时直接把旧值写回去。它不关心数据页在磁盘上长什么样只关心行数据的逻辑变更所以叫逻辑日志。2. Read View读视图MVCC 核心Read View快照的「可见性规则」记录生成快照瞬间所有活跃事务 ID用来判断版本能不能看见。Read View 四个核心字段m_ids当前系统里所有活跃未提交事务 ID 集合开启了但还没 commit 的事务min_trx_idm_ids里面最小的事务 IDmax_trx_id下一个将要分配的事务 ID不是 m_ids 最大值是全局事务 ID 计数器creator_trx_id创建这个 Read View 的当前事务 ID可见性判断规则判断某行记录的trx_id记录行上的trx_id创建这行版本的事务 ID如果trx_id min_trx_id可见事务早已提交如果trx_id max_trx_id不可见这个事务是创建 Read View 之后才开启的如果min_trx_id trx_id max_trx_id若trx_id在m_ids集合中不可见事务还没提交不在m_ids可见事务已经提交如果当前版本不可见就顺着 undo log 往前找历史版本重复判断直到找到可见版本或者没有历史版本返回空。快照读Snapshot Read不加锁的普通 SELECT 查询就是快照读。读取的是快照版本的数据依靠 MVCCRead View undo log 版本链实现不加行锁。核心特点不加锁读写不阻塞。写操作修改最新行读操作去读 undo log 里的历史版本互不阻塞。读到的不是当前最新数据而是快照可见版本。底层依赖Read View undo log 版本链。什么时候生成 Read ViewRR可重复读InnoDB 默认隔离级别事务中第一次快照读的时候生成 1 个 Read View整个事务复用这一个。✅ 所以 RR 可以解决幻读MVCC 层面因为整个事务快照不变。RC读已提交每一次快照读都会重新生成一个新的 Read View。✅ 所以 RC 可以读到别的事务已提交的数据无法防止幻读。⚠️ 重点区分 RC每次 select 都新建 ReadView RR事务第一次 select 才创建 ReadView后续 select 复用同一个总结Read View 就是快照读的一个时间快照记录生成快照那一刻所有活跃事务用来过滤 undo log 版本链决定哪一行旧数据能被当前事务读到。事务执行快照读普通 select时生成一个 Read View用来判断这条记录的版本对当前事务是否可见。 Read View 4 个字段m_ids当前活跃未提交事务 ID 集合min_trx_idm_ids 里最小事务 IDmax_trx_id下一个将要分配的事务 IDcreator_trx_id当前事务自己的 ID可见性判断规则重点拿到记录的DB_TRX_ID和 Read View 对比DB_TRX_ID min_trx_id事务已经提交 →可见DB_TRX_ID max_trx_id事务在 Read View 之后开启 →不可见min_trx_id DB_TRX_ID max_trx_id在活跃区间如果DB_TRX_id在m_ids里事务还没提交 →不可见不在 m_ids已经提交 →可见如果当前版本不可见顺着roll_ptr去 undo log 找版本链上更早版本继续判断。数据最新版本物理行磁盘上当前这条记录DB_TRX_ID 是最后修改它的事务这是当前版本update/delete/select ... for update当前读就读这个。快照版本事务在某个时间点生成 Read View 之后根据这个视图规则沿着 undo log 版本链找到对当前事务可见的那一条历史版本就是快照版本。⚠️ 重点快照版本不会复制一份新数据到磁盘只是复用 undo log 里旧记录靠版本链跳转读取所以开销很小。3. 快照读 vs 当前读快照读普通 SELECT走 MVCC读取快照版本不加行锁读写不阻塞。select * from table;当前读加锁读读取最新已提交版本会加行锁不走 MVCC 快照。select ... for update / lock in share mode; update / delete / insert❗ MVCC 解决的是快照读的并发问题当前读靠锁。二、SQL 标准 4 种隔离级别 InnoDB 实现方式隔离级别脏读不可重复读幻读InnoDB 实现方案读未提交 Read Uncommitted✅存在✅存在✅存在不使用 MVCC直接读最新数据读已提交 Read CommittedRC❌解决✅存在✅存在每次快照读都生成新 Read View可重复读 Repeatable ReadRRMySQL 默认❌解决❌解决⚠️ 快照读解决当前读仍存在靠间隙锁部分抑制事务第一次快照读时生成 Read View整个事务复用这一个 Read View串行化 Serializable❌解决❌解决❌解决关闭 MVCC所有 select 自动转为当前读加共享锁完全串行逐个拆解1. 读未提交 RU事务可以读到其他事务未提交的数据。 InnoDB 这个级别不使用 MVCC直接读最新行数据。几乎不会使用。2. 读已提交 RC只能读到已经提交的数据杜绝脏读每次 select 快照读都会生成全新 Read View现象同一个事务先后两次 select中间别的事务提交修改两次读到不一样数据 →不可重复读RC 没有间隙锁所以幻读问题也存在。3. 可重复读 RRMySQL InnoDB 默认隔离级别核心事务第一次快照读的时候创建 Read View后续快照读全程复用这一个视图所以同一个事务多次快照读看到的数据和第一次一致解决不可重复读。幻读说明面试高频 幻读一个事务内同样的查询第二次查出多 / 少了新插入的数据。快照读MVCC 快照里不包含新插入记录快照读看不到幻读当前读for update/updateMVCC 快照无效会读到新插入行仍然存在幻读InnoDB RR 通过间隙锁 临键锁阻止其他事务插入抑制幻读不是彻底消除RC vs RR MVCC 最大区别一句话 RC每次 select 新建 ReadViewRR事务第一次 select 才创建 ReadView复用。4. 串行化 Serializable最高隔离级别MVCC 失效。 普通select自动等价于select ... lock in share mode全部变成加锁当前读读写互相阻塞完全串行执行性能最差。脏读、不可重复读、幻读全部解决。三、经典现象举例RR事务 ARRbegin; select * from user where id1; -- 第一次快照读生成ReadViewversion100 -- 此时事务B开启修改id1并commit select * from user where id1; -- 复用旧ReadView仍然读到version100不会读到B提交的数据 → 可重复读同样场景RC 下第二次 select 会新建 ReadView读到事务 B 提交后的新数据出现不可重复读。四、引入 Seata AT 之后数据库隔离级别推荐是什么✅生产推荐MySQL 使用 RC读已提交口述版 Seata AT 模式官方推荐数据库隔离级别为 RC。 原因Seata AT 的核心原理一阶段执行业务 SQL生成 undo_log 快照提交本地事务二阶段提交直接删 undo_log回滚时通过 undo_log 恢复数据。如果用 RR可重复读MySQL RR 是基于 MVCC 快照。事务开启时就拿到快照。一阶段本地事务提交了但其他事务读到的还是旧快照此时如果发生二阶段回滚通过 undo_log 恢复数据别的事务的 MVCC 快照看不到回滚后的数据会读到脏数据出现分布式脏读。RC 下每次查询都会获取最新快照一旦二阶段回滚完成其他事务马上读到最新数据规避上面这个分布式脏读问题。补充RR 不是不能用只是坑多不推荐生产Seata 本身不会改变 MySQL 底层隔离级别隔离级别是数据库层面配置Seata 只是协调分布式事务。一句话记忆Seata AT 推荐 RC避免 RR 的 MVCC 快照长时间持有造成分布式脏读注意区分MySQL 隔离级别控制库内事务可见性Seata 全局事务隔离级别是 Seata 自己的概念有读未提交、读已提交和数据库隔离级别不是一回事。 Seata 全局隔离级别默认是读未提交可以配置为读已提交但性能会下降。五、引入 Seata 之后MVCC 会带来什么问题重点讲AT 模式下 MySQL MVCC 带来的 2 个核心问题面试高频问题 1RR 隔离级别下的分布式脏读最重要场景全局事务 G1AT 模式G1 一阶段本地事务执行 update写入 undo_log本地事务提交此时数据库数据已经改了undo_log 保留此时全局事务还没二阶段提交属于全局未提交状态另一个全局事务 G2MySQL 隔离级别 RRG2 事务开启时拿到 MVCC 旧快照G2 查询这行拿到的是快照里旧数据G1 修改前的数据这时 G1 发生回滚二阶段通过 undo_log 把数据恢复回去G2 继续查询仍然是它事务一开始的快照读到的还是旧数据。现象全局事务已经回滚但 G2 读到了被回滚之前的数据分布式脏读。根源RR 的 MVCC 快照是事务启动时固定不受全局事务二阶段回滚影响。解决数据库改成 RCRC 每次查询重新拿最新快照回滚完成后查询就能看到最新值。问题 2undo_log 快照 和 MySQL MVCC 快照不一致问题Seata 的 undo_log 是业务 SQL 执行前手动记录的数据快照是 Seata 自己的快照MySQL MVCC 是数据库自动维护的 undo 版本链两套快照体系。RC 模式每次读最新版本业务查询走 MySQL 最新数据和 Seata undo_log 的时序更容易对齐RR 模式MySQL 读的是 MVCC 快照有可能读到和 Seata undo_log 不一致的数据版本增加数据一致性风险。延伸坑RC 下也有小问题RC 虽然解决上面脏读但 RC 本身会出现不可重复读。在 Seata 长全局事务场景同一个分支事务内多次查询可以读到其他已提交事务的数据业务代码需要考虑这个特性。三、扩展Q1Seata 全局隔离级别和 MySQL 隔离级别区别MySQL 隔离级别单机数据库事务可见性依赖 MVCC、锁。Seata 全局隔离级别分布式事务之间的数据可见性Seata AT 默认全局读未提交。如果设置 Seata 全局读已提交Seata 会加锁其他全局事务不能读到当前全局事务一阶段修改但未二阶段提交的数据代价是性能下降并发降低。Q2Seata TCC 模式也要 RC 吗TCC 没有 undo_log不需要依赖 MySQL undo 快照TCC 对数据库隔离级别没有强制要求RC/RR 都可以。上面的 MVCC 脏读问题主要是 AT 模式特有。Q3为什么 AT 一阶段本地事务要提交AT 一阶段业务 SQL 写入 undo_log 在同一个本地事务然后提交本地事务。 本地提交释放数据库锁提升并发但数据已经落库只是 undo_log 保留等待二阶段决定提交 / 回滚。这也是为什么会出现中间状态需要处理可见性问题。Q4Seata AT 默认全局读未提交但本地是 RC数据库本地隔离级别MySQLRCRead CommittedSeata AT【全局事务隔离级别】默认Read Uncommitted全局读未提交这两个不是一个维度。原理AT 模式一阶段分支本地事务直接提交到数据库本地 RC别的普通 SQL 能读到这个分支修改的数据但是全局事务还没提交TC 上的全局锁还没释放随时可能二阶段回滚undo_log 会把数据改回去 现象 别的普通select可以读到分支已经本地提交但全局还没提交的数据。 这就是 Seata 文档说的全局脏读也就是全局层面的读未提交。数据库 RC 只保证看不到同一个数据库本地事务未提交的数据RC 管不了「本地已经提交但分布式全局事务还没提交后续可能回滚」这个场景。举例子全局事务 G1调用分支 B1 更新余额B1 本地事务提交MySQL RC别的 SQL 能读到 B1 修改但是 G1 还没全局提交随时会触发回滚undo_log 恢复旧数据此时另一个普通 select 读到了 B1 修改的值读到了全局未提交事务的数据全局脏读。exG1 全局事务分支 B1 扣余额B1 本地事务已经提交DB 层面可见但 G1 全局还没提交。此时业务查询读到扣减后的余额如果后续 G1 发生回滚undo_log 把余额恢复回去。结果 查询在一段时间内读到了一个最终会被撤销的数据。Q5 : 如何解决读到全局未提交事务的数据这个问题答案升级成【全局读已提交Global Read Committed】想要全局 RC两种写法SQL 写SELECT ... FOR UPDATESeata 会代理这条 SQL去 TC 校验全局锁如果数据被别的全局事务持有全局锁就阻塞重试直到拿到全局锁代表对方全局事务已经完成才返回数据。保证读到的数据一定是全局已提交的。注解GlobalLock select for update效果一样。⚠️ 只有select for update会被 Seata RM 代理、检查全局锁普通 select 不会拦截会出现全局脏读。
延伸阅读

更多相关文章

2026/10/4 23:27:07

TCP/UDP性能测试工具实战:iperf3吞吐、丢包与协议栈调优避坑指南

简介:TCP_UDP_PerformanceTest 是一款面向网络编程开发者与系统运维人员的传输层协议性能测试工具,用于对比 TCP 与 UDP 在真实网络环境下的吞吐量、延迟与丢包率表现,帮助判断高并发低延迟场景下应选用哪种协议。资源包共 5 个文件&#xff…

2026/10/4 23:27:07

微小型双足鸭形机器人强化学习落地全解析

我一直觉得,双足机器人研究里有一个很奇怪的门槛效应:一提强化学习,大家默认就是人形机器人、四足大狗那种级别,要么是仿真里空跑,要么是一套几万块的硬件平台。真正想把深度强化学习这套东西在一只巴掌大、几百克重、…

2026/10/5 0:37:10

HarmonyOS 7 QuickDock 闪控窗开发实录 04:Background Tasks Kit × Stage:前后台任务切换与窗口异常恢复【鸿蒙心迹】

03 把 QuickDock 的拖动、侧边暂存和位置持久化做稳定以后,窗口这条线终于没有太多悬念。 第四篇开始处理真正复杂的业务状态: 应用进入后台 两个任务并存 不同任务后台策略不同 当前闪控窗切换展示任务 Float Surface 中途丢失 回前台后恢复这一篇我故意…

2026/10/5 0:32:10

RAG知识库建设:从语义分块到MCP调度的工程实践

1. 这不是“建个数据库”,而是给AI装上真正能用的脑子你有没有试过把几十个PDF、上百篇微信文章、几十条会议录音、还有自己随手记的几十个Obsidian笔记,一股脑塞进某个标着“知识库”的框里,然后满怀期待地问AI:“上个月客户提的…

2026/10/4 0:01:02

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/4 1:01:05

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑