判断高水位浪费是否显著需先更新统计信息,再比较BLOCKS与NUM_ROWS×AVG_ROW_LEN的偏差;若偏差超100MB或UNFORMATTED_MB超50MB,且存在大量DELETE、APPEND插入或行迁移,则表明空间浪费严重并影响性能。

查 dba_tables 判断高水位浪费空间是否显著
高水位整理不是“一有碎片就做”,而是看真实浪费是否影响性能或存储成本。关键指标是:高水位空间(BLOCKS)远大于实际数据占用(NUM_ROWS × AVG_ROW_LEN)。必须先收集统计信息,否则 NUM_ROWS 和 AVG_ROW_LEN 为 NULL 或过期值,计算完全失真:
EXEC DBMS_STATS.GATHER_TABLE_STATS(ownname => 'SCHEMA_NAME', tabname => 'TABLE_NAME');- 再执行查询,例如筛选“浪费空间 > 100MB”的表:
SELECT table_name, ROUND(blocks * 8 / 1024, 2) AS hwm_gb, ROUND(num_rows * avg_row_len / 1024 / 1024 / 0.9, 2) AS used_mb, ROUND((blocks * 8 / 1024 - num_rows * avg_row_len / 1024 / 1024 / 0.9), 2) AS waste_gb FROM dba_tables WHERE owner = 'SCHEMA_NAME' AND blocks > 0 AND num_rows > 0 AND (blocks * 8 / 1024 - num_rows * avg_row_len / 1024 / 1024 / 0.9) > 100 ORDER BY waste_gb DESC;
注意:0.9 是估算压缩/填充因子的保守系数;若表含 LOB 或 LONG 字段,该公式会低估实际使用空间,需单独查 dba_lobs。
用 dba_segments 对比物理分配与逻辑使用
dba_tables.blocks 只反映段内已格式化块数,而 dba_segments.bytes 是实际分配的磁盘空间(含未格式化但已分配的区)。两者偏差大,说明存在 ASSM 下的 LHWM/HWM 分离问题——即 HWM 很高,但 LHWM 很低,大量块已分配却未格式化,全表扫描仍要跳过它们,但空间无法被其他对象复用。
- 运行对比查询:
SELECT t.table_name, s.bytes / 1024 / 1024 AS seg_mb, t.blocks * 8 / 1024 AS hwm_mb, (s.bytes / 1024 / 1024 - t.blocks * 8 / 1024) AS unformatted_mb FROM dba_tables t JOIN dba_segments s ON t.owner = s.owner AND t.table_name = s.segment_name WHERE t.owner = 'SCHEMA_NAME' AND s.segment_type = 'TABLE' AND s.bytes > 1024 * 1024 * 100 -- 大于100MB才纳入评估 AND (s.bytes / 1024 / 1024 - t.blocks * 8 / 1024) > 50; -- 未格式化空间超50MB - 如果
unformatted_mb显著(比如 >50MB),且该表近期无大插入,大概率是 ASSM 段因频繁小量插入导致“稀疏分配”,此时SHRINK SPACE效果有限,MOVE更彻底。
结合业务操作历史识别高风险段
高水位膨胀往往不是自然增长,而是特定操作模式触发的。以下场景的表应优先检查:
- 执行过大量
DELETE但没TRUNCATE或SHRINK—— 尤其是日志类、临时中间表; - 用
INSERT /*+ APPEND */批量加载后又删掉部分数据 ——APPEND绕过 freelist,直接推高 HWM; - 用
SQL*Loader加载时带TRUNCATE但未加REUSE STORAGE—— 实际上保留了原 HWM; - 表上有大量
UPDATE导致行迁移(CHAIN_CNT > 0且AVG_ROW_LEN明显增长)—— 迁移块会卡在 HWM 下无法回收。
可快速筛查:
SELECT table_name, chain_cnt, num_rows, avg_row_len FROM dba_tables WHERE owner = 'SCHEMA_NAME' AND chain_cnt > num_rows * 0.01 -- 迁移行超1% AND avg_row_len > 1.5 * (SELECT AVG(avg_row_len) FROM dba_tables WHERE owner = 'SCHEMA_NAME');
SHRINK 前必须确认的两个前提条件
ALTER TABLE ... SHRINK SPACE 看似简单,但失败率高,主因是忽略前提:
- 表所在表空间必须为
AUTOALLOCATE或UNIFORM且启用了自动段空间管理(ASSM)—— 查dba_tablespaces.segment_space_management = 'AUTO'; - 必须先执行
ALTER TABLE table_name ENABLE ROW MOVEMENT;,否则报错ORA-10636: row movement is not enabled; - 不能在有未提交事务、全局临时表、IOT 的主键表、或含
OBJECT TYPE列的表上执行; - 在线收缩会生成大量 UNDO 和 redo,生产环境建议在低峰期做,并监控
v$session_longops中的进度。
真正容易被忽略的是:如果表上有函数索引、域索引或位图连接索引,SHRINK 可能 silently 失效或导致索引失效,必须收缩后手动验证 dba_indexes.status 是否全为 VALID。


















