DBA_FREE_SPACE的bytes总和小于表空间空闲总量,因其仅记录连续空闲EXTENT,非空闲字节池快照;物理不相邻即拆分为多条记录,SUM(bytes)反映连续段数量而非碎片程度。
DBA_FREE_SPACE 的 bytes 总和为什么远小于表空间空闲总量?
它根本就不是“空闲字节池”的快照,而是连续空闲 extent 的登记簿。只要两块空闲空间在物理上不相邻(block_id 不连续),哪怕只隔一个已用块,就会拆成两条记录。所以 sum(bytes) 看起来小,不是数据错了,是你误把“连续段数量”当成了“碎片程度”。真正该盯的是这些记录的 blocks 分布——比如大量 blocks = 1 或 blocks = 8(对应 64kb)的行,说明系统频繁分配又释放小块,而大对象扩容时可能直接卡住。
怎么用 DBA_FREE_SPACE 判断是否真影响业务?
碎片率数字本身没用,关键看能不能满足下一次 EXTENT 申请。实操分三步:
- 查当前主流对象的
NEXT_EXTENT:运行SELECT DISTINCT NEXT_EXTENT FROM DBA_SEGMENTS WHERE TABLESPACE_NAME = 'USERS',重点关注出现频次高的值(如 1048576 字节即 1MB) - 反查空闲块能否支撑:执行
SELECT COUNT(*) FROM DBA_FREE_SPACE WHERE TABLESPACE_NAME = 'USERS' AND BYTES >= 1048576,如果返回 0,说明哪怕总空闲有 5GB,也分配不出下一个 1MB 的 extent - 排除 ASSM 干扰:如果是
EXTENT MANAGEMENT LOCAL AUTOALLOCATE,小碎片天然多,BYTES 的记录占多数属正常,别一看到 count > 100 就 panic
如何识别真实连续空闲段,而不是被 BLOCK_ID 欺骗?
BLOCK_ID 和 BLOCKS 才是物理连续性的唯一依据,BYTES 是换算值,受 DB_BLOCK_SIZE 影响还可能四舍五入失真。例如:
BLOCK_ID = 1000, BLOCKS = 8 → 占用 1000~1007<br>BLOCK_ID = 1008, BLOCKS = 16 → 实际是同一连续段(1000~1023)
要合并这类记录,得用自连接或窗口函数:
- 先查出
DB_BLOCK_SIZE:SELECT VALUE FROM V$PARAMETER WHERE NAME = 'db_block_size' - 写查询找相邻段:
SELECT t1.BLOCK_ID, t1.BLOCKS, t2.BLOCKS FROM DBA_FREE_SPACE t1 JOIN DBA_FREE_SPACE t2 ON t1.TABLESPACE_NAME = t2.TABLESPACE_NAME AND t1.FILE_ID = t2.FILE_ID AND t1.BLOCK_ID + t1.BLOCKS = t2.BLOCK_ID - 过滤掉文件头干扰:
WHERE t1.BLOCK_ID > 10(跳过前几个保留块)
coalesce 能不能一键解决碎片?
只能合并**同一数据文件内**的相邻空闲 extent,对跨文件碎片完全无效。而且它只作用于 DICTIONARY 管理的表空间;对 AUTOALLOCATE 表空间,ALTER TABLESPACE ... COALESCE 命令会静默失败,不报错也不起作用。更现实的做法是:
- 确认表空间类型:
SELECT EXTENT_MANAGEMENT FROM DBA_TABLESPACES WHERE TABLESPACE_NAME = 'USERS' - 若为
LOCAL,优先考虑ALTER TABLE ... MOVE或ALTER INDEX ... REBUILD这类段级重组 - 若为
DICTIONARY且碎片记录数 > 500,再跑ALTER TABLESPACE users COALESCE - 永远别在没备份控制文件前执行 coalesce —— 它虽不改数据,但会更新数据字典,出事没法回退
最常被忽略的一点:DBA_FREE_SPACE 不包含 TEMP 和 UNDO 表空间,也完全不反映 ASSM 下 bitmap 的实际管理粒度。盯着它看碎片,就像只用温度计判断发动机是否过热——指标相关,但不能代替压力、转速、油压的综合判断。











