不能直接用 rownum,因为 rownum 在 order by 之前分配,未聚合排序时截取的前10行是随机零碎段(如单分区、lob子段、小索引),无法反映对象真实总大小,会遗漏真正占用空间的业务表。

直接查 DBA_SEGMENTS 并按 SUM(bytes) 聚合排序,是定位最大段对象唯一可靠的方式。用 ROWNUM 或裸查不聚合的结果,基本没参考价值。
为什么不能直接 SELECT * FROM DBA_SEGMENTS WHERE ROWNUM
因为 DBA_SEGMENTS 是按物理段(segment)记录的,不是按逻辑表。一张分区表有 50 个分区,就占 50 行;一个带 3 个 CLOB 字段的表,会额外生成 3 个 LOBSEGMENT 行;索引、LOB 索引也都独立成行。不先 GROUP BY owner, segment_name, segment_type 再 SUM(bytes),前 10 行大概率是零碎小分区或系统索引,真正吃空间的业务表根本不会上榜。
更关键的是:ROWNUM 在 ORDER BY 之前执行,未排序取前 10 行毫无意义。
- 必须用
FETCH FIRST 10 ROWS ONLY(Oracle 12c+),语义明确且避免子查询别名问题 - 聚合粒度要适中:只按
owner + segment_name会把 TABLE 和 INDEX 合并,掩盖索引膨胀;加partition_name又太碎,失去表级视角 -
segment_type必须保留——一眼就能区分出LOBSEGMENT这类高危对象
怎么写聚合查询才准
以下语句是实操中验证过最稳的起点:
SELECT owner, segment_name, segment_type, ROUND(SUM(bytes)/1024/1024) AS size_mb FROM dba_segments GROUP BY owner, segment_name, segment_type ORDER BY SUM(bytes) DESC FETCH FIRST 10 ROWS ONLY;
注意几个实操细节:
-
bytes是原始字节数,直接参与SUM和ORDER BY,不要先除以 1024 再求和,否则精度丢失 - 单位换算用浮点除法:
/ 1024 / 1024比/ 1048576更安全,避免整数截断 - 若目标表空间明确(如
'USERS'),在WHERE中加过滤,减少扫描量 - 普通用户查不到
DBA_SEGMENTS,需确认已授予SELECT_CATALOG_ROLE;RAC 环境下默认只返回当前实例数据
怎么知道“这张表”到底占多大
segment_name 不等于业务表名。LOB 段名类似 SYS_LOB0000012345C00002$$,索引段名是 MYTABLE_IDX。想算清一张业务表的真实开销,得把它的 LOB 段、索引段都归并进去。
核心关联逻辑:
- 从
DBA_LOBS查owner和table_name,用其segment_name关联DBA_SEGMENTS.segment_name,把 LOB 空间加回基表 - 从
DBA_INDEXES查table_name,用其index_name关联DBA_SEGMENTS.segment_name,把索引空间计入基表 - 别被
DBA_LOBS.segment_name带偏——owner和table_name才是业务归属
例如,DOC_TABLE 在 DBA_SEGMENTS 中只显示 200MB,但通过 DBA_LOBS 关联发现它对应一个 8GB 的 LOB 段,这才是真实瓶颈。
容易被忽略的干扰项
查最大段时,TEMPORARY、UNDO、SYSTEM 表空间里的对象,以及统计信息延迟更新,都会让结果失真。
- 临时段(
segment_type = 'TEMPORARY')通常来自大排序或哈希连接,不持久,别误判为“常驻大户” -
SYSTEM或SYSAUX下的对象,比如WRH$_ACTIVE_SESSION_HISTORY,可能只是 AWR 快照,真实膨胀源其实是SM/ADVISOR模块,得查v$sysaux_occupants - 刚
TRUNCATE或DROP的对象,空间未必立即释放,DBA_SEGMENTS还会显示旧值,需结合dba_free_space看实际空闲 - LOB 数据若启用了
SECUREFILE和压缩,bytes值反映的是分配量,不是实际存储量,需用dbms_lob.getlength()抽样验证
真正难的不是查出数字,而是判断哪个段背后连着哪段业务逻辑、谁在持续写入、能不能收缩——这些都得跳出 DBA_SEGMENTS,结合 v$session、v$sql 和应用日志交叉验证。











