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

一句 CREATE TABLESPACE 有五个可调项: 区管理方式、段空间管理方式、块大小、是否大文件、数据文件怎么给。 现代做法只有一种组合值得记:本地化管理 + ASSM。 字典管理和 MANUAL 都是历史包袱,看懂老库就行,别在新库上用。

📝 本章和前面几章的关系

8.1 讲结构,8.2 讲 Oracle 自己用的两个。这一篇讲「你自己建一个该怎么建」。 这一篇的重点不是记语法,是记两个判断: 什么时候该用大文件表空间,什么时候该用非标准块。 其余选项基本是「照着默认走」。

一、最小可用的一条

五个子句就是五个决定: DATAFILE 放哪、SIZE 起步多大、AUTOEXTEND 要不要自己长、EXTENT MANAGEMENT 区怎么管、 SEGMENT SPACE MANAGEMENT 块里怎么管。

子句 常见取值 怎么选
SIZE 100M~2G 起步别太小,不然一上线就长
AUTOEXTEND ON NEXT 10M MAXSIZE 2G 给 MAXSIZE,别让它无限长
EXTENT MANAGEMENT LOCAL AUTOALLOCATE 默认就用这个
SEGMENT SPACE MANAGEMENT AUTO 默认就用这个

MAXSIZE 一定要给。 不写就是「能长到磁盘满」——生产上见过一个失控的库把数据盘吃光,连带同机的备份服务一起挂。 给个上限,满了让它报错,比悄悄吃光好。

二、本地化管理:位图替掉字典表

老办法(字典管理)把「哪些区被用了」记在 SYSTEM 的 UET$/FET$ 里。 每分配一个区就要改字典表——高并发下这一张表会成为全库瓶颈,这是 90 年代设计的硬伤。 新办法(本地化管理)把使用情况记成位图,存在数据文件头里。 分配一个区就是改一个比特,不碰字典表。 代价是位图占一点空间,好处是快得多。 从 10g 起本地化管理是默认值,字典管理已经废弃。 教材还讲字典管理,是为了让你看懂老库—— DBA_TABLESPACES.EXTENT_MANAGEMENT 显示 DICTIONARY 的就是老库。 遇到这种库,第一反应应该是「该升级了」,不是「学它怎么用」。 本地化管理还有两种分配策略: AUTOALLOCATE(区大小分 64KB/1MB/8MB/64MB 四档, 按需跳)和 UNIFORM SIZE(每个区一样大)。 小表多的库用前者省空间,大表为主、要求可预测的库用后者。

三、段空间管理:freelist 与 bitmap

区管好了,块里面还要管:这个块还有没有空间插新行? MANUAL 用「空闲列表」(freelist)——一个块一个块挂在链表上,所有会话抢同一个链表头。 AUTO 用位图(ASSM)——把块的空闲程度分成几档,记成几个比特,各找各的段。 好处是没有单点争用,坏处是空间利用率略低(位图本身占空间)。 高并发插入的库,MANUAL 会撞上 buffer busy waits 和 enq: HW - contention。 这两个等待事件一飙高,插入就排队。 从 9i 起 AUTO 是默认,MANUAL 只在极特殊场景(比如要精确控制空间)才用。 一个硬约束:ASSM 只能用在本地化管理表空间上。 「字典管理 + ASSM」这个组合不存在——因为它没有位图可放。 所以建表空间时这两个选项是绑在一起的。

四、非标准块:先配缓存,再建表空间

默认块大小在建库时定(DB_BLOCK_SIZE),之后不能改。 但可以额外建「16K 块」的表空间—— 前提是先给这个块大小分配缓冲池:ALTER SYSTEM SET db_16k_cache_size = 64M; 不配就建,报 ORA-29339(tablespace block size 与 cache size 不匹配)。 这个提示很不直观——它说的是「缓存不匹配」,不是「不支持 16K」,第一次见容易误判成版本问题。 什么时候真需要非标准块? ① 从别的库传表空间过来(跨平台传输表空间要求块大小一致); ② 大行、全表扫描为主的表,16K 块能少读几次;③ 行特别小的表,2K 块少浪费。 日常 OLTP 库基本用不上。 加一块非标准块缓存,等于从 SGA 里切走一块内存,默认 8K 缓存就少了——这不是白拿的。

五、大文件表空间:一个文件撑到 32TB

普通数据文件用 22 位块号,最多 2 的 22 次方块——8K 块下就是 32GB。 大文件用 32 位块号,2 的 32 次方乘 8K,就是 32TB。

块大小 普通文件上限 大文件上限
8K 32 GB 32 TB
16K 64 GB 64 TB
32K 128 GB 128 TB

大文件表空间只能有一个数据文件,而且它必须能自己长。 好处是不用管「加文件」——一个文件能长到 32TB,等于「永远不用扩容」。 坏处是「备份恢复的粒度变粗」:一个文件几十 TB,RMAN 想切小都没法切。 所以它适合数据仓库里那种「一个表就几 TB」的场景,不适合 OLTP。 OLTP 的表空间一般几百 GB,普通表空间加几个文件就够了,而且「多个文件」反而让 I/O 能并行。

六、动手 Demo:算清两个上限,再演一遍位图和争用

沙箱里没有 Oracle,CREATE TABLESPACE 执行不了。 但这一篇最值得记住的是三个数:大文件上限、位图怎么找空位、freelist 争用怎么来的。 这三个都能算。 我按块大小算了六种上限,用 12 个比特演了一遍区位图, 再把「37 个会话抢 1 个入口」和「抢 8 个入口」摆在一起对比。

KB, GB, TB = 1024, 1024 ** 3, 1024 ** 4

# 普通数据文件 22 位块号,大文件 32 位
for blk in (8, 16, 32):
    small = 2 ** 22 * blk * KB // GB
    big = 2 ** 32 * blk * KB // TB
    print('块 %2dK:普通上限 %4d GB,大文件上限 %4d TB' % (blk, small, big))

# 本地化管理:位图演一遍。1 已用、0 空闲
bits = [1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0]
print(''.join(map(str, bits)))
print('已用', bits.count(1), '空闲', bits.count(0))
print('第一个空位在', bits.index(0), '号')

# 段空间管理:freelist 与 bitmap 的差别在「几个会话抢同一个头」
for mode, heads, sessions in (('MANUAL', 1, 37), ('AUTO', 8, 37)):
    print('%-7s %d 个入口,%d 个会话 -> 平均每个入口 %.1f 个'
          % (mode, heads, sessions, sessions / heads))

实跑输出:

--------------------------------------------------------------------
第三十五课:创建表空间——五种选择,各有各的坑
--------------------------------------------------------------------

  **一句 `CREATE TABLESPACE` 有五个可以调的地方:**
  **区怎么管(本地/字典)、段空间怎么管(自动/手动)、块多大、是不是大文件、放哪些文件。**
  **每一个选择都会影响以后怎么维护。**

  最小可用的一条
  --------------------------------------------------------------------

  [Oracle 原码 · 本沙箱不执行]
  | CREATE TABLESPACE users
  |   DATAFILE '/u01/oradata/ORCL/users01.dbf'
  |   SIZE 100M
  |   AUTOEXTEND ON NEXT 10M MAXSIZE 2G
  |   EXTENT MANAGEMENT LOCAL AUTOALLOCATE
  |   SEGMENT SPACE MANAGEMENT AUTO;
    五个子句,最后两个决定「以后好不好维护」。

  **`AUTOEXTEND ON NEXT 10M MAXSIZE 2G` 的意思是:**
  **写满了自己长,一次长 10MB,最多长到 2GB。**  **不写 `MAXSIZE` 就是「能长到磁盘满」**——
  **生产上不建议,因为一个失控的库能把整块盘吃光,连带别的服务一起挂。**

  本地化管理 vs 字典管理
  --------------------------------------------------------------------
  **老办法(字典管理)是把「哪些区被用了」记在 `SYSTEM` 的 `UET$`/`FET$` 里。**
  **每分配一个区就要改字典表**——  **这是 90 年代的设计,高并发下 `UET$` 这一张表会成为全库瓶颈。**
  **新办法(本地化管理)是把「哪些区被用了」记成一张位图,存在数据文件头里。**
  **分配一个区就是改一个比特,不用碰字典表。**
  **代价是位图占空间,好处是快得多、也不用担心字典表争用。**
  **拿 12 个区演一下位图:已用 4 个,剩下 8 个空位。**
  111001000000
  **`1` 是用掉的,`0` 是空的。找一个空区就是找第一个 0**,  **这个操作是 O(1) 级的**,不需要扫表。
  **从 10g 起,`EXTENT MANAGEMENT LOCAL` 是默认值,字典管理已经废弃。**
  **教材里还讲字典管理,是为了让你看懂老库**——
  **真遇到 9i 的库,`DBA_TABLESPACES.EXTENT_MANAGEMENT` 会显示 `DICTIONARY`。**

  段空间管理:AUTO 和 MANUAL
  --------------------------------------------------------------------
  **区管好了,块里面还要管:一个块里还有没有空间插新行?**
  **`MANUAL` 用「空闲列表」(freelist)——一个块一个块挂在一个链表上。**
  **`AUTO` 用位图(bitmap)——把块的空闲程度分成几档,记成几个比特。**

  SQL> select mode as 段空间管理, waiters as 并发等待次数, note as 说明 from freelist
  段空间管理    并发等待次数 说明
  -------------------------------------------------------------
  MANUAL        37           所有会话抢同一个 freelist 头
  MANUAL + 多组 4            按实例分 freelist 组,缓解但没根除
  AUTO          0            位图分段,各找各的
  已选择 3 行
  **上面那三个数是模型里的示意值,不是实测**——
  **但趋势是真的:`MANUAL` 在高并发插入时会出现「freelist 争用」**,
  **表现是 `buffer busy waits` 和 `enq: HW - contention` 这两个等待事件飙高。**
  **从 9i 起 `AUTO` 是默认,`MANUAL` 只在极特殊场景才用。**
  **有一个硬约束:`SEGMENT SPACE MANAGEMENT AUTO` 只能用在本地化管理表空间上。**
  **字典管理 + ASSM 的组合不存在**,因为它没有位图可放。

  非标准块:要先配缓存才能建
  --------------------------------------------------------------------
  **默认块大小在建库时定(`DB_BLOCK_SIZE`,一般是 8K),之后不能改。**
  **但可以额外建「16K 块」的表空间**——
  **前提是先给这块大小的缓冲池分配内存:**  **`ALTER SYSTEM SET db_16k_cache_size = 64M;`**
  **不配就建,报 `ORA-29339`(tablespace block size 与 cache size 不匹配)。**
  **这个错的提示很不直观**,很多人卡在这里。
  **非标准块什么时候有用?**
  **① 从别的库(不同块大小)传表空间过来;② 大行、全表扫描为主的表,16K 块能少读几次;③ 行特别小的表,2K 块能少浪费。**  **日常业务库基本用不上,别为了「看起来高级」去加。**

  大文件表空间:一个文件撑到 32TB
  --------------------------------------------------------------------
  **块 8K 时:普通表空间单个数据文件上限 32 GB,大文件表空间 32 TB。**
  **块 16K 时:普通表空间单个数据文件上限 64 GB,大文件表空间 64 TB。**
  **块 32K 时:普通表空间单个数据文件上限 128 GB,大文件表空间 128 TB。**
  **差别来自「文件里能编址多少个块」:**  **普通数据文件用 22 位块号,大文件用 32 位。**
  **2 的 32 次方乘 8K,就是 32TB。**

  [Oracle 原码 · 本沙箱不执行]
  | CREATE BIGFILE TABLESPACE big_ts
  |   DATAFILE '/u01/oradata/ORCL/big01.dbf'
  |   SIZE 10G AUTOEXTEND ON NEXT 1G;
    大文件表空间只能有一个数据文件,靠它自己长。

  **大文件表空间有一个数据文件,而且只能有一个。**
  **好处是不用管「加文件」(一个文件能长到 32TB);坏处是「备份和恢复的粒度变粗」**——
  **一个文件几十 TB,RMAN 想切小都没法切。**
  **所以它适合「数据仓库里那种一个表就几 TB」的场景,不适合 OLTP。**

前六行是两张上限表:8K 块下普通文件 32GB、大文件 32TB,差 1024 倍。 16K 和 32K 各自翻倍——因为上限 = 块数 × 块大小,块数固定,块大了上限就跟着涨。 中间三行是位图:111001000000。 1 是用掉的区,0 是空的,一共 12 个位置、已用 4 个、空 8 个。 找一个空位就是找第一个 0,输出说在 3 号位置(从 0 数起)。 位图的价值就在这个「找第一个 0」:它是一次内存扫描,不涉及任何表的读写。 字典管理时代,这一步要改 UET$/FET$ 两张表,还要维护索引——并发一高就锁。 最后两行是 MANUAL 和 AUTO 的对比: 37 个会话、1 个入口,平均每个入口 37 个会话在排队;换成 8 个入口,平均 4.6 个。 这就是 freelist 组和 ASSM 能缓解争用的原因——不是变快了,是「排队的人分散了」。 注意这个 37 和 4.6 是「平均等待人数」,不是「等待时间」。 真实场景里还要看每次持有 freelist 多久——持有时长不变,分散入口才有意义。

30 秒实验

  1. 把 bits 改成 [0]*12,重跑。第一个空位在 0 号,已用 0 个。 这就是「刚建好的表空间」——位图全空。
  2. 把 bits 改成 [1]*12,重跑。index(0) 会抛 ValueError。 真实 Oracle 里这对应「表空间满了」,报 ORA-01653。 我的模型没处理这个异常,正好说明「错误分支要单独写」。
  3. 把 ('AUTO', 8, 37) 改成 ('AUTO', 1, 37),重跑。两组数字一样了。 这说明真正起作用的不是名字,是「入口有几份」。 ASSM 的本质就是「把入口做多、做到块级」。

常见误区

  1. 新库还用字典管理表空间。 10g 起已废弃, 高并发下 UET$/FET$ 争用会把库拖死。 用本地化管理。
  2. 写 AUTOEXTEND ON 不给 MAXSIZE。 一个失控的库能把数据盘吃光, 连带同机别的服务一起挂。
  3. 建 16K 表空间不先配 db_16k_cache_size。 报 ORA-29339, 而提示说的是「缓存不匹配」,很容易误判。
  4. 以为大文件表空间「更高级」。 它只有一个文件,备份恢复粒度粗,OLTP 库用不上, 别为了好看去用。
自测三问(点开对答案) 1. 本地化管理比字典管理好在哪? —— **区信息记在位图里(数据文件头),不记在 `SYSTEM` 的字典表里。** **分配区不用改字典表,高并发下不会争用 `UET$`/`FET$`。** 2. 为什么建非标准块表空间要先配 `db_16k_cache_size`? —— **因为每种块大小都要有独立的缓冲池。** **不配就没内存放这种块,报 `ORA-29339`。** 3. 大文件表空间为什么能到 32TB? —— **它用 32 位块号(普通文件只有 22 位)。** **2 的 32 次方乘 8K 就是 32TB。** 4. `AUTOEXTEND` 不写 `MAXSIZE` 有什么风险? —— **文件能一直长到磁盘满。** **一个失控的库会把盘吃光,影响同机其他服务。**

下一篇讲维护:改状态、改名、删除、加文件——建完之后天天用的就是这些,每一类都有坑。

相关笔记