这两段语句虽然表现的结果都是一样的,但其实原理是不同的,对吗?:
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 InnoDB | Oracle |
|---|---|---|
| 事务内未提交数据 | ❌ 不在内存私有区 | ✅ 在 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 InnoDB | Oracle |
|---|---|---|
| 事务模型 | 无状态事务(无私有内存) | 有状态事务(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 InnoDB | Oracle |
|---|---|---|
| 事务状态 | 无状态(无 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)"