表空间是逻辑的,数据文件是物理的,一对多。 一个数据文件只属于一个表空间,反过来不成立。 层级四层:表空间 → 段 → 区 → 块。 段可以跨数据文件,区不行——这一条决定了「加文件能扩容,但救不了碎片」。
第 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 秒实验
- 把
HDR从 100 改成 500(块头更大),重跑。 每块行数从 80 降到 76,big_tab的「需要块」从 125 涨到 132、「需要区」从 16 涨到 17。 块头开销是实打实吃掉容量的,这也是宽表比窄表费空间的原因之一。 - 把
huge_tab的行长从 200 改成 100,重跑。 需要块会从 25000 减到 12500——正好一半。行长的线性关系在这里看得很清楚。 - 把
free改成{'users01.dbf': 7, 'users02.dbf': 40},重跑。 7 块还是不够 8 块,闲置块还是 7(而不是 0)——差一块就是差一块,凑不出区来。
常见误区
- 以为「表空间」是一个大文件。 不是。它是逻辑容器,底下可以挂很多数据文件。 问「表空间多大」,答案是一堆文件加起来的总和。
- 以为一个数据文件能属于两个表空间。 不能。头部记着归属,只能一对一。 要挪就挪表,别挪文件。
- 看到「表空间还有 5% 空闲」就以为还能建表。
可能是碎片:空间有,但不连续,凑不出一个区。
要看
DBA_FREE_SPACE里最大的那一段有多大。 - 只算数据不算索引。 一张表的索引可能占总空间的 30% 以上。估容量时数据和索引要分开算。
自测三问(点开对答案)
1. 表空间和数据文件的对应关系是什么? —— **一对多:一个表空间可以有多个数据文件,但一个数据文件只属于一个表空间。** **所以不能把文件从一个表空间挪到另一个,只能挪表。** 2. 「段可以跨数据文件,区不行」是什么意思? —— **一张表的数据可以散在几个文件里,但每一个区必须在同一个文件内连续。** **这就是文件快满时会出现「有空间但建不了表」的原因。** 3. `ORA-01653` 是什么意思? —— **表空间满了,表扩不出新区域。** **不是表坏了,是要加数据文件或者清理空间。**下一篇讲两个默认表空间:SYSTEM 和 SYSAUX——它们是 Oracle 自己用的,不能当用户数据的仓库。
- ☐ (本篇完,下一篇 8.2 Oracle 的默认表空间)
相关笔记
- 创建表空间
- 逻辑存储结构
- 管理控制文件
-
- 🐬 MySQL必知必会/00-索引
- 🏺 Oracle从入门到精通 · 索引