MySQL和Oracle关于读未提交的区别
2026/6/12 23:05:19 网站建设 项目流程

这两段语句虽然表现的结果都是一样的,但其实原理是不同的,对吗?:

1 MySQL InnoDB:事务级 ReadView

事务级事务开始时建立 ReadView,整个事务期间复用(RR)或每次新建(RC)

关键事实MySQL 看不到自己未提交的修改

-- MySQL InnoDB 行为 BEGIN; UPDATE account SET balance = 1000 WHERE id = 1; -- 改了 SELECT balance FROM account WHERE id = 1; -- ⚠️ 看到 1000(自己的修改) -- 注意:这是"自己能看到自己",但其他事务看不到 -- 其他事务 SELECT balance FROM account WHERE id = 1; -- ⚠️ 看不到 1000(未提交)

2 Oracle:语句级 ReadView

语句级每个 SQL 语句启动时建立 ReadView

关键事实Oracle 事务内能读取到自己未提交的修改

-- Oracle 行为 BEGIN; UPDATE account SET balance = 1000 WHERE id = 1; -- 改了 SELECT balance FROM account WHERE id = 1; -- ✅ 看到 1000(事务内可见) -- 这是因为 Oracle 读 PGA/SGA 内存中的数据,优先于 Undo -- 其他事务 SELECT balance FROM account WHERE id = 1; -- ⚠️ 看不到 1000(未提交)

一、直接答:是的,原理不同

数据库表面结果底层原理
MySQL InnoDB✅ 看到 1000(自己的修改)⚠️不是"读内存",是"读最新已提交版本"(InnoDB 不存在"事务内未提交可见"机制)
Oracle✅ 看到 1000(自己的修改)是"读 PGA/SGA 内存"(Oracle 事务内未提交数据真的就在内存里

表面相同,原理不同——这是 4 大差异里最核心的一点

二、MySQL InnoDB 真实原理(不是内存优先

2.1 关键事实:MySQL InnoDB 没有"事务内未提交数据可见"机制
-- MySQL InnoDB 实际行为(**注意:MySQL 也能看到自己的未提交修改**) BEGIN; UPDATE account SET balance = 1000 WHERE id = 1; -- 改了 -- InnoDB 实际做了什么? -- 1. 把 balance=1000 写入 **Buffer Pool(内存)** -- 2. 把旧版本 balance=900 写入 **Undo Log** -- 3. 当前行的 DB_TRX_ID 改为 **当前事务 ID**(假设 100) -- 4. DB_ROLL_PTR 指向 Undo Log 里的旧版本 SELECT balance FROM account WHERE id = 1; -- 看到 1000 -- InnoDB 实际做了什么? -- 1. 读当前行 → DB_TRX_ID=100(当前事务 ID) -- 2. **ReadView 4 步判断**: -- - trx_id (100) == creator_trx_id (100) ✅ 自己改的,**可以读** -- 3. 所以读到 balance=1000

⚠️ 关键点

  • MySQL 看到自己未提交的修改,不是因为读内存
  • 是因为ReadView 4 步判断的第 1 步trx_id == creator_trx_id→ **自己改的,返回当前版本
  • 当前版本就是最新的(已修改的)版本——不是内存,是数据行的当前状态
2.2 MySQL InnoDB 看不到"内存中的数据",看到的是"数据行当前状态"
-- MySQL InnoDB 的内存数据(Buffer Pool)不是"事务私有" -- 多个事务可能同时访问同一行 -- 当前行的 DB_TRX_ID 决定谁能看 BEGIN; UPDATE account SET balance = 1000 WHERE id = 1; -- 当前行 DB_TRX_ID=100 -- 同一时刻,另一事务: SELECT balance FROM account WHERE id = 1; -- 看不到 1000 -- 因为另一事务的 ReadView 看不到 trx_id=100(未提交)

MySQL InnoDB 的核心机制

  • **不存"事务内未提交的私有数据"**到内存
  • 当前行的最新状态就是DB_TRX_ID指向的事务的修改
  • 谁能看,由 ReadView 决定

三、Oracle 真实原理(真的读内存

3.1 关键事实:Oracle 事务内未提交数据真的在内存里
-- Oracle 实际行为 BEGIN; UPDATE account SET balance = 1000 WHERE id = 1; -- 改了 -- Oracle 实际做了什么? -- 1. **把 balance=1000 写入 PGA(Process Global Area,进程私有内存)** -- 2. **不写回磁盘**(不刷新到数据块) -- 3. 数据块 SCN 没变(还指向旧版本) SELECT balance FROM account WHERE id = 1; -- ✅ 看到 1000 -- Oracle 实际做了什么? -- 1. **优先读 PGA 内存**(事务私有) -- 2. 找到 balance=1000 -- 3. 返回 1000

⚠️ 关键点

  • Oracle **真的把"事务内未提交的修改"**存在内存里
  • 这是和 MySQL 最大的差异——Oracle 事务是有状态的
  • MySQL 事务是无状态的(不看内存,看 ReadView)
3.2 Oracle 的内存分层
┌─────────────────────────────────────────────┐ │ Oracle 内存架构(事务视角) │ ├─────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────┐ │ │ │ PGA(Process Global Area) │ ← 进程私有内存 │ │ ├─ 当前事务未提交的修改 │ │ │ ├─ 临时表 │ │ │ └─ 排序区 │ │ └──────────────────────────────────┘ │ │ ↓ 优先读 │ │ ┌──────────────────────────────────┐ │ │ │ SGA(System Global Area) │ ← 系统共享内存 │ │ ├─ Buffer Cache(数据块缓存) │ │ │ ├─ Shared Pool │ │ │ └─ Redo Log Buffer │ │ └──────────────────────────────────┘ │ │ ↓ 二次读 │ │ ┌──────────────────────────────────┐ │ │ │ 磁盘 │ ← 最后读 │ │ ├─ Data Files │ │ │ └─ Undo Segment(独立回滚段) │ │ └──────────────────────────────────┘ │ └─────────────────────────────────────────────┘

Oracle 读优先级PGA(事务私有内存) > SGA(共享缓存) > 磁盘

四、MySQL vs Oracle 内存机制对比

维度MySQL InnoDBOracle
事务内未提交数据不在内存私有区在 PGA(事务私有)
内存读取优先级Buffer Pool(共享)PGA(私有) > SGA(共享)> 磁盘
能看到自己未提交原因ReadView 第 1 步:trx_id == creator_trx_id直接读 PGA 内存
其他事务能看到自己未提交❌ 不能❌ 不能(PGA 是事务私有的
事务回滚Undo Log丢弃 PGA 内存 + Undo Segment
持久化时机提交时刷脏页提交时刷 SGA + 写 Redo

核心差异

  • MySQL 是"逻辑层"控制(ReadView + DB_TRX_ID)
  • Oracle 是"物理层"控制(PGA 内存 + SCN)

五、同一段 SQL 在两个 DB 的执行路径对比

-- 老哥同一段 SQL:UPDATE 后立即 SELECT BEGIN; UPDATE account SET balance = 1000 WHERE id = 1; SELECT balance FROM account WHERE id = 1; -- 看到 1000
5.1 MySQL InnoDB 执行路径
1. UPDATE 执行 ├─ 读取 id=1 的当前行(DB_TRX_ID=99, balance=900) ├─ 写入新版本:balance=1000, DB_TRX_ID=100(当前事务 ID) ├─ 旧版本写入 Undo Log:balance=900, DB_TRX_PTR └─ 当前行 DB_ROLL_PTR 指向 Undo Log 2. SELECT 执行 ├─ 读取当前行(DB_TRX_ID=100, balance=1000) ├─ 创建 ReadView(m_ids=[100], min=100, max=101, creator=100) ├─ 4 步判断:trx_id(100) == creator_trx_id(100) ✅ └─ 返回 balance=1000 ⚠️ 注意:InnoDB 没有"内存优先"机制,**只是因为 ReadView 判断逻辑**
5.2 Oracle 执行路径
1. UPDATE 执行 ├─ 读取 id=1 的当前数据块(SCN=1234567, balance=900) ├─ 写入新版本:balance=1000 → **PGA 内存**(事务私有) ├─ SCN +1 → 1234568 └─ 数据块未刷新(commit 时才刷) 2. SELECT 执行 ├─ **优先查 PGA 内存**(事务私有) ├─ 找到 balance=1000 └─ 返回 1000(**不需要查数据块**) ⚠️ 注意:Oracle **真的从内存读**,**不经过 SCN 判断**

六、3 大本质差异

维度MySQL InnoDBOracle
事务模型无状态事务(无私有内存)有状态事务(PGA 私有内存)
判断机制逻辑判断(ReadView + DB_TRX_ID)物理判断(PGA 优先读)
数据可见性所有事务共享同一份 Buffer Pool事务私有 PGA + 共享 SGA

七、面试话术

"表面相同,原理不同

MySQL InnoDB看到自己未提交的修改,不是读内存,是ReadView 4 步判断的第 1 步trx_id == creator_trx_id→ 自己改的,返回当前版本InnoDB 事务是无状态的没有事务私有内存

Oracle看到自己未提交的修改,是真的读 PGA 内存(事务私有)。Oracle 事务是有状态的PGA 存了事务内未提交的数据

核心差异

  • MySQL = 逻辑判断(ReadView + DB_TRX_ID)
  • Oracle = 物理读取(PGA 优先 + SCN)

项目选型

  • 新项目MySQL(无状态事务,分布式友好)
  • 老项目Oracle(有状态事务,OLAP 友好)"

八、记忆口诀

"MySQL 读当前版本,Oracle 读 PGA 内存"

"MySQL 无状态,Oracle 有状态"

"MySQL 逻辑判断(ReadView),Oracle 物理读取(PGA)"

"表面相同,原理不同"

"新项目用 MySQL(无状态分布式友好),老项目用 Oracle(有状态 OLAP 友好)"

🎯 总结

问:MySQL 看到 1000 vs Oracle 看到 1000,原理相同吗?

答:完全不同的原理

数据库表面结果真实原理
MySQL InnoDB✅ 看到 1000不是读内存——是 ReadView 4 步判断第 1 步:trx_id == creator_trx_id→ 自己改的,返回当前版本
Oracle✅ 看到 1000真的读 PGA 内存(事务私有)

3 大本质差异

维度MySQL InnoDBOracle
事务状态无状态(无 PGA 私有内存)有状态(PGA 私有)
判断机制逻辑判断(ReadView + DB_TRX_ID)物理读取(PGA 优先)
数据存储Buffer Pool 共享PGA 私有 + SGA 共享

面试话术

"表面相同,原理完全不同

  • MySQL InnoDB看到自己未提交,不是读内存,是ReadView 4 步判断第 1 步trx_id == creator_trx_id
  • Oracle看到自己未提交,真的读 PGA 内存(事务私有)

核心差异

  • MySQL = 逻辑判断(ReadView)
  • Oracle = 物理读取(PGA)"

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询