全表扫描变慢主因常是HWM虚高:删大量数据后表空间未释放,导致BLOCKS远大于实际行数换算的块数;确认后可用SHRINK SPACE(需满足ASSM、启用ROW MOVEMENT等条件)或TRUNCATE(仅适用全清空场景)降低HWM。

为什么全表扫描变慢?先确认是不是HWM问题
不是所有慢查询都怪HWM,但如果你删过大量数据、表实际行数很少、却占着几GB空间,那八成是它。最直接的判断方式是比对 BLOCKS 和 NUM_ROWS:
- 查
dba_tables或user_tables:如果BLOCKS远大于NUM_ROWS * AVG_ROW_LEN / 8192(换算成块数),说明有大量空块 - 执行
SELECT COUNT(DISTINCT DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)) FROM table_name,结果远小于BLOCKS,就坐实了HWM虚高 - 别跳过统计信息收集:用
DBMS_STATS.GATHER_TABLE_STATS更新后再查,否则BLOCKS值可能不准
ALTER TABLE SHRINK SPACE 是首选,但必须满足三个前提
这是10g以后最常用、最安全的在线方案,但它不是“一敲就灵”,漏掉任一条件都会报错或无效:
- 表空间必须是本地管理(
Locally Managed)且启用了自动段空间管理(ASSM)——查dba_tablespaces的SEGMENT_SPACE_MANAGEMENT列,值必须是AUTO - 必须提前启用行移动:
ALTER TABLE your_table_name ENABLE ROW MOVEMENT;没这句,SHRINK直接报 ORA-10636 - 表不能有基于函数的索引、域索引、物化视图日志等特殊对象;如果有,得先临时禁用或重建
推荐分两步执行:ALTER TABLE t SHRINK SPACE COMPACT(只整理不释放,业务影响最小),再 ALTER TABLE t SHRINK SPACE(真正降低HWM并归还空间)。
MOVE 和 SHRINK 的核心区别:索引要不要重建
很多人以为 MOVE 更“彻底”,其实它代价更高、风险更大:
-
ALTER TABLE t MOVE后,所有普通索引全部失效(STATUS = UNUSABLE),必须手动执行ALTER INDEX i REBUILD,否则后续查询走不了索引 -
SHRINK SPACE不改变索引结构,索引保持VALID状态,无需额外操作(但建议仍跑一次DBMS_STATS.GATHER_INDEX_STATS) -
MOVE是离线操作,表锁整个过程;SHRINK支持在线,仅在 compact 阶段短暂加锁,适合生产环境 - 两者都会产生大量 redo/undo,尤其大表,务必避开业务高峰,并确认归档空间充足
TRUNCATE 能一步到位,但只适用于“真·清空”场景
如果表数据确实要全删,别用 DELETE,直接 TRUNCATE TABLE t:
- 它会重置 HWM 到初始位置,空间立即释放,全表扫描瞬间变快
- 不走回滚段,无 undo,速度快、开销小
- 但它是 DDL,隐式提交,不可回滚;且会清空所有数据,无法保留部分记录
- 如果只是删掉历史分区(比如按月分区),优先用
ALTER TABLE t DROP PARTITION,它也会重置该分区的 HWM
HWM 本质是物理存储的“历史刻度”,不是逻辑数据的代理。降低它不靠删数据,而靠重构段结构——所以任何方案都绕不开空间重分配。最容易被忽略的是:收缩后不更新统计信息,优化器仍按旧 BLOCKS 估算成本,执行计划可能依旧不准。


















