先说说我自己遇到的场景。某天下午测试环境磁盘告警,我登录StarRocks想看看是哪些表在膨胀,结果发现一个尴尬的事实:界面上能看到集群总容量、BE节点的数据目录大小,但真要到“每张表到底占了多少G”这个粒度,默认命令居然给不出一个干净利落的答案。折腾了一圈,把information_schema里的几张系统表啃明白之后,才算找到正规军。这篇就聊聊 StarRocks 如何查看每张表占用的存储,包括背后的存储结构、官方系统表口径、实操SQL、以及我在排障过程中踩过的坑。
这个主题适合三类人:日常维护 StarRocks 集群的运维、需要做容量规划和成本核算的数据平台工程师、以及遇到磁盘告警但不知道从哪张表下手的开发同学。看完之后,你至少能写出第一条“全表存储占用清单”SQL,并且知道为什么有些统计数字和你预期的对不上。
1. 先搞清楚:StarRocks 里的“表大小”到底怎么算
1.1 一张表的数据是怎样落盘的
想查表大小,先得明白 StarRocks 的存储分层:表(Table)→ 分区(Partition)→ 分桶(Tablet)→ Rowset → Segment。这里最关键的一层是 Tablet,它是 StarRocks 中数据复制、均衡、迁移的最小物理单元。每个 Tablet 会按照副本数复制多份,分散到不同的 BE 节点上,而每个 BE 节点上真正落盘的东西,就是 Tablet 对应目录下的 Rowset 文件和 Segment 数据文件。
用大白话理解:一张表好比一个大仓库,分区是楼层,分桶(Tablet)是楼层里的独立房间,Rowset 是房间里的一批货,Segment 是货架上的具体箱子。系统表里统计的数据大小,本质上就是统计这些“箱子”加在一起的体积。
因为数据是分布式的,同一张表的 Tablet 可能散落在十几台 BE 上,所以“表大小”天然是一个需要聚合计算的值。这也解释了为什么简单的SHOW TABLES看不到 size 字段——存储统计必须跨节点汇总,不可能存在一张普通元数据表里直接给你。
1.2 三种口径:逻辑大小、单副本大小、物理占用
我在查容量时发现,很多人对“表大小”的理解其实有偏差。StarRocks 的存储统计至少要区分三种口径:
- 逻辑大小(导入数据体积):你通过 Stream Load、Broker Load 等导入的原始数据量,未经过压缩,是“业务视角”的大小。
- 单副本大小(系统表展示值):BE 上某个 Tablet 数据文件实际占用的磁盘空间。StarRocks 默认使用 LZ4 等压缩算法,所以这个值比原始导入体积小不少。
- 物理占用(集群视角真实消耗):单副本大小乘以副本数。默认副本数为 3,也就是说一份数据理论上会在集群里占三份磁盘。
information_schema系统表返回的DATA_SIZE,一般指的是第二类——单副本的物理文件大小,已经包含了压缩效果。很多人拿这个值去核对集群总容量,发现乘上副本数才对得上,就是因为没区分这三种口径。
1.3 为什么没有一个现成的 table_size 字段
这也是新手最容易困惑的地方。MySQL 里查information_schema.tables有DATA_LENGTH,StarRocks 早期版本参考了这套设计,但实际用起来会发现不准,原因有两个:
一是 StarRocks 是 MPP 架构,同一个表的统计信息分散在多个 BE 上,FE 需要周期性地收集各 BE 的 Tablet 报告,才能汇总出全局值。这个收集过程有延迟,不是实时精确值。二是 StarRocks 的部分系统表在实现时更偏重“表结构元数据”,而不是“存储统计”,真正存储维度的信息被拆到了另外的表中。所以你需要组合查询tables_config、table_sk等系统表,才能拼出完整答案。
2. 官方入口:information_schema 两张核心系统表
2.1 tables_config:拿“表ID到表名”的映射
StarRocks 的系统表里,很多存储统计表只记录TABLE_ID,不直接给你表名。所以第一步通常是先查information_schema.tables_config,拿到表名和表 ID 的对应关系。
SELECT TABLE_ID, TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE, KEY_TYPE, REPLICATION_NUM, DISTRIBUTED_BUCKETS FROM information_schema.tables_config LIMIT 20;这张表的特点是每个字段都有用:KEY_TYPE告诉你表是主键表、聚合表还是明细表;REPLICATION_NUM表示副本数,计算物理占用时要用到;DISTRIBUTED_BUCKETS是分桶数,能辅助判断表是否建得合理。它本身不存大小,但它是所有存储统计查询的“字典表”。
2.2 table_sk:表级存储统计的正确入口
真正存“大小”的系统表,在较新的 StarRocks 3.x 版本里叫information_schema.table_sk(某些版本也叫table_statistics或类似命名),它的粒度是“每个 Tablet 一条记录”。重要字段如下:
| 字段名 | 含义 |
|---|---|
| TABLE_ID | 表 ID,关联 tables_config |
| PARTITION_ID | 分区 ID |
| TABLET_ID | Tablet ID,分桶后的最小单元 |
| NUM_ROWS | 该 Tablet 的行数 |
| DATA_SIZE | 该 Tablet 数据文件的大小(单副本,单位字节) |
| CREATE_TIME / UPDATE_TIME | 元数据记录时间,用于判断统计新鲜度 |
这个表的设计很有意思:它不是表级汇总,而是 tablet 级明细。也就是说你可以按TABLE_ID分组求和,得到表级存储;也可以按PARTITION_ID分组,得到分区级存储。这让容量分析灵活了很多。
2.3 不同版本怎么选
如果你的 StarRocks 版本比较老,可能没有table_sk。这时有两个备选方案:一是查询information_schema.be_tablets,这个视图也会返回TABLE_ID、BE_ID、DATA_SIZE、ROW_NUM,区别是它还带了 BE 节点维度,可以用来做节点均衡分析;二是直接用SHOW DATA命令,它会返回每张表的 size 和副本数,虽然粒度粗,但胜在简单,适合临时应急。
建议先把table_sk和be_tablets都摸一遍,因为它们在排查“某个 BE 磁盘特别满”的场景下各有优势。
3. 实战SQL:一条语句查完所有表的存储占用
3.1 最常用的清单SQL
下面这条 SQL 是我现在排查容量问题时最先跑的一条,它把表名、数据库名、总大小、总行数、副本数一次拉齐:
SELECT t.TABLE_SCHEMA AS db_name, t.TABLE_NAME AS table_name, ROUND(SUM(s.DATA_SIZE) / 1024 / 1024 / 1024, 2) AS size_gb, SUM(s.NUM_ROWS) AS total_rows, COUNT(DISTINCT s.TABLET_ID) AS tablet_count, MAX(t.REPLICATION_NUM) AS replica_num FROM information_schema.table_sk s JOIN information_schema.tables_config t ON s.TABLE_ID = t.TABLE_ID GROUP BY t.TABLE_SCHEMA, t.TABLE_NAME ORDER BY size_gb DESC LIMIT 50;输出结果大致长这样:
| db_name | table_name | size_gb | total_rows | tablet_count | replica_num |
|---|---|---|---|---|---|
| dwd | ads_order_detail | 128.55 | 89012345 | 48 | 3 |
| dwd | dwd_user_login_log | 87.30 | 330012345 | 36 | 3 |
| ads | app_user_portrait | 45.12 | 5678210 | 24 | 3 |
这个结果里的size_gb是单副本大小,要估算真实磁盘占用,要乘以replica_num(默认 3)。比如第一行ads_order_detail单副本约 128.55 GB,实际占用的物理空间约为 128.55 × 3 ≈ 385.65 GB。
3.2 看懂返回结果:单位、版本、副本这些细节
很多人第一次跑完这条 SQL,会有几个疑问:
DATA_SIZE 的单位是字节,所以我在 SQL 里做了三次除以 1024 的换算,得到 GB。如果你只除以一次,算出来的是 KB,容易误判数量级。
DATA_SIZE汇总的是当前所有存活版本的文件大小。StarRocks 的每次导入会产生新的 Rowset 版本,老版本要等 Compaction 合并后才释放。如果你刚导入完大批量数据,立刻查表大小,看到的值可能偏大,因为版本还没合并完。这不是系统表统计错,而是 Compaction 还没做完。
NUM_ROWS是所有 Tablet 行数的累加值。对于主键表,如果删除操作很多,磁盘上可能还残留旧版本的删除标记,所以会出现“行数不大、占用却不小”的情况,具体原因后面单独讲。
3.3 行数和大小对不上?大概率是这些原因
我遇到过几次诡异情况:一张表只有几万行,但 size_gb 显示好几十 G。排查下来主要有三类原因:
- 主键表的小批量高频更新。每次更新都会产生新的版本文件,旧版本数据在 Compaction 之前不会立即清理。高频更新的表,版本文件堆积起来,体积可能远超实际有效数据。
- 导入时生成了大量小文件。如果你用 Stream Load 频繁写入,每个批次都会产生新的 Rowset,不给系统合并的时间,就会有很多零碎文件占空间。
- 表结构里的字段有大字段。比如 String 类型存了几 KB 的大文本,行数不多但单个 Row 体积大,放大到 Tablet 级别就非常可观。
遇到这类问题,不要直接怀疑系统表统计错误,先检查表的 Compaction 状态和最近写入频率,通常能找到原因。
4. 进阶玩法:按库、分区、BE 节点多维度盘容量
4.1 按数据库维度统计
表级清单适合定位“哪张表最大”,但如果整个库都占空间很大,你还需要一个库维度的汇总视图。把前面那条 SQL 的 GROUP BY 改成TABLE_SCHEMA就行:
SELECT t.TABLE_SCHEMA AS db_name, ROUND(SUM(s.DATA_SIZE) / 1024 / 1024 / 1024, 2) AS size_gb, SUM(s.NUM_ROWS) AS total_rows, COUNT(DISTINCT t.TABLE_NAME) AS table_cnt, MAX(t.REPLICATION_NUM) AS replica_num FROM information_schema.table_sk s JOIN information_schema.tables_config t ON s.TABLE_ID = t.TABLE_ID GROUP BY t.TABLE_SCHEMA ORDER BY size_gb DESC;这个视图的价值在于“治理优先级”。如果某个库占用了全集群 60% 的空间,那容量治理的重点就该放在这个库的业务表上。我在实际运维中,会把这条 SQL 的结果存成历史快照,每周对比一次,看哪个库增长最猛,再往下钻取表级明细。
4.2 按分区粒度找“时间黑洞”
表级统计只能告诉你哪张表大,不能告诉你是哪个时间段的数据占了大头。对于日志类分区表,你需要的其实是“哪个分区最大”。改一下 GROUP BY 条件,把PARTITION_ID加进来:
SELECT t.TABLE_SCHEMA, t.TABLE_NAME, s.PARTITION_ID, ROUND(SUM(s.DATA_SIZE) / 1024 / 1024 / 1024, 2) AS size_gb, SUM(s.NUM_ROWS) AS total_rows FROM information_schema.table_sk s JOIN information_schema.tables_config t ON s.TABLE_ID = t.TABLE_ID WHERE t.TABLE_NAME = 'dwd_user_login_log' GROUP BY t.TABLE_SCHEMA, t.TABLE_NAME, s.PARTITION_ID ORDER BY size_gb DESC LIMIT 30;这样能快速定位到“最近三个月数据占 80% 空间”这种典型问题。找到之后,就可以针对性地给分区设置生命周期,或者直接把历史大分区做冷备归档。分区维度的统计,是最适合做数据治理的视角。
4.3 用 be_tablets 定位 BE 上的热点 Tablet
存储分布不均也是常见问题——集群整体容量够,但某个 BE 磁盘快满了。这时候要看be_tablets,它比table_sk多了 BE 节点维度:
SELECT BE_ID, ROUND(SUM(DATA_SIZE) / 1024 / 1024 / 1024, 2) AS size_gb, COUNT(*) AS tablet_cnt FROM information_schema.be_tablets GROUP BY BE_ID ORDER BY size_gb DESC;如果发现某个 BE 的数据量远超其他节点,可以再往下钻取,看这个 BE 上都有哪些大 Tablet:
SELECT BE_ID, TABLET_ID, ROUND(DATA_SIZE / 1024 / 1024, 2) AS size_mb FROM information_schema.be_tablets WHERE BE_ID = <你的BE_ID> ORDER BY size_mb DESC LIMIT 20;定位到热点 Tablet 之后,可以借助 StarRocks 的 Tablet 均衡机制,或者手动调整分桶策略,把压力分摊开。这个排障思路是常规监控面板给不了的。
4.4 算集群“真实物理占用”的正确姿势
如果老板问“咱们集群数据量多大”,你不能拿单副本大小糊弄过去。真实物理占用要考虑两点:副本数和压缩率。
如果系统表返回的 DATA_SIZE 已经是压缩后的单副本大小,那么:
集群物理占用 ≈ SUM(单副本DATA_SIZE) × 平均副本数举例说明:假设table_sk汇总后所有表单副本大小合计 500 GB,默认副本数 3,那么集群实际数据文件占用约 1500 GB。如果集群里还开了多副本迁移、或者存在副本不均衡的情况,这个数字会有轻微浮动,但作为容量估算已经足够精准。
如果你想知道“导入前的原始数据量”,那就得再乘上压缩倍数。LZ4 压缩倍率取决于数据类型,数值型字段压缩率低,字符串字段压缩率高,通常可以按 2 到 5 倍估算,但这不是一个稳定的系数,我建议不要过于依赖这个估算,日常容量管理以单副本 × 副本数为准。
5. 常见问题与排查技巧实录
5.1 删了数据,容量为什么没降
这是最高频的问题。你在业务上 DELETE 了很多行,甚至 DROP 了分区,但查下来容量一点没少。
原因在于 StarRocks 的存储模型。在明细表和聚合表中,DELETE 操作通常不是物理删除,而是生成删除条件标记,真正释放空间要等 Compaction。主键表虽然支持真正的点查删除,但旧版本的删除标记也要等合并后才会清理。所以删除后立刻查容量,数字自然不会明显下降。
实操建议:
- 如果是整个分区过期,直接用
DROP PARTITION,不要用 DELETE 逐行删。 - 如果是全表清空,用
TRUNCATE TABLE,它会直接重新创建 Tablet,释放最干净。 - 如果是大量条件删除,删除后等待 Compaction 完成,再观察容量变化。
5.2 表显示 0 行,存储却占几个 G
这种情况我排查过好几次,几乎都出现在主键表上。主键表为了保证实时更新能力,会保存每个主键对应的删除位图和旧版本数据。业务上把行更新成“逻辑删除”,但主键索引还在,旧版本数据也没有立即清理,因此存储占用降不下来。
另外还有一种可能:你 SELECT COUNT(*) 查的是最新版本的有效行数,而table_sk里的 NUM_ROWS 统计的是所有版本的行数累加。如果导入历史版本只被部分合并,就会出现“统计行数远大于实际可见行数”的反差。
这类表如果要彻底瘦身,建议重建表并重新导入数据,或者对表执行一次大版本 Compaction,等它完成后空间会明显回落。
5.3 副本数调整后,统计口径又变了
还有一个很容易混淆的点。如果你用ALTER TABLE ... SET REPLICATION_NUM调整过副本数,那么系统表返回的单副本大小不变,但集群实际占用会相应变化。比如原来 3 副本每表 100 GB,调整成 2 副本后,物理占用从 300 GB 降到 200 GB。查系统表看到的还是单副本 100 GB,所以要结合tables_config.REPLICATION_NUM来动态计算实际占用,而不是单纯看 DATA_SIZE。
副本调整时要注意,降低副本数会立即触发 BE 上的副本删除任务,期间磁盘 IO 会有波动;增加副本数则会触发跨节点复制,会占用网络带宽和磁盘写入。建议在业务低峰期操作。
5.4 用 Python 脚本做定时容量巡检
手动跑 SQL 只能解决一时之急。我后来写了一个简单的 Python 脚本,用 pymysql 直连 FE 的查询端口,定时执行前面那几条统计 SQL,把结果写到监控表里,再用看板展示趋势:
import pymysql import datetime conn = pymysql.connect( host="fe_host", port=9030, user="monitor_user", password="******", database="information_schema" ) sql = """ SELECT t.TABLE_SCHEMA AS db_name, t.TABLE_NAME AS table_name, ROUND(SUM(s.DATA_SIZE) / 1024 / 1024 / 1024, 2) AS size_gb, SUM(s.NUM_ROWS) AS total_rows, MAX(t.REPLICATION_NUM) AS replica_num FROM table_sk s JOIN tables_config t ON s.TABLE_ID = t.TABLE_ID GROUP BY t.TABLE_SCHEMA, t.TABLE_NAME ORDER BY size_gb DESC """ cur = conn.cursor() cur.execute(sql) rows = cur.fetchall() for row in rows: db_name, table_name, size_gb, total_rows, replica_num = row physical_gb = round(size_gb * replica_num, 2) print(f"{datetime.datetime.now()} | {db_name}.{table_name} | " f"单副本: {size_gb} GB | 物理占用: {physical_gb} GB | 行数: {total_rows}") cur.close() conn.close()脚本本身不复杂,关键是把“每天跑一遍”“数据落库”“对比环比”这三件事固定下来。容量问题最怕的不是突然爆掉,而是缓慢增长到临界点你才发现。定时巡检就是用来提前暴露趋势的。
6. 日常存储治理的几条实操建议
6.1 给表设生命周期,别裸奔
日志类、行为类数据表,一定要在建表时就规划好生命周期。StarRocks 支持通过动态分区特性管理过期数据,你可以设置保留最近 N 天的分区,系统会自动创建新分区、淘汰过期分区。别等到磁盘满了再手工 DROP,那是被动救火,建表时多想一步才是正经。
实操上,可以按天分区,设置dynamic_partition.enable = true、dynamic_partition.start = -7,保留最近一周的数据。这样存储增长是可控的,查容量时也能通过分区维度轻松定位到时间范围。
6.2 数据模型和分桶数影响存储效率
我在排查中发现,很多表的存储膨胀和分桶设置不合理有关。分桶数过多、桶内数据太少,会产生大量空 Tablet,每个 Tablet 都有元数据和索引开销;分桶数过少、桶内数据太多,又会导致单次查询扫描数据量过大,影响并发性能。
建表时根据数据规模选分桶数,一般建议每个 Tablet 的数据量在几百 MB 到 1 GB 左右。有些表从几千万行涨到了几十亿行,最初的分桶设计早就过时了,这时候不要硬扛,可以考虑重新设计分桶并迁移数据,存储和查询性能都会有明显改善。
6.3 大数据量导入时的注意事项
如果你经常使用 Stream Load 导入数据,我有个实在建议:合理控制批次大小,不要一次导入太小批。大批量高频的小文件导入,会让每个 Tablet 的版本数快速膨胀,版本多了 Compaction 压力大,存储统计也会短暂虚高。
更合理的做法是攒批导入,比如每 5 分钟或者每 500 MB 一个批次,让每次导入的数据量相对均衡。这样既不影响实时性,又能给 Compaction 留出足够的时间,存储占用和查询性能都会更稳定。
6.4 把容量统计融入日常运维
最后想分享的一个运维习惯是:每次排查完存储问题之后,把用过的 SQL 沉淀下来,做成一个“容量巡检四件套”——全表容量 Top 50、全库容量汇总、分区热点 Top 20、BE 节点分布。这四张表组合起来,基本能应对 90% 的容量分析场景。
配套的还有告警规则。除了监控集群总容量,建议对单表增量也做告警,比如某张表一周内体积增长超过 50%,就触发提醒。这样能在业务异常写入时第一时间发现,而不是等到磁盘告警才回头查表。
说实话,查表存储占用本身不难,难的是把“查出来的数字”和“真实的存储模型”对应起来。我刚开始用系统表的时候也走过弯路,拿单副本大小乘错倍数、拿带副本数的统计当原始数据大小,这些坑躲过一次之后,后面就顺畅了。建议你拿到本文的 SQL 后,先在测试环境跑一遍,对照集群面板的实际数据核对一下口径,再拿到生产环境用。熟悉之后,这张表就是你的容量管理底牌。