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

表空间是逻辑的,数据文件是物理的,一对多。 一个数据文件只属于一个表空间,反过来不成立。 层级四层:表空间 → 段 → 区 → 块。 段可以跨数据文件,区不行——这一条决定了「加文件能扩容,但救不了碎片」。

📝 本章和前面几章的关系

第 7 章讲的是「库的外壳」:控制文件、日志、归档。从这一篇起进到「数据到底存在哪」。 这一篇是第 8 章的地基。 后面五篇——默认表空间、创建、维护、撤销、临时——全是在这四层上做文章。

一、两层皮:逻辑叫表空间,物理叫数据文件

你建表时说「放哪个表空间」,Oracle 落到磁盘上写的是「哪个数据文件」。 你几乎感觉不到这层转换——因为表空间本身不存数据,它只是「一组数据文件的集合名」。

层 是什么 能不能跨
表空间 逻辑容器,一组数据文件 —
段 一个对象占的空间(表、索引) 能跨数据文件
区 段里一次分配的一块连续空间 不能跨
块 最小 I/O 单位,通常 8K —

一个数据文件只能属于一个表空间。 因为文件头部记着「我属于谁」,一个文件跟着两个表空间就说不清了。 所以「把数据文件从一个表空间挪到另一个」这件事不存在—— 能做的是「把表挪过去」:ALTER TABLE t MOVE TABLESPACE new_ts;。 反过来就成立:一个表空间可以有十几个数据文件。 这也是扩容的主要手段之一,尤其当单个文件已经到文件系统上限的时候,只能加文件、不能改大小。

二、四层结构:从表空间一路切到块

建一张表,就是建一个段。 段一开始只分到一个区(8 块),写满了再要下一个区。 区要不到就报 ORA-01653(unable to extend table)—— 这个错的意思是「表空间满了」,不是「表坏了」。 块是最小 I/O 单位,也是最小缓存单位。 你查一行,Oracle 也是把整块读进内存。 所以「行越小、一块装得越多、一次 I/O 拿到的行越多」—— 这也是为什么宽表(几百列)的全表扫描特别慢。 块大小在建库时定(DB_BLOCK_SIZE,一般 8K),之后不能改。 但可以额外建不同块大小的表空间,8.3 节讲那个。

三、算一笔:三张表各要多少块

8K 块减去块头约 100 字节,可用约 8KB。 PCTFREE 默认 10%,再扣掉一成留给更新。 所以「一块能放几行」= 可用字节 ÷ 行长 × 0.9。 这个算式在估算容量时天天要用。 一张一千万行的表要多大?先算每块行数,再乘行数得块数,块数乘 8K 得字节数。 误差主要来自行长估计——VARCHAR2(200) 不代表真占 200 字节,实际长度决定占用。 估容量时按「行数 × 平均行长 × 1.3」粗算比较稳,1.3 里含了 PCTFREE、块头、行头这些开销。 索引另算——索引一般按「数据量的 20% 到 40%」估, 唯一索引和组合索引差得很多,别一概而论。

四、区不能跨文件:碎片是怎么来的

假设表空间有两个数据文件,users01.dbf 剩 3 块、users02.dbf 剩 40 块。 现在要分配一个 8 块的区。 users01.dbf 那 3 块不够,只能从 users02.dbf 拿,于是那 3 块就闲置了。 文件快满的时候,这种零散块会越来越多。 表现是「表空间显示还有空间,但一建表就报 ORA-01653」。 这就是碎片:空间有,但不连续,凑不出一个整区。 治碎片有两个办法: ① ALTER TABLE t MOVE; 把表搬到新位置重排(会重建段,索引要重建); ② 导出再导入(expdp/impdp),效果更彻底。 治完之后表空间可能一下子空出很多。 预防比治疗便宜: 用 UNIFORM SIZE 让每个区一样大,碎片不会那么碎;给表空间留足余量,别卡在 95% 上; 大表单独放一个表空间,别和小表混。

五、动手 Demo:算清一块放几行、一张表要几个区

这一篇没有可执行的 Oracle 命令,全是「算」的活。 而算,沙箱里能做得很实:块 8K、行 100 字节,这些数都是真的。 我按「可用字节 ÷ 行长」算每块行数,再推三张表各需要多少块、多少区, 最后用两个文件剩余的块数,演一遍「区不能跨文件」造成的闲置。

BLK, HDR = 8192, 100
usable = BLK - HDR                      # 先按「满块」算上限,PCTFREE 留到正文说

def need(name, rows, rowlen):
    per = usable // rowlen                 # 一块放几行
    blocks = -(-rows // per)               # 向上取整
    extents = -(-blocks // 8)              # 按每区 8 块估上限
    return name, rows, rowlen, per, blocks, extents

for spec in (('emp', 14, 100), ('big_tab', 10000, 100), ('huge_tab', 1000000, 200)):
    print(need(*spec))

# 一个区不能跨数据文件:文件 A 剩 3 块、文件 B 剩 40 块,要一个 8 块的区
free = {'users01.dbf': 3, 'users02.dbf': 40}
want = 8
for f, n in free.items():
    print(f, '剩余', n, '块 ->', '够' if n >= want else '不够,只能从别的文件拿')

# 所以 A 那 3 块会闲置:看着有空间,用不上
print('闲置块', sum(n for n in free.values() if n < want))

实跑输出:

--------------------------------------------------------------------
第三十三课:表空间与数据文件——逻辑一层、物理一层
--------------------------------------------------------------------

  **Oracle 存数据有两层皮:逻辑上叫表空间,物理上是数据文件。**
  **你建表时说的是「放哪个表空间」,Oracle 落到磁盘上写的是「哪个数据文件」。**
  **层级是四层:表空间 → 段 → 区 → 块。**
  **建一张表,就是建一个段;段由若干区组成;区由若干块组成;块是最小 I/O 单位。**
  **块再往下就是操作系统的块了,Oracle 不管。**

  先算清一块能放多少行
  --------------------------------------------------------------------
  **块大小 8K,减去块头约 100 字节,可用 8092 字节。**
  **行平均 100 字节,一块能放 80 行。**
  **注意这是上限**——**块里还要留 `PCTFREE` 的空位给后续更新**,
  **默认 10%,所以实际每块只放 72 行左右。**
  **`PCTFREE` 是「预留」不是「浪费」**:
  **行变长(比如 `VARCHAR2` 改长了)时,新内容要有地方放**,
  **不然就得「行迁移」,一行的数据被劈到两个块里,读它要跳两次。**

  表格化:三张表各需要多少区
  --------------------------------------------------------------------

  SQL> select seg as 段, rows as 行数, rowlen as 行长, per_block as 每块行数, blocks as 需要块, extents as 需要区 from calc
  段       行数    行长 每块行数 需要块 需要区
  --------------------------------------------
  emp      14      100  80       1      1
  big_tab  10000   100  80       125    16
  huge_tab 1000000 200  40       25000  3125
  已选择 3 行
  **区的大小不是固定的。**  **`AUTOALLOCATE` 时区大小分四档:64KB、1MB、8MB、64MB**——
  **8K 块下就是 8 块、128 块、1024 块、8192 块,按段的增长情况逐级往上跳。**
  **所以上面那个「需要区」是按「每区 8 块」算的上限**,真实值会小不少。
  **`UNIFORM SIZE 1M` 时,每个区都是 1MB(128 块),一刀切。**
  **哪个好?小表多的库用 `AUTOALLOCATE` 省空间,大表为主的库用 `UNIFORM` 好预测。**

  表空间可以跨多个数据文件,但一个区不能跨文件
  --------------------------------------------------------------------
  **假设 `USERS` 表空间有两个数据文件:**
  **`users01.dbf` 只剩 3 个块,`users02.dbf` 剩 40 个块。**
  **现在要给一张表分配一个 8 块的区。**
  **`users01.dbf` 那 3 个块不够,只能从 `users02.dbf` 拿。**
  **于是 `users01.dbf` 那 3 个块就闲置在那儿了。**
  **这就是「区不能跨数据文件」的意思**——  **一个区必须在同一个文件里连续**,
  **所以文件快满的时候会有零散碎片,看着有空间,其实用不上。**
  **段可以跨数据文件,区不行。**  **这一点常被搞混:一张大表的数据可以散在好几个数据文件里**,
  **但每一个区(8 块、64 块)是落在某一个文件里的。**
  **所以「表空间加数据文件」能扩容,但救不了「碎片」**——
  **要治碎片得重建表或者用 `ALTER TABLE ... MOVE`。**

  一个数据文件只属于一个表空间
  --------------------------------------------------------------------

  SQL> select name as 表空间, files as 文件数, bytes_mb as 合计MB from ts
  表空间   文件数 合计MB
  ----------------------
  SYSTEM   1      900
  SYSAUX   1      700
  USERS    2      500
  UNDOTBS1 1      200
  TEMP     1      300
  已选择 5 行
  **反过来不成立:一个表空间可以有多个数据文件,但一个数据文件只能属于一个表空间。**
  **这个方向不能反**——  **因为数据文件头部记着「我属于谁」,一个文件跟着两个表空间就说不清了。**
  **所以「把数据文件从一个表空间挪到另一个」这件事不存在。**
  **能做的是「把表从一个表空间挪到另一个」**:  **`ALTER TABLE t MOVE TABLESPACE new_ts;`**,
  **这一步会重写数据、重建索引,大表要挑窗口做。**

三行输出对应三张表:emp 14 行只要 1 块;big_tab 一万行要 125 块; huge_tab 一百万行(行长 200)要 25000 块。 注意 huge_tab 每块只放 40 行——行长翻倍,每块行数减半,这是线性的。 「需要区」那一列是按「每区 8 块」估的上限。 真实 AUTOALLOCATE 会让区越变越大(64KB → 1MB → 8MB → 64MB),所以实际区数会少很多。 给这个数是让你有个上界概念,不是精确值。 下面那段是两个文件的剩余块数:users01.dbf 剩 3 块,不够 8 块;users02.dbf 剩 40 块,够。 所以那 3 块会一直闲置。 最后一行打印「闲置块 3」——这就是碎片的量化形式。 真实环境里这个数要看 DBA_FREE_SPACE,BLOCK_ID/BLOCKS 那一列会告诉你每段空闲空间在哪、 多大。

30 秒实验

  1. 把 HDR 从 100 改成 500(块头更大),重跑。 每块行数从 80 降到 76,big_tab 的「需要块」从 125 涨到 132、「需要区」从 16 涨到 17。 块头开销是实打实吃掉容量的,这也是宽表比窄表费空间的原因之一。
  2. 把 huge_tab 的行长从 200 改成 100,重跑。 需要块会从 25000 减到 12500——正好一半。行长的线性关系在这里看得很清楚。
  3. 把 free 改成 {'users01.dbf': 7, 'users02.dbf': 40},重跑。 7 块还是不够 8 块,闲置块还是 7(而不是 0)——差一块就是差一块,凑不出区来。

常见误区

  1. 以为「表空间」是一个大文件。 不是。它是逻辑容器,底下可以挂很多数据文件。 问「表空间多大」,答案是一堆文件加起来的总和。
  2. 以为一个数据文件能属于两个表空间。 不能。头部记着归属,只能一对一。 要挪就挪表,别挪文件。
  3. 看到「表空间还有 5% 空闲」就以为还能建表。 可能是碎片:空间有,但不连续,凑不出一个区。 要看 DBA_FREE_SPACE 里最大的那一段有多大。
  4. 只算数据不算索引。 一张表的索引可能占总空间的 30% 以上。估容量时数据和索引要分开算。
自测三问(点开对答案) 1. 表空间和数据文件的对应关系是什么? —— **一对多:一个表空间可以有多个数据文件,但一个数据文件只属于一个表空间。** **所以不能把文件从一个表空间挪到另一个,只能挪表。** 2. 「段可以跨数据文件,区不行」是什么意思? —— **一张表的数据可以散在几个文件里,但每一个区必须在同一个文件内连续。** **这就是文件快满时会出现「有空间但建不了表」的原因。** 3. `ORA-01653` 是什么意思? —— **表空间满了,表扩不出新区域。** **不是表坏了,是要加数据文件或者清理空间。**

下一篇讲两个默认表空间:SYSTEM 和 SYSAUX——它们是 Oracle 自己用的,不能当用户数据的仓库。

相关笔记