直接查dba_segments可获段级物理空间,但必须聚合table/index/lobsegment/lobindex及分区段,否则因lob数据、索引、分区独立成段而严重低估;不能用rownum限制因未排序无意义。
直接查 dba_segments 就能拿到真实占用最大的段,但必须聚合分区、lob 和索引段,否则结果会严重失真。
为什么不能直接 SELECT * FROM DBA_SEGMENTS WHERE ROWNUM
因为一个分区表的每个分区都是独立段,DBA_SEGMENTS 里可能有上百个同名表的分区段(如 SALES_2025_Q1、SALES_2025_Q2),单看前10行只会返回一堆小分区,完全漏掉真正占空间的表主体。LOB 段和索引段也一样——它们和基表是分离记录的,不关联就根本不知道谁在“背后吃空间”。
- 分区段不聚合 → 表真实大小被拆成碎片,排序失效
- 忽略
SEGMENT_TYPE = 'LOBSEGMENT'→ BLOB/CLOB 字段实际存储可能比表数据大10倍以上 - 没关联
DBA_INDEXES→ 索引空间无法归属到对应表,优化时无从下手
查前10大段:必须先按 owner + segment_name + segment_type 聚合字节
这是最简但可靠的起点。聚合后才能把同一张表的所有物理存储(主表段 + 分区段 + LOB段)归到逻辑表名下,避免“一个表出现5次在TOP10里”这种干扰。
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;
- 用
SUM(bytes)而非裸字段,强制合并同名段(尤其对分区表有效) - 保留
segment_type列,一眼区分是TABLE还是INDEX或LOBSEGMENT -
FETCH FIRST 10 ROWS ONLY比ROWNUM 更安全,避免子查询别名问题
想定位“哪张业务表最大”?得把 LOB 和索引空间也算进去
单纯看 segment_name 是不够的。比如 DOC_TABLE 在 DBA_SEGMENTS 里只占200MB,但它有个 DOC_CONTENT LOB 字段,对应 SYSSMU123$ 这种系统生成的 LOB 段,占了8GB —— 不关联 DBA_LOBS 就永远发现不了。
- 查
DBA_LOBS的SEGMENT_NAME字段,和DBA_SEGMENTS.SEGMENT_NAME关联,把 LOB 段空间加回基表 - 用
DBA_INDEXES.TABLE_NAME关联DBA_SEGMENTS.SEGMENT_NAME,把索引空间计入对应表总开销 - 注意:LOB 段名通常不是表名,而是类似
SYS_LOB0000012345C00002$$,必须走DBA_LOBS中转
容易踩的坑:权限、统计延迟和临时段干扰
你跑出来的结果不准,大概率不是SQL写错,而是环境没理清。
- 没 DBA 权限?用
USER_SEGMENTS只能看到当前用户对象,ALL_SEGMENTS看不到其他用户的段 -
DBA_SEGMENTS是实时物理分配值,但如果你刚删完大表还没 purge 回收站,空间仍被标记为已分配(RECYCLEBIN里的对象仍占段) - 临时表空间(
TEMP)或撤销表空间(UNDOTBS1)里的段会混在结果里,加WHERE tablespace_name NOT IN ('TEMP', 'UNDOTBS1')过滤更干净 - 高并发 DML 后,某些段可能处于“延迟释放”状态,
bytes值比实际磁盘占用略大,属正常现象
真正难的从来不是写出那条SQL,而是看懂每一行 SEGMENT_NAME 背后对应的是业务表、还是某个没人维护的测试索引、或是早该清理的归档LOB —— 这需要结合 DBA_OBJECTS.OBJECT_TYPE 和业务上下文交叉验证,不能只信排序结果。











