碎片整理应在IO性能下降、空间浪费明显且AWR确认读多写少时才执行;怀疑表空间碎片需看AWR中Physical Reads/Logical Reads>0.15、db file sequential read平均等待>15ms,并排除归档、备份等干扰;高碎片表需满足fragmentation_ratio>0.25且表大小>100MB,再执行SHRINK SPACE COMPACT或SHRINK SPACE(需先ENABLE ROW MOVEMENT),并重建索引;MOVE适用于可停业务大表,SHRINK适用于ASSM表空间OLTP环境;LOB字段需加CASCADE;根本解决需优化数据生命周期设计。
碎片整理不是定期执行的例行任务,而是当io性能下降、空间浪费明显、且awr确认存在读多写少特征时才值得投入。
什么时候该怀疑表空间有碎片?看AWR里的Physical Reads和db file sequential read
碎片最直接的表现是“物理读变多但逻辑读没涨”,这说明数据块在磁盘上离散分布,一次查询要跳着读多个不连续的块。
在AWR报告的 Top 5 Timed Events 中重点关注:
- 如果
db file sequential read排名靠前,且平均等待时间Av Rd(ms)> 15ms,同时Physical Reads/Logical Reads比值 > 0.15(即每100次逻辑读伴随15次以上物理读),就要查具体表是否碎片化 - 对比正常时段报告:若问题时段该比值翻倍,而SQL执行计划未变、缓存命中率(
Buffer Hit %)下降超5%,基本可锁定IO层碎片影响 - 注意排除误判:归档日志写、RMAN备份、大表全扫描也会拉高
Physical Reads,需结合SQL Statistics中SQL ordered by Physical Reads确认是否集中在业务表
怎么用SQL快速定位高碎片表?别只看user_extents
仅查 user_extents 容易漏掉行迁移、链式行等隐性碎片。必须结合统计信息算出实际空间利用率。
执行前先收集统计:
EXEC DBMS_STATS.GATHER_TABLE_STATS(ownname => 'SCOTT', tabname => 'ORDERS');
再运行评估SQL(注意替换表名):
SELECT
t.table_name,
ROUND((t.avg_row_len * t.num_rows) / (1 - t.pct_free/100) / 8192, 0) actual_blocks,
a.alloc_block,
ROUND(1 - ((t.avg_row_len * t.num_rows) / (1 - t.pct_free/100) / 8192) / a.alloc_block, 2) fragmentation_ratio,
'ALTER TABLE ' || t.table_name || ' ENABLE ROW MOVEMENT;' enable_sql,
'ALTER TABLE ' || t.table_name || ' SHRINK SPACE COMPACT;' shrink_sql
FROM user_tables t,
(SELECT segment_name, SUM(blocks) alloc_block FROM user_extents GROUP BY segment_name) a
WHERE t.table_name = a.segment_name
AND t.table_name = 'ORDERS';
- 当
fragmentation_ratio> 0.25(即25%)且表大小 > 100MB,SHRINK SPACE才有意义 -
SHRINK SPACE COMPACT不释放空间,但整理行位置、减少链式行;SHRINK SPACE(无COMPACT)会释放空间,但要求表启用了ROW MOVEMENT - 索引重建不能省:
ALTER INDEX idx_name REBUILD ONLINE必须跟在SHRINK后,否则索引仍指向旧块地址
MOVE vs SHRINK:选错命令会让IO雪上加霜
ALTER TABLE ... MOVE 和 SHRINK SPACE 行为完全不同,误用会导致更严重的IO压力。
-
MOVE是完全复制数据到新段,全程产生大量redo和临时空间,且期间表锁不可写;适合已禁用约束、可停业务的大表 -
SHRINK SPACE是原地整理,依赖ROW MOVEMENT和高水位线(HWM)下调,不生成redo(除非带UPDATE INDEXes),但要求表在自动段管理(ASSM)表空间中 - 千万避免在OLTP高峰期跑
MOVE:它会触发大量db file scattered read(多块读),直接拖垮IO队列 - 如果表含LOB字段,
SHRINK默认不处理LOB段,得额外加SHRINK SPACE CASCADE,否则碎片仍在
真正麻烦的不是操作本身,而是碎片背后的数据生命周期问题——比如日志表不做分区归档、历史数据不清理、频繁UPDATE导致行迁移。AWR能告诉你“读变慢了”,但解决它得回到应用层设计。











