不能用 rownum,因为它在排序前就已生成,无法保证按真实大小排序;必须先聚合同名表的所有物理存储段(主表、分区、lob、索引),再排序取top n。
直接查 dba_segments 能拿到真实占用最大的段,但必须聚合同名表的所有物理存储(主表段 + 分区段 + lob段 + 索引段),否则结果严重失真——比如一个带 clob 的表,lobsegment 可能比表本身大 10 倍,不聚合就根本看不见它。
为什么不能用 ROWNUM
因为 DBA_SEGMENTS 是按段(segment)记录的,不是按逻辑表。一张分区表有 20 个分区,就会产生 20 条记录;一个表带 3 个 LOB 字段,会额外生成 3 个 LOBSEGMENT 记录;索引又单独成段。不先 GROUP BY 就排序取前 10,大概率拿到一堆小分区或零碎索引,漏掉真正吃空间的业务主体。
-
ROWNUM在ORDER BY之前执行,未排序时取前 10 行毫无意义 - 必须先
SUM(bytes)聚合,再ORDER BY SUM(bytes) DESC - 推荐用
FETCH FIRST 10 ROWS ONLY,语义清晰且避免子查询别名问题
怎么聚合才准确?
最简可靠起点是按 owner + segment_name + segment_type 分组——这能区分同一张表的不同物理组件(如 MYTABLE 的 TABLE 段和对应 MYTABLE_IDX 的 INDEX 段),同时保留类型线索,方便后续判断是否需进一步归并。
- 聚合粒度太粗(只按
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;
想定位“哪张业务表最大”,还得关联 DBA_LOBS 和 DBA_INDEXES
segment_name 不等于业务表名:LOB 段名形如 SYS_LOB0000012345C00002$$,索引段名是 MYTABLE_IDX,都不直接对应 MYTABLE。要算清一张表的真实开销,得把它的 LOB 段、索引段空间都加回去。
- 用
DBA_LOBS.segment_name关联DBA_SEGMENTS.segment_name,把 LOB 空间归到DBA_LOBS.table_name - 用
DBA_INDEXES.table_name关联DBA_SEGMENTS.segment_name = DBA_INDEXES.index_name,把索引空间计入基表 - 注意:
DBA_LOBS中的owner和table_name才是业务归属,别被segment_name带偏
容易被忽略的坑:临时段、UNDO 段和 SYSTEM 表空间
查 TOP 10 时如果看到大量 TEMPORARY 或 UNDO 类型段,说明有长事务、大排序或未提交操作在持续占空间——这不是数据增长,而是运行态残留,得结合 V$TRANSACTION 或 V$TEMPSEG_USAGE 追踪源头。
SYSTEM 表空间出现大 LOBSEGMENT 很可能是审计表(如 AUD$)失控,这类对象不能随便删,得走 TRUNCATE ... REUSE STORAGE + DEALLOCATE UNUSED 组合清理。
真正麻烦的从来不是“哪个表最大”,而是“为什么这个表的 LOB 段比表本身还大 8 倍”——那个 segment_name 看似陌生,但通过 DBA_LOBS 一查,往往直指某个日志表或附件表,这才是该动手的地方。











