不能用 where rownum 因为未聚合前排序会导致 rownum 截断零碎段(如单分区、lob子段),遗漏真正占用空间的业务表;须先按 owner、segment_name 聚合 sum(bytes),再 order by 取前10。
直接查 dba_segments 拿不到真正“最大”的对象——不聚合就排序,rownum 会返回一堆零碎段(比如单个分区、lob子段、小索引),漏掉实际吃空间的业务表。必须先按物理存储单元聚合,再排序取前10。
为什么不能用 WHERE ROWNUM 直接限制?
ROWNUM 在 ORDER BY 之前分配,未排序时取前10行毫无意义;一张分区表可能在 DBA_SEGMENTS 里有上百条记录,ROWNUM 会随机截断,根本看不到它的真实总大小。
- 分区表每个分区是独立段,不
GROUP BY就会被拆成碎片 -
LOBSEGMENT和INDEX段名不等于表名,单独查segment_name无法归因 -
FETCH FIRST 10 ROWS ONLY是更安全的替代,语义明确且避免子查询别名问题
怎么聚合才既准确又实用?
最简可靠粒度是按 owner + segment_name + segment_type 分组:既能区分同一张表的不同物理组件(如 MYTABLE 的 TABLE 段和 MYTABLE_IDX 的 INDEX 段),又保留类型线索,一眼识别出高危的 LOBSEGMENT。
- 只按
owner+segment_name聚合 → 把索引和表合并,掩盖索引膨胀 - 加
partition_name→ 分区表被拆得更碎,失去“表级”视角 - 必须含
segment_type,否则无法判断SYS_LOB0000012345C00002$$是谁的 LOB
示例语句:
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 不等于业务表名:LOBSEGMENT 名是系统生成的乱码,INDEX 段名是索引名而非基表名。要算清一张表的真实开销,必须把它的 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带偏
容易被忽略的干扰项
临时段(TEMPORARY)、UNDO 段、SYSTEM 表空间里的对象,以及统计信息延迟更新,都会让结果失真。执行前确认当前用户有 SELECT_CATALOG_ROLE 权限,且不是在 CDB 环境中忘了加 CON_ID 过滤。
真正难的不是写 SQL,而是理解:你看到的每一条 DBA_SEGMENTS 记录,只是物理存储的一个切片,不是业务逻辑上的“对象”。











