🏺 第8章 管理表空间和数据文件
📄 一句话概括

撤销(undo)存的是「改之前是什么」,不是「改成了什么」。 所以它能干三件事:回滚、读一致性、闪回查询。 UNDO_RETENTION 是「尽力保留」不是「保证保留」。 空间不够就会覆盖老 undo,长查询于是报 ORA-01555(snapshot too old)。

📝 本章和前面几章的关系

8.1 到 8.4 讲的是「数据放哪」。这一篇讲「改之前的样子放哪」。 undo 是 Oracle 并发控制的底座——没有它,一个会话在改表,别的会话就没法读一致的快照。 这一篇的算术是全章最有用的:「undo 要多大」这道题,面试和实战都会问,答案是一道乘法。

一、三个用处,一份数据

回滚:rollback 就是把 undo 里的旧值写回去。 读一致性:你查的时候别人在改,Oracle 拿 undo 把块「还原」成你开始查那一刻的样子。 闪回查询:select ... as of timestamp 也是拿 undo 还原,时间点由你定。 这三件事是同一份数据支撑的,只是「还原到什么时候」不同: 回滚还原到事务开始前;读一致性还原到「你这条查询开始那一刻」; 闪回查询还原到「你指定的那个时间」。 所以 undo 是「有保质期的历史」——过了保质期就没了,这就是 ORA-01555 的根源。 注意它叫「快照太旧」,不是「数据坏了」,本质是「你要的那个版本已经被覆盖了」。

二、两个参数:UNDO_TABLESPACE 和 UNDO_RETENTION

UNDO_TABLESPACE 指定当前用哪个 undo 表空间。 11g 起只能有一个处于使用状态(老的 MANUAL 回滚段模式已经废了)。 UNDO_RETENTION 是「希望保留多久」,单位秒,默认 900(15 分钟)。 关键在「希望」两个字——它是给优化器的建议值,空间不够时 Oracle 会优先保证新事务能跑。 要真保证,得开 RETENTION GUARANTEE: ALTER TABLESPACE undotbs1 RETENTION GUARANTEE; 代价是空间不够时【新事务直接失败】(ORA-30036)——牺牲新事务,换老查询能跑完。 这是一个明确的取舍,没有「都对」的答案: 跑批多的库倾向 GUARANTEE(保证长查询不被掐);OLTP 倾向默认(保证新事务不被拒)。 DBA_TABLESPACES.RETENTION 那一列能看出当前是哪种。

三、算一笔:undo 到底要给多大

公式是一道乘法:每秒 undo 量 × 保留秒数 × 1.5 的余量。 每秒 undo 量从 V$UNDOSTAT 的 UNDOBLKS 取——它按 10 分钟一段统计,找出峰值那一段。 为什么乘 1.5?因为业务量会涨、批量任务会突然插进来。 按峰值刚好给,等于每月都要扩容一次。 一个反直觉的事实:undo 表空间「用满」不一定报错。 默认状态下它会自己覆盖老 undo, 所以「undo 使用率 95%」往往没事,真正出事的是「长查询正好需要被覆盖的那段」。 这也是 ORA-01555 难复现的原因。

四、切换与删除:先有替代,再动老的

换 undo 表空间:ALTER SYSTEM SET UNDO_TABLESPACE = undotbs2; 切完之后旧的那个不能立刻删——里面还有未过期的 undo,得等过了 undo_retention 才能删。 立刻删会报 ORA-01548(active rollback segment exists)。 正确做法是: 切走 → 等一会儿(至少一个 retention 周期)→ 确认 V$ROLLSTAT 里没活动段 → 再删。 还有一个坑:切 undo 表空间会让「当前正在跑的长事务」出问题。 所以要在业务低峰做,而且要提前确认没有长事务—— V$TRANSACTION 里 USED_UBLK 大、START_TIME 早的那些就是。

五、动手 Demo:算清 ORA-01555:200MB 的 undo 撑不住 15 分钟

沙箱里没有 Oracle,ALTER TABLESPACE ... RETENTION 执行不了。 但 ORA-01555 的成因是一道除法:能留多久 = undo 大小 ÷ 每秒产生量。 这道题沙箱里能算得比文档还清楚。 我先按 200MB/每秒 1MB 算出「只能留 200 秒」,跟默认的 900 秒一比,缺口就出来了, 然后按峰值反算三种保留时长各要多大空间。

size_mb, rate_mb_s, retention = 200, 1, 900

keep = size_mb / rate_mb_s
print('undo %dMB,每秒产生 %dMB' % (size_mb, rate_mb_s))
print('能留 %.0f 秒,而 UNDO_RETENTION 要 %d 秒' % (keep, retention))
print('差 %.0f 秒 -> 空间不够,老 undo 会被覆盖 -> ORA-01555' % (retention - keep))

# 按峰值估容量:每秒 undo 量 x 保留秒数 x 1.5 余量
peak = 1.4
for r in (900, 3600, 10800):
    print('峰值 %.1fMB/s,留 %5d 秒(%2d 分钟)-> 约 %.2f GB'
          % (peak, r, r // 60, peak * r * 1.5 / 1024))

实跑输出:

--------------------------------------------------------------------
第三十七课:撤销表空间——回滚、读一致、闪回,都靠它
--------------------------------------------------------------------

  **撤销(undo)存的不是「改成了什么」,是「改之前是什么」。**
  **所以它有三个用处:回滚、读一致性、闪回查询。**
  **这三件事其实是同一份数据支撑的,只是用法不同。**
  **回滚**:`rollback` 就是把 undo 里的旧值写回去。
  **  **读一致性**:你查的时候别人在改,Oracle 拿 undo 把块「还原」成你开始查那一刻的样子。**
  **闪回查询**:`select ... as of timestamp` 也是拿 undo 还原,只是时间点由你指定。

  算一笔账:undo 到底要留多久
  --------------------------------------------------------------------
  **假设撤销表空间 200MB,业务每秒产生 1MB 的 undo。**
  **那么理论上能留 200 秒的 undo。**
  **而 `UNDO_RETENTION` 默认是 900 秒(15 分钟)。**
  **200 秒 < 900 秒——这就是问题所在。**
  **`UNDO_RETENTION` 是「尽力保留」,不是「保证保留」。**
  **空间不够时,Oracle 会优先保证新事务能跑,把老 undo 覆盖掉。**
  **这时候一个跑了很久的查询去读老块,就报 `ORA-01555`(snapshot too old)。**
  **要真保证,得开 `RETENTION GUARANTEE`:**  **`ALTER TABLESPACE undotbs1 RETENTION GUARANTEE;`**
  **代价是空间不够时【新事务直接失败】**(`ORA-30036`),
  **而不是牺牲老查询。**  **这是一个取舍:牺牲新事务,还是牺牲长查询。**

  两个参数:UNDO_TABLESPACE 和 UNDO_MANAGEMENT
  --------------------------------------------------------------------

  [Oracle 原码 · 本沙箱不执行]
  | UNDO_MANAGEMENT = AUTO      -- 11g 之后这是唯一可选值
  | UNDO_TABLESPACE = undotbs1
  | UNDO_RETENTION  = 900
    AUTO 之外的取值已经是历史了。

  **`UNDO_MANAGEMENT` 只有 `AUTO` 一种了**,
  **老教材里那个 `MANUAL`(用回滚段)在 11g 之后彻底没了。**  **所以这一段教材内容要打折看**:
  **知道「以前有回滚段这回事」就行,不用记语法。**
  **`UNDO_TABLESPACE` 可以切,但不能没有:**  **`ALTER SYSTEM SET UNDO_TABLESPACE = undotbs2;`**
  **切完之后旧的那个不能立刻删**——
  **里面还有未过期的 undo,得等 `undo_retention` 过了才能删。**
  **立刻删会报 `ORA-01548`(活动回滚段存在)。**

  怎么估 undo 大小
  --------------------------------------------------------------------
  **公式是「每秒 undo 量 × 保留秒数 × 余量」。**
  **每秒 undo 量从哪儿来?`v$undostat` 里 `UNDOBLKS` 那一列,按 10 分钟一段统计。**
  **算一个峰值,乘保留时间,再乘 1.5 倍余量,就是该给的大小。**
  **峰值 1.4MB/秒,保留 900 秒(15 分钟),需要约 1.8GB。**
  **峰值 1.4MB/秒,保留 3600 秒(60 分钟),需要约 7.4GB。**
  **峰值 1.4MB/秒,保留 10800 秒(180 分钟),需要约 22.1GB。**
  **看到这个数就明白为什么 DBA 总说「undo 别抠」**——
  **留 15 分钟和留 3 小时,空间差 12 倍。**
  **生产上常见的是 1 小时起步,跑批多的库给到 3 到 6 小时。**

前两行是题干:undo 200MB,每秒产生 1MB。 能留 200 秒,而 UNDO_RETENTION 要 900 秒。 差 700 秒。 第三行直接给出结论:空间不够,老 undo 会被覆盖,于是 ORA-01555。 注意「会被覆盖」是 Oracle 的主动选择——它优先保证新事务能跑,牺牲老快照。 后面三行是反算:按峰值 1.4MB/秒,留 15 分钟要 1.85GB,留 1 小时要 7.4GB, 留 3 小时要 22.1GB。 这三个数就是「undo 别抠」的量化依据。 注意 1.4MB/秒这个峰值不是拍脑袋—— V$UNDOSTAT 里 UNDOBLKS 乘块大小再除时间段,就能得到每秒量。 真实库要先测再给,别照抄我这个数。 还有一个隐藏变量:并发。 上面算的是「总产生量」,它已经含了并发。 如果只按单条语句估,会严重低估——一百个会话同时改,undo 产生量是一百份。

30 秒实验

  1. 把 rate_mb_s 从 1 改成 5,重跑。能留的时间从 200 秒降到 40 秒。 产出量翻五倍,保质期缩到五分之一——这就是纯除法。
  2. 把 size_mb 从 200 改成 2048,重跑。能留 2048 秒,超过 900 秒的 retention。 这时候「默认模式」也够用了,不必开 GUARANTEE。
  3. 把 peak 从 1.4 改成 8(跑批库常见),重跑。留 3 小时要 126GB。 这就是为什么跑批库的 undo 常常给到上百 GB。

常见误区

  1. 以为设了 UNDO_RETENTION 就一定保留那么久。 它是「尽力」,空间不够照样覆盖。 要保证得开 RETENTION GUARANTEE。
  2. 看到 undo 使用率 95% 就慌。 默认模式下它会自己覆盖, 真正出事的是「长查询正需要被覆盖的那段」,不是使用率本身。
  3. 切完 undo 表空间立刻删旧的。 报 ORA-01548, 里面还有未过期的 undo。 等一个 retention 周期再删。
  4. 按单条语句估 undo 大小。 并发一上来就低估得离谱,要按 V$UNDOSTAT 的总量峰值估。
自测三问(点开对答案) 1. undo 支撑哪三件事? —— **回滚、读一致性、闪回查询。** **三件事都是「拿旧值还原」,只是还原到的时间点不同。** 2. `UNDO_RETENTION` 是保证还是建议? —— **是「尽力保留」,不是保证。** **空间不够时会覆盖老 undo。** **要保证得开 `RETENTION GUARANTEE`,代价是新事务可能失败。** 3. `ORA-01555` 的本质是什么? —— **查询需要的那个数据版本,它的 undo 已经被覆盖了。** **不是数据坏了,是「快照太旧」。** 4. undo 大小怎么估? —— **每秒 undo 量 × 保留秒数 × 1.5。** **每秒量从 `V$UNDOSTAT` 的 `UNDOBLKS` 取峰值。**

下一篇讲临时表空间:排序和哈希的落脚点。 ORA-01652 就是它不够用了。

相关笔记