Oracle 多版本并发控制(MVCC)详解

Oracle 多版本并发控制(MVCC)详解

适用版本:Oracle Database 11g / 12c / 19c / 23ai 文档版本:v1.0 / 2026-07


1. 概述

Oracle 通过多版本并发控制(MVCC,Multi-Version Concurrency Control) 实现高并发下的数据一致性[1]。

核心原则

  • 读不阻塞写:查询不阻塞 DML
  • 写不阻塞读:DML 不阻塞查询
  • 写写串行:同一行的并发修改排队执行

实现基础

  • Undo 数据:保存行的旧版本
  • SCN:标识版本时间
  • CR 块(Consistent Read):从 Undo 构造的旧版本块

2. 工作原理

2.1 旧版本构造流程

事务 T1(SCN=1000)开始查询:
  SELECT sal FROM emp WHERE id=1;

事务 T2(SCN=1005)已经修改了 emp.id=1:
  UPDATE emp SET sal=5000 WHERE id=1;
  COMMIT;

T1 的查询执行流程:
1. 找到 id=1 所在数据块
2. 检查块头 SCN:当前块 SCN=1005
3. 1005 > 1000(T1 查询 SCN),需要构造 CR 块
4. 从 Undo 段读取 ITL 中记录的 Undo 地址
5. 应用 Undo 数据,回滚到 SCN ≤ 1000 的状态
6. 得到 CR 块,返回旧值

2.2 Undo 链

如果一行被多次修改,旧版本通过 Undo 链连接:

当前块(SCN=1005): sal=5000

   ▼ Undo 记录1
CR 块(SCN=1003): sal=4500

   ▼ Undo 记录2
CR 块(SCN=1001): sal=4000

   ▼ Undo 记录3
CR 块(SCN=0998): sal=3500(足够旧,T1 可读)

3. CR 块构造示例

-- 准备测试数据
DROP TABLE mvcc_test PURGE;
CREATE TABLE mvcc_test (id NUMBER, sal NUMBER);
INSERT INTO mvcc_test VALUES (1, 1000);
COMMIT;

-- 会话 1:开始事务但不提交
UPDATE mvcc_test SET sal=2000 WHERE id=1;
-- 不要 COMMIT

-- 会话 2:查询(应该看到旧值 1000)
SELECT * FROM mvcc_test WHERE id=1;
-- 返回:1, 1000(CR 块)

-- 会话 2:使用 AS OF 查询
SELECT * FROM mvcc_test AS OF TIMESTAMP (SYSTIMESTAMP - INTERVAL '1' MINUTE);

4. 读一致性的三个层次

层次范围设置方式
语句级单条 SQL 执行期间默认(READ COMMITTED)
事务级整个事务期间SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
只读整个事务,不允许 DMLSET TRANSACTION READ ONLY
-- 语句级(默认)
SELECT * FROM emp;  -- 此语句看到的是开始执行时的 SCN 快照

-- 事务级
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SELECT * FROM emp;  -- 看到事务开始时的快照
-- 其他会话修改并提交后
SELECT * FROM emp;  -- 仍看到事务开始时的快照
COMMIT;

-- 只读事务
SET TRANSACTION READ ONLY;
SELECT * FROM emp;
UPDATE emp SET sal=sal+1;  -- 报错:ORA-01456

5. 相关视图

-- 查看当前事务
SELECT addr, xidusn, xidslot, xidsqn, status, 
       start_time, used_ublk
FROM v$transaction;

-- 查看 Undo 段
SELECT segment_name, tablespace_name, status 
FROM dba_rollback_segs;

-- 查看 Undo 表空间使用
SELECT 
  tablespace_name,
  SUM(bytes)/1024/1024 AS used_mb
FROM dba_undo_extents
WHERE status='ACTIVE'
GROUP BY tablespace_name;

-- CR 块统计
SELECT name, value 
FROM v$sysstat 
WHERE name IN (
  'consistent gets',         -- CR 读次数
  'consistent gets - examination',
  'data blocks consistent reads - undo records applied',
  'no work - consistent read gets',
  'cleanouts only - consistent read gets'
);

6. 常见坑与排错

6.1 ORA-01555: snapshot too old

现象:长查询报 ORA-01555。

原因:CR 块构造需要的 Undo 数据已被覆盖。

修复

-- 1. 增大 Undo 表空间
ALTER TABLESPACE undotbs1 ADD DATAFILE '/u02/undo02.dbf' SIZE 1G;

-- 2. 增加 undo_retention
ALTER SYSTEM SET undo_retention=3600 SCOPE=BOTH;

-- 3. 启用 Undo 保留保证
ALTER TABLESPACE undotbs1 RETENTION GUARANTEE;

-- 4. 优化长查询,分批处理

6.2 长事务导致 Undo 膨胀

现象:Undo 表空间快速增长。

修复

-- 找出长事务
SELECT 
  s.sid, s.serial#, s.username,
  t.start_time, t.used_ublk
FROM v$session s
JOIN v$transaction t ON s.taddr = t.addr
ORDER BY t.used_ublk DESC;

-- 终止长事务
ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;

6.3 CR 块构造消耗 Undo

现象:查询慢,统计显示大量 data blocks consistent reads - undo records applied

修复:缩短事务持续时间,减少长事务与长查询并行。


7. 最佳实践

  1. 合理设置 undo_retention:业务最长查询时间的 2-3 倍
  2. 避免长事务:定期 COMMIT,减少 Undo 占用
  3. 批量操作分批提交:避免一次性事务过大
  4. 监控 Undo 使用率dba_undo_extents 视图
  5. 关键业务启用 Guarantee:保证 Undo 不被覆盖
  6. 长查询使用 AS OF:明确指定时间点,避免依赖默认 Undo 保留

8. 参考资料

[1] Oracle Database Concepts 19c, “Multiversion Read Consistency” https://docs.oracle.com/en/database/oracle/oracle-database/19/cncpt/data-concurrency-and-consistency.html

[2] AskTOM, “MVCC and Undo Internals” https://asktom.oracle.com/pls/apex/f?p=100:1:0

[3] 墨天轮,“Oracle MVCC 与读一致性深度解析” https://www.modb.pro/db/1759656813559025664