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

临时表空间不存用户数据,存的是「中间结果」:排序、哈希连接、临时表。 排序先在内存(PGA)做,放不下才落盘,落盘就落到临时表空间。 它不走 redo、不走 undo,所以崩溃后内容直接丢弃。 报错是 ORA-01652(unable to extend temp segment)。

📝 本章和前面几章的关系

8.5 讲 undo(改之前的样子),这一篇讲 temp(算到一半的样子)。 两者都是「辅助空间」,但一个要恢复、一个不用恢复—— 这个差别决定了它们的备份策略完全不同。 临时表空间不够是「查询失败」的常见原因之一,而且报错信息很容易被误读成「表空间满了」。

一、简介:三种场合会用到它

场合 什么时候落盘 典型语句
排序 内存排序区放不下 order by/group by/distinct
哈希连接 哈希表放不下 大表 join 小内存
临时表 一直放着 CREATE GLOBAL TEMPORARY TABLE

排序先在 PGA 的排序区做(SORT_AREA_SIZE 或自动管理的 PGA_AGGREGATE_TARGET)。 放不下就「分块排、写盘、归并」——这就是外部排序,和算法课讲的一模一样。 临时表空间里的东西不写 redo、不写 undo。 所以实例崩溃之后,里面的内容直接丢弃,重开库时重新初始化。 这也是为什么它「不用备份」——备份一个「算到一半的中间结果」没有意义。

二、创建:用 TEMPFILE 不是 DATAFILE

关键差别:临时表空间用的是 TEMPFILE,不是 DATAFILE。 所以查它的视图是 V$TEMPFILE/DBA_TEMP_FILES,不是 V$DATAFILE。 用 V$DATAFILE 查临时文件,一行都查不到——这一点最容易搞混。 建临时表空间时 EXTENT MANAGEMENT LOCAL UNIFORM SIZE 1M 是标准写法。 临时段的大小是固定的,用 UNIFORM 更好预测—— 反正里面不留长期数据,不需要 AUTOALLOCATE 那种渐进策略。 临时表空间不能建永久对象。 CREATE TABLE ... TABLESPACE temp 直接报错—— 因为它的内容不写 redo、不保证持久,放永久数据等于自毁。

三、查询:三张视图看明白

DBA_TEMP_FILES 看文件:路径、大小、是否自动增长。 V$TEMPFILE 是它的动态版。 V$TEMPSEG_USAGE 看「现在谁在用它、用了多少」——这张最有用,出问题时第一个查它。 V$SORT_USAGE 也能看排序占用,但它只记「正在排的」,历史查不到。 要查历史得靠 AWR 里的 TEMP 相关段。 一个排查技巧:临时表空间「用完了但不释放」是正常现象。 临时段用完会标记为空闲,但文件不缩小。 所以「用了 90%」不一定是问题,要看 V$TEMPSEG_USAGE 里有没有活跃会话。 没有活跃会话还占着,才是真问题(通常是会话没断干净)。

四、临时表空间组:分散,不是均衡

临时表空间组(10g 起)的作用是「轮流分配」: 每个新会话连上来,按顺序分到组里不同的临时表空间。 十个会话同时做大排序,会分散到三个文件上,而不是挤一个。 建组三句:建表空间时带 TABLESPACE GROUP、把已有表空间加入组、把组设为默认临时表空间。 注意组不是「负载均衡」。 它是「轮着来」,不看谁空。 所以可能出现「一个用了 180MB、另一个只用 40MB」的情况——轮到谁就是谁,不回头调整。 什么时候该建组?并发排序多、单个 TEMP 老报 ORA-01652 的库。 治法是「加文件或建组」,别只盯着「把 TEMP 调大」——一个文件太大,争用反而更集中。

五、动手 Demo:算一遍外部排序:一万行分五段落盘

沙箱里没有 Oracle,CREATE TEMPORARY TABLESPACE 执行不了。 但「排序放不下内存会落多少盘」这件事,是一道能算准的题。 我按「一万行、每行 200 字节、排序区只放 2000 行」算了一遍: 分成几段、每段多大、总共落多少盘、并发放大多少倍,最后演一遍临时表空间组怎么轮流分配会话。

MB = 1024 * 1024
rows, rowlen, mem_rows = 10000, 200, 2000

seg = -(-rows // mem_rows)
per_seg = mem_rows * rowlen
print('一万行、每行 %d 字节,排序区只放 %d 行' % (rowlen, mem_rows))
print('分成 %d 段,每段 %d 行、约 %d 字节' % (seg, mem_rows, per_seg))
print('落盘总量约 %.2f MB' % (seg * per_seg / MB))

# 并发放大:100 个会话同时排
for s in (1, 10, 100):
    print('%3d 个会话同时排 -> 需要 %.0f MB 临时空间' % (s, seg * per_seg * s / MB))

# 临时表空间组:会话依次分到不同成员
members = ['temp01', 'temp02', 'temp03']
for i in range(6):
    print('第 %d 个会话 -> %s' % (i + 1, members[i % len(members)]))

实跑输出:

--------------------------------------------------------------------
第三十八课:临时表空间——排序和哈希的落脚点
--------------------------------------------------------------------

  **临时表空间不存用户数据,它存「中间结果」。**
  **什么时候会有中间结果?三种场合:排序(`order by`/`distinct`/`group by`)、哈希连接、临时表。**
  **排序先在内存里做(`PGA` 的排序区),放不下才落盘。**  **落盘落到哪儿?临时表空间。**
  **所以「临时表空间不够」的报错是 `ORA-01652`(unable to extend temp segment)**,
  **它的表现是「一条查询跑着跑着失败」,而不是「写数据失败」。**

  算一下落盘的量
  --------------------------------------------------------------------
  **一万行、每行 200 字节,排序区只能放 2000 行:**
  **于是分成 5 段,每段 2000 行先排好,写到临时表空间。**
  **再把这 5 段归并(merge),输出有序结果。**
  **落盘总量约 2MB。**
  **这就是「外部排序」的经典做法:分块排、写盘、归并。**
  **临时表空间要装的就是这 5 段中间结果。**
  **注意:如果同时有 100 个会话都在做大排序,需要的临时空间是 2MB 乘 100。**
  **这就是为什么临时表空间不能只按「一条语句够用」来给。**

  建一个临时表空间
  --------------------------------------------------------------------

  [Oracle 原码 · 本沙箱不执行]
  | CREATE TEMPORARY TABLESPACE temp2
  |   TEMPFILE '/u01/oradata/ORCL/temp02.dbf'
  |   SIZE 200M AUTOEXTEND ON NEXT 50M MAXSIZE 2G
  |   EXTENT MANAGEMENT LOCAL UNIFORM SIZE 1M;
    临时表空间的文件叫 TEMPFILE,不是 DATAFILE。

  **关键差别:临时表空间用的是 `TEMPFILE`,不是 `DATAFILE`。**
  **所以查它的视图是 `v$tempfile`/`dba_temp_files`,不是 `v$datafile`。**  **这一点最容易搞混**——
  **用 `v$datafile` 查临时文件,一行都查不到。**
  **临时表空间不走 redo,也不走 undo。**  **所以它里面的东西「不用恢复」**——
  **实例崩溃之后临时文件里的内容直接丢弃,重开库时重新初始化。**
  **这也是为什么临时表空间可以 `NOLOGGING` 而不影响恢复。**

  临时表空间组:让并发排序不挤一个文件
  --------------------------------------------------------------------

  SQL> select grp as 组, member as 成员, used_mb as 已用MB from tgroup
  组       成员       已用MB
  --------------------------
  TEMP_GRP temp01.dbf 180
  TEMP_GRP temp02.dbf 40
  TEMP_GRP temp03.dbf 55
  已选择 3 行

  [Oracle 原码 · 本沙箱不执行]
  | CREATE TEMPORARY TABLESPACE temp2 TEMPFILE '...' SIZE 200M
  |   TABLESPACE GROUP temp_grp;
  | ALTER TABLESPACE temp1 TABLESPACE GROUP temp_grp;
  | ALTER DATABASE DEFAULT TEMPORARY TABLESPACE temp_grp;

  **组的作用是「轮流分配」:**  **每个新会话连上来,按顺序分到组里不同的临时表空间。**
  **这样十个会话同时做大排序,会分散到三个文件上,而不是挤一个。**
  **上表里 180/40/55 的分布就是「分配不均」的样子**——
  **轮到哪个是哪个,不看谁空。所以组不是负载均衡,是「分散」。**
  **建库时如果没建临时表空间组,默认只有一个 `TEMP`。**
  **单实例跑跑没问题;一旦并发排序多,`ORA-01652` 就会开始出现。**
  **治法是加文件或者建组,别只盯着「把 `TEMP` 调大」**——  **单个文件太大,争用反而更集中。**

  临时表空间的两个禁忌
  --------------------------------------------------------------------
  **① 不能在里面建永久对象。**  **`CREATE TABLE ... TABLESPACE temp` 直接报错**——
  **因为临时表空间的内容不写 redo、不保证持久,放永久数据等于自毁。**
  **② 不能把 `TEMP` 删掉。**  **它是数据库的默认临时表空间,删了所有排序都没地方落。**
  **要换就新建一个、切成默认、再删旧的**:
  **`ALTER DATABASE DEFAULT TEMPORARY TABLESPACE temp2;`**
  **顺带一个排查技巧:临时表空间「用完了但不释放」是正常现象。**
  **临时段用完会标记为空闲,但文件不缩小**——
  **所以看「临时表空间用了 90%」不一定是问题,要看 `v$tempseg_usage` 里是谁在用。**

第一段:一万行分 5 段,每段 2000 行、约 400KB,总落盘约 2MB。 这就是外部排序的「分块排、写盘、归并」三步里的前两步。 注意「约 2MB」这个数很小——一条语句的临时空间通常不是问题,问题是并发。 所以下面三行才是重点:10 个会话要 19MB,100 个会话要 191MB。 并发放大是线性的,但现实里会更糟—— 因为会话不是同时开始同时结束,峰值往往出现在「大家都排到一半」的时候。 这就是为什么临时表空间要按「峰值并发 × 单语句量 × 2」给。 最后六行是临时表空间组的分配:会话 1 到 3 分别落到 temp01/temp02/temp03, 第 4 个会话又回到 temp01。 这就是「轮流」,不看谁空。 所以会出现「一个用了 180MB、另一个只用 40MB」的情况——这是分配策略决定的,不是 bug。 要真均衡,得靠监控 + 手工调,组本身做不到。

30 秒实验

  1. 把 mem_rows 从 2000 改成 5000,重跑。段数从 5 降到 2,落盘量不变。 这说明「内存大一点,段少一点,但总量差不多」—— 内存只减少「段数」,减少不了「要排的数据量」。
  2. 把 rows 从 10000 改成 100000,重跑。落盘量涨十倍。 这就是「大表排序要盯临时表空间」的原因。
  3. 把 members 改成两个,重跑。六个会话分成 2/2/2。 组里成员越多越分散,但每个成员的争用也越少——代价是管理复杂度上升。

常见误区

  1. 用 V$DATAFILE 查临时文件。 查不到。临时文件在 V$TEMPFILE/DBA_TEMP_FILES 里。
  2. 看到临时表空间用了 90% 就急着扩容。 临时段用完不释放是正常的,要看 V$TEMPSEG_USAGE 里有没有活跃会话。
  3. 把永久表建在临时表空间里。 直接报错。它不写 redo、不保证持久,放永久数据等于自毁。
  4. 以为临时表空间组是负载均衡。 它是「轮流分配」,不看谁空,会出现明显不均。
自测三问(点开对答案) 1. 临时表空间里的东西为什么不备份? —— **因为它存的是「算到一半的中间结果」**,不写 redo、不走 undo。 **崩溃后直接丢弃,重开库重新初始化。** **备份一个中间结果没有意义。** 2. `ORA-01652` 是什么问题? —— **临时表空间不够,扩不出临时段。** **表现是「一条查询跑着跑着失败」**,不是写数据失败。 3. 临时表空间组解决什么问题? —— **并发排序挤在一个文件上的争用。** **它把会话轮流分散到多个临时表空间**,**注意是「分散」不是「均衡」。** 4. 临时表空间「用了 90% 但不释放」正常吗? —— **通常正常。** 临时段用完标记空闲但文件不缩。**要看 `V$TEMPSEG_USAGE` 里有没有活跃会话。**

第 8 章到此为止。表空间、数据文件、undo、temp——存储这一层算是走完了。 下一篇进第 9 章:数据表对象。 话题从「空间怎么分」转到「表怎么设计」。

相关笔记