LOB段空间“虚高”源于预分配chunk(默认8KB)及版本滞留:BasicFiles无法shrink且分配粗放,SecureFiles需RETENTION策略控制历史版本;真实占用应查V$LOBSTAT和dba_extents而非DBA_SEGMENTS。

LOB段预分配空间导致“虚高”占用
Oracle 19c 中 LOB 段(尤其是 BLOB/CLOB)默认启用 ENABLE STORAGE IN ROW,但一旦数据超过约 3964 字节,就会自动移出行外、存入独立的 LOB segment。此时 Oracle 不是按需分配块,而是按 chunk 大小(默认 CHUNK = 8KB)整块预分配——哪怕只存 1 字节,也先占满一个 chunk。
常见现象:DBA_SEGMENTS.BYTES 显示几百 MB,而 DBMS_LOB.GETLENGTH() 累加总和才几 MB。
-
CHUNK大小不可小于数据库块大小,且一经创建不可修改(建表时定死) - 即使执行
TRUNCATE TABLE,LOB segment 的已分配空间也不会自动收缩,仍保留原 extent 分配 - 使用
ALTER TABLE ... MODIFY LOB (...) (SHRINK SPACE)需要先启用ROW MOVEMENT,且仅对 SecureFiles 有效(BasicFiles 不支持SHRINK)
BasicFiles vs SecureFiles:存储机制差异放大空间错觉
19c 默认仍允许创建 BasicFiles LOB(除非显式指定 SECUREFILE),但它采用老旧的“freelist + lobindex”管理方式,extent 分配更粗放、碎片更难回收;而 SecureFiles 支持压缩、去重、加密,且 SHRINK SPACE 和 RETENTION 控制更精细。
错误倾向:用 CREATE TABLE t(x CLOB) 隐式创建 BasicFiles,后续才发现无法 shrink、无法压缩。
- 确认类型:查
DBA_LOBS.SEGMENT_NAME对应的DBA_SEGMENTS.SEGMENT_TYPE,或直接看DBA_LOBS.SECUREFILE列值是否为YES - 迁移建议:BasicFiles → SecureFiles 需
ALTER TABLE ... MOVE LOB(...) STORE AS SECUREFILE,该操作会重建 LOB segment,释放闲置空间(但需业务停写或在线重定义) - 新建表务必显式写
LOB (col) STORE AS SECUREFILE (COMPRESS MEDIUM DEDUPLICATE),避免默认陷阱
未清理的旧版本(UNDO/VERSIONING)持续占空间
SecureFiles LOB 启用 RETENTION(默认 GUARANTEED)后,每次更新不会立即覆盖旧 chunk,而是保留旧版本供一致性读——这些“历史 chunk”仍计入 segment 总大小,直到被后台清理(_securefile_cleanup_interval 默认 300 秒)或触发显式清理。
典型症状:频繁 UPDATE 同一 LOB 字段后,DBA_SEGMENTS.BYTES 持续上涨,但 DBMS_LOB.GETLENGTH() 几乎不变。
- 临时缓解:执行
ALTER SYSTEM FLUSH SHARED_POOL并等待清理间隔,或手动调用DBMS_LOB.CLEANUP(lob_loc)(仅限部分场景) - 长期控制:建表时设
RETENTION NONE(放弃多版本一致性保障),或改用RETENTION AUTO让系统按 UNDO 表空间压力自动裁剪 - 注意:
RETENTION NONE下闪回查询(AS OF TIMESTAMP)可能无法读取 LOB 历史值
DBA_SEGMENTS 统计滞后导致误判
DBA_SEGMENTS.BYTES 来自字典缓存,不实时反映物理块使用情况;尤其在大量 INSERT/DELETE/UPDATE LOB 后,统计可能严重过期。直接看它判断“浪费”,容易误伤。
真实空间水位要看 DBA_LOBS.CHUNK × DBA_LOBS.CACHE × 实际分配 chunk 数,再结合 V$LOBSTAT(19c 新增)中的 USED_CHUNKS 和 EXPIRED_CHUNKS。
- 刷新统计:运行
EXEC DBMS_SPACE.OBJECT_SPACE_USAGE('OWNER','TABLE_NAME','TABLE',NULL,NULL,NULL)或全量收集DBMS_STATS.GATHER_TABLE_STATS(含CASCADE => TRUE) - 查真实占用:
SELECT SUM(bytes) FROM dba_extents WHERE segment_name IN (SELECT segment_name FROM dba_lobs WHERE table_name='T')—— 这才是 OS 层实际分配的磁盘空间 - 别依赖
DBA_FREE_SPACE估算 LOB 可用空间:LOB 使用自己的 freelist 或 bitmap,与表空间级空闲区无关
真正棘手的是 BasicFiles 的不可逆膨胀和 SecureFiles 的版本滞留——前者得靠重建,后者得靠 RETENTION 策略权衡。不查 DBA_LOBS.SECUREFILE 和 V$LOBSTAT 就动手 shrink 或 purge,大概率白忙活。


















