在Oracle数据库中,表数据块的使用由高水位线管理。执行DELETE操作只会标记数据为已删除,并释放块内的空间,但不会降低高水位线。当高水位线远大于实际数据所需的块数时,全表扫描会读取大量空块,导致I/O浪费。段收缩(Segment Shrink)正是为了解决这类问题而设计的在线空间回收机制。它通过将高水位线附近的数据行移动到低水位区域中的空闲空间,减少段占用的数据块数量,最后调整高水位线并释放未使用的空闲区。不过,并不是所有表都能直接执行SHRINK SPACE,操作前需要核对多个使用条件。

一、段收缩的核心机制与基础使用条件
段收缩由两个阶段组成。第一个阶段是压缩,Oracle从高水位线附近扫描数据块,把仍在使用的行移动到段内部较低位置的空闲空间中。由于行的物理位置发生了变化,行的ROWID也会随之改变。第二个阶段是降低高水位线,将已经腾空的尾部数据块释放回表空间。正是由于行会移动,Oracle要求在执行段收缩前必须启用行移动(ROW MOVEMENT)。如果未启用行移动,命令会直接报错ORA-10631: SHRINK clause should not be specified for this object.
启用行移动的操作十分简单,通过ALTER TABLE ... ENABLE ROW MOVEMENT完成。但需要注意,开启行移动本身是DDL操作,会短暂获取表锁,并且一旦开启后,任何导致行移动的操作(如Flashback Table、Segment Shrink)都可能改变行的ROWID。如果应用程序依赖ROWID进行定位,需要提前评估影响。
另一个基础条件是表空间必须使用自动段空间管理(ASSM)。ASSM通过位图来跟踪段内空闲块,段收缩依赖位图块中的空间信息来确定哪些块可以被释放。如果表所在表空间是手动段空间管理(MSSM)或字典管理,则段收缩不可用。可以通过DBA_TABLESPACES视图的SEGMENT_SPACE_MANAGEMENT列来确认表空间类型。
此外,段收缩对数据库兼容性有要求,该功能从Oracle 10g开始引入,如果数据库的兼容参数设置低于10.0,则无法使用。不过目前大部分生产库都已经在11g以上,这一限制影响不大。
二、执行SHRINK SPACE前的限制清单与检查方法
在打开数据库后直接执行SHRINK SPACE之前,DBA需要检查表上是否包含一系列禁止收缩的对象。如果表上存在位图索引,段收缩会失败。位图索引维护的是每个数据块中多个行的位图信息,行移动会破坏位图与数据块之间的对应关系,导致索引失效。因此,对于数据仓库中大量使用位图索引的事实表,通常不能直接选择段收缩,而应改用其他重组方式。
同样,函数索引也会导致段收缩不可用。函数索引存储的是列表达式计算结果与ROWID的映射,当行移动后ROWID变化,函数索引需要重新计算并更新,但Oracle在段收缩期间不会自动维护函数索引,因此存在函数索引的表无法执行SHRINK SPACE。位图连接索引、域索引(如Oracle Text索引、空间索引)也属于不支持的对象类型。
另一个容易被忽略的就是物化视图相关对象。如果表上定义了物化视图日志,执行段收缩可能造成日志中的ROWID与物理行不一致,因此Oracle直接禁止对带有物化视图日志的表进行段收缩。同样,基于该表创建的ROWID物化视图在行移动后也会失效,需要在操作前删除或刷新这些物化视图。可以通过DBA_MVIEW_LOGS和DBA_MVIEWS视图进行排查。
下面给出一组常用的检查脚本,可以在执行段收缩前运行,以确定表是否满足基本条件。
-- 1. 检查表空间是否为ASSM
SELECT tablespace_name, segment_space_management
FROM dba_tablespaces
WHERE tablespace_name =
(SELECT tablespace_name FROM dba_segments WHERE segment_name = 'EMP' AND ROWNUM = 1);
-- 2. 检查表上是否有位图索引、函数索引或域索引
SELECT index_name, index_type
FROM dba_indexes
WHERE table_name = 'EMP'
AND index_type IN ('BITMAP', 'FUNCTION-BASED NORMAL', 'FUNCTION-BASED BITMAP', 'DOMAIN');
-- 3. 检查表上是否有物化视图日志
SELECT log_table
FROM dba_mview_logs
WHERE master = 'EMP';
-- 4. 检查基于该表的ROWID物化视图
SELECT mview_name
FROM dba_mviews
WHERE query LIKE '%EMP%'
AND refresh_method = 'PRIMARY KEY'; -- 视具体环境调整
上述SQL中的表名EMP需要根据实际情况替换。如果检查发现表上有位图索引或函数索引,可以先将索引删除,待段收缩完成后再重建索引。但删除索引会影响相关查询性能,并且重建索引需要额外时间,因此一定要在维护窗口内操作。
三、段收缩的操作步骤、锁与注意事项
在确认表满足条件后,可以按步骤执行段收缩。第一步是启用行移动,第二步执行SHRINK SPACE命令。如果不确定是否要立即降低高水位线,可以先使用COMPACT参数,该参数只压缩段内的碎片,不降低高水位线,这样可以缩短锁表时间,后续再单独执行一次不带COMPACT的SHRINK SPACE来降低高水位线。这种分阶段方式特别适合数据变化频繁、维护窗口有限的场景。
下面是一个完整的操作示例,假设表名为SCOTT.EMP。
-- 启用行移动
ALTER TABLE scott.emp ENABLE ROW MOVEMENT;
-- 先压缩碎片,不降低高水位线
ALTER TABLE scott.emp SHRINK SPACE COMPACT;
-- 正式收缩并同时收缩表上的所有索引
ALTER TABLE scott.emp SHRINK SPACE CASCADE;
-- 重新收集统计信息
BEGIN
DBMS_STATS.GATHER_TABLE_STATS('SCOTT', 'EMP', CASCADE => TRUE);
END;
执行SHRINK SPACE会在表上持有排他锁,任何并行的DML操作都会被阻塞。在收缩期间,SELECT查询通常可以继续执行,但当高水位线实际调整的瞬间,正在进行的全表扫描可能受到影响。因此,生产环境中应选择业务低峰期执行,并提前评估表大小和碎片程度。如果表非常大,可以结合PARALLEL子句并行收缩,但并行会占用更多系统资源,需要权衡。
段收缩完成后,高水位线会下降,但索引和统计信息需要及时更新。虽然CASCADE参数会同步收缩索引,但索引的统计信息可能已经过时,建议执行完收缩后收集表及索引的统计信息。另外,段收缩只回收高水位线以下未被使用的空间,如果表空间的空闲空间碎片化严重,释放的空间可能无法立即被其他对象使用,此时可以结合表空间级别的合并操作。
四、段收缩与MOVE、在线重定义的对比及选型建议
面对高水位线过高或表碎片严重的情况,除了段收缩之外,DBA还可以选择ALTER TABLE ... MOVE、数据泵导出导入或在线重定义。不同方案在锁表时间、操作复杂度、索引维护和空间需求上差异明显。段收缩的最大优势是在线执行,不需要额外的表空间来存放临时数据,并且通过CASCADE可以自动维护索引,因此在满足使用条件时优先推荐。
相比之下,ALTER TABLE ... MOVE TABLESPACE需要将整个表数据移动到新的段中,操作期间表完全不可用,并且移动之后所有索引都会失效,必须逐一重建。虽然MOVE操作能彻底压缩表并降低高水位线,但停机时间长,在7x24小时业务系统中往往难以接受。导出导入方式虽然也能达到同样效果,但操作步骤更多,风险管理复杂,一般只在跨平台迁移时才考虑。
在线重定义是一种功能强大的重组手段,它可以在表保持可用的前提下重新创建表结构、移动数据并同步增量变更。但与段收缩相比,在线重定义需要额外的存储空间来保存临时表,而且操作过程需要较多的步骤和监控。只有当事务表不满足段收缩的使用条件(例如存在位图索引、函数索引或物化视图日志)时,才建议将在线重定义作为替代方案。
综合来看,段收缩适合那些满足ASSM、行移动和对象限制条件,同时仅由DELETE或UPDATE造成高水位线偏高、碎片集中在高水位线附近的堆表。对于存在大量位图索引的数据仓库表、包含LONG列的历史表或无法接受锁表时间的核心交易表,应优先评估MOVE或在线重定义方案。无论选择哪种方式,都应在操作前备份对象结构,并在测试环境验证整个流程。

