LOB段收缩失败主因是表存在函数索引、位图索引、域索引或未启用ROW MOVEMENT;SHRINK SPACE仅整理LOB段,CASCADE才同步收缩索引段;ASSM下需满足连续空闲extents≥5才归还空间。
LOB段收缩失败报ORA-10631:为什么SHRINK SPACE不生效
直接原因:表上存在基于函数的索引、位图索引、域索引,或启用了启用行移动(enable row movement)但未开启。oracle在执行alter table ... modify lob (shrink space)前会校验约束条件,任一不满足就抛ora-10631: shrink clause is not allowed for this object。
- 必须先执行
ALTER TABLE table_name ENABLE ROW MOVEMENT(即使表没被更新过) - 检查是否存在
FUNCTION-BASED INDEX:SELECT index_name, index_type FROM user_indexes WHERE table_name = 'YOUR_TABLE' AND index_type LIKE '%FUNCTION-BASED%' - 位图索引(
BITMAP)和域索引(DOMAIN)同样禁止SHRINK,需临时删除再重建 - 分区LOB表需对每个分区单独操作,不能只对主表发SHRINK命令
SHRINK SPACE和SHRINK SPACE CASCADE的区别
前者只收缩LOB段本身,后者还会级联收缩LOB索引段(如SYS_IL0000xxxx$$)。不加CASCADE时,虽然LOB数据页被压缩,但索引段仍保留旧指针,后续插入可能触发额外分裂,空间回收不彻底。
-
SHRINK SPACE:仅重排LOB段中的chunk,不碰索引;适合只读场景或想最小化锁时间 -
SHRINK SPACE CASCADE:同时整理LOB段+对应索引段;实际生产中应默认用这个 - 注意:CASCADE会持有更长时间的
SS(subshare)锁,高并发DML期间慎用 - 执行后可用
SELECT segment_name, bytes/1024/1024 AS mb FROM user_segments WHERE segment_name LIKE 'SYS_IL%' AND segment_name IN (SELECT segment_name FROM user_lobs WHERE table_name = 'YOUR_TABLE')验证索引段是否变小
收缩后空间没释放?检查高水位线和ASSM自动段管理
SHRINK操作只是把数据块往段头部挪,并不会自动把空闲extents归还给表空间——除非启用了自动段空间管理(ASSM)且满足“连续空闲extent ≥ 5个”的合并阈值。常见现象是DBA看到dba_segments.bytes没变,误以为没成功。
- 确认表空间是否为ASSM:
SELECT segment_space_management FROM dba_tablespaces WHERE tablespace_name = (SELECT tablespace_name FROM user_tables WHERE table_name = 'YOUR_TABLE') - 手动触发extents回收(需等待):
ALTER TABLE table_name DEALLOCATE UNUSED KEEP 0(慎用,可能引发延迟写) - 更稳妥做法:收缩后立即收集统计信息,避免优化器误判块数:
EXEC DBMS_STATS.GATHER_TABLE_STATS(user, 'YOUR_TABLE', cascade => true) - 如果LOB列定义为
DISABLE STORAGE IN ROW,收缩效果更明显;若为默认ENABLE STORAGE IN ROW(≤4000字节存行内),SHRINK主要影响溢出段(CHUNK存储区)
在线收缩的锁行为和业务影响
SHRINK不是完全无锁操作。它会在开始和结束阶段获取TM(DML队列锁),中间阶段以短事务形式逐块处理,但整个过程仍可能阻塞DDL(如ADD COLUMN)和某些ALTER INDEX操作。
- 高峰期避免执行:即使加了
WAIT子句,超时后仍会报ORA-00054: resource busy - 监控锁等待:
SELECT blocking_session, event, seconds_in_wait FROM v$session WHERE event LIKE 'enq: TX%' - LOB段越大,SHRINK耗时越长——10GB LOB可能持续数分钟,期间
v$session_longops里能看到shrink table条目 - 如果LOB列有大量
NULL值,SHRINK几乎不生效(Oracle不为NULL分配chunk),先用UPDATE ... SET lob_col = EMPTY_BLOB() WHERE lob_col IS NULL转成空LOB再收缩
v$session_longops看进度,再查user_segments确认bytes下降,最后用DBMS_SPACE.SPACE_USAGE验证块内真实利用率——漏掉任何一环,都可能白忙活。

















