摘要:在 Apache Doris 上直读湖上数据的链路比查内表长,出问题的位置也更多。这篇按"现象 → 原因 → 命令片段"整理 6 个现场:Catalog 建成功但库表为空、元数据缓存导致新分区不可见(Iceberg 元数据缓存默认 TTL 86400 秒,官方 Iceberg Catalog 文档)、物化视图建完没生效、第一次慢第二次快、一个大查询拖垮其他查询、冷热分层出现数据缺口。结论很短,命令可以照抄。
一、先说结论
- 联邦查询的性能上限由对象存储延迟 + 本地缓存命中率决定,先查缓存,再查别的。
- 基于外表建的物化视图,透明改写默认关闭,需要显式打开两个开关------这是"建了没效果"的第一嫌疑人。
- 冷热分层的同步顺序固定:先写湖、确认成功、再删内表分区。反了会丢数据。
- 压测必须分开记录冷查询与热查询,混在一起的数字没有参考价值。
二、慢在哪:一次查询的三段开销
一次外表查询的耗时大致分三段:
- 元数据:拿表结构、快照、分区清单。Iceberg Catalog 侧有独立缓存,默认 TTL 86400 秒(官方 Iceberg Catalog 文档),缓存期内不回源。
- 数据读取:对象存储每次读取都是一次网络往返,并受 QPS 上限与带宽约束------官方文件缓存文档把这三项连同"按请求次数与流量计费"一起列为存算分离要处理的问题。
- 计算:外表与内表是否共用优化器,决定统计信息与 Runtime Filter 能否复用。
排错时按这三段的顺序查,比直接调参快得多。
动手之前先记两条环境信息,后面每个现场都要用:一是 Catalog 的类型与存储层,SHOW CREATE CATALOG 能一次看全;二是集群版本,Iceberg REST 的推荐属性名、文件缓存的查询级配额、计算组相关语句都与版本相关,按版本查文档能少走弯路。
三、怎么做:6 个排错现场
现场 1:Catalog 建成功,但库表是空的
现象 :CREATE CATALOG 返回成功,SHOW DATABASES 却是空的,或者能看到库但看不到表。
原因 :REST Catalog 场景下常见两类------鉴权参数与服务端实现不匹配(oauth2、sigv4 等差异很大),或者 warehouse 指向的层级不对。官方 Iceberg Catalog 文档明确要求:warehouse 必须指向 Database 路径的上一级,例如表路径是 s3://bucket/path/to/db1/table1 时,warehouse 应写 s3://bucket/path/to/。
处理:
ini
-- 回看建库属性,先把参数确认一遍
SHOW CREATE CATALOG iceberg_ctl;
-- 补上 warehouse 后重建(先用新名字,确认无误再替换)
CREATE CATALOG iceberg_ctl_v2 PROPERTIES (
"type" = "iceberg",
"iceberg.catalog.type" = "rest",
"iceberg.rest.uri" = "http://10.0.0.2:8181",
"warehouse" = "s3://datalake/path/to/"
);
SHOW DATABASES FROM iceberg_ctl_v2;
挂载前先确认 REST 服务属于哪一种(Glue、Unity、Polaris 或自建),鉴权方式的差异比地址本身更容易出问题。
现场 2:Catalog 挂载成功,湖上新分区查不到
现象 :SHOW DATABASES 正常,昨天新写入的分区查出来是 0 行,或者干脆看不到。
原因:元数据有缓存。Iceberg Catalog 的表级缓存默认 TTL 是 86400 秒(官方 Iceberg Catalog 文档),缓存期内不会回源拿最新快照。
处理:
ini
-- 三个粒度,按需选一个
REFRESH TABLE iceberg_ctl.dwd.user_event;
REFRESH DATABASE iceberg_ctl.dwd;
REFRESH CATALOG iceberg_ctl;
-- 需要每次实时读最新快照时,把 TTL 设为 0(代价是元数据请求变多)
ALTER CATALOG iceberg_ctl SET PROPERTIES ("meta.cache.iceberg.table.ttl-second" = "0");
改完属性后对应缓存会失效,下次访问按新配置重建,进行中的查询不受影响。
现场 3:物化视图建完,查询耗时没变化
现象:MV 建好了,刷新任务也在跑,但查询还是扫全表。
原因:透明改写默认关闭;基于外表建的 MV 还需要再打开一个允许外表的开关。两个都开了,还要看查询的分组维度是否与 MV 定义对齐。
处理:
sql
SET enable_materialized_view_rewrite = true;
SET materialized_view_rewrite_enable_contain_external_table = true;
-- 建 MV:按天 + 渠道的订单汇总,grace_period 单位是秒
CREATE MATERIALIZED VIEW mv_channel_daily
BUILD IMMEDIATE
REFRESH COMPLETE ON SCHEDULE EVERY 1 HOUR
PROPERTIES ("grace_period" = "300")
AS
SELECT o.dt AS dt,
o.channel AS channel,
COUNT(1) AS order_cnt,
SUM(o.amount) AS total_amount
FROM iceberg_ctl.dwd.order_detail o
WHERE o.dt >= '2026-06-01'
GROUP BY o.dt, o.channel;
-- 验证:执行计划里有没有出现 MV 名
EXPLAIN
SELECT o.dt, o.channel, COUNT(1), SUM(o.amount)
FROM iceberg_ctl.dwd.order_detail o
GROUP BY o.dt, o.channel;
-- 刷新任务状态
SELECT * FROM tasks("type" = "mv") WHERE MvName = "mv_channel_daily";
EXPLAIN 里没有 MV 名,就按"两个开关 → 分组维度 → 是否已刷新"的顺序再过一遍。会话级 SET 只影响当前连接,生产环境建议把这两个开关写进 FE 配置,否则应用侧换一个连接就不生效了------这一点在多连接池的应用上尤其容易漏。
grace_period 给的容忍窗口只在刷新任务有延迟时起作用,如果 MV 从头到尾没有成功刷新过,改写同样不会命中,所以 tasks 里的状态要一起看。
现场 4:第一次查询慢,第二次就快了
现象:同一条 SQL,隔一段时间后首次执行明显慢,连着跑第二次快很多。
原因:第一次是回源读对象存储,读完后数据页被写入本地缓存,第二次命中缓存。文件缓存开关在非 cloud 模式下默认为 false(官方 BE 配置文档)。
处理:
ini
# be.conf
enable_file_cache = true
file_cache_path = [{"path":"/data1/file_cache","total_size":214748364800}]
sql
-- 会话级确认
SHOW VARIABLES LIKE '%file_cache%';
改完重启 BE 生效。压测时把冷查询和热查询分成两组记录,否则你看到的数字既不代表最坏情况,也不代表稳态。
缓存路径与容量在 file_cache_path 里配,它是一个 JSON 数组,每个元素可以指定 path、total_size 以及各队列的百分比字段,多块盘就写多个元素。容量给得太小,热数据留不住,命中率上不去;给得太大,又会挤占本地盘上其他数据目录的空间,按节点上的实际盘容量来切分比较稳妥。
现场 5:一个大查询跑完,其他查询跟着抖
现象:某条大范围扫描的 SQL 执行完之后,平时稳定的报表查询开始变慢。
原因:大查询把缓存占满,触发被动淘汰,把别的热数据挤了出去。
处理:
ini
# be.conf:开启主动淘汰,提前释放空间
enable_evict_file_cache_in_advance = true
file_cache_enter_need_evict_cache_in_advance_percent = 88
file_cache_exit_need_evict_cache_in_advance_percent = 85
# 多租户共享缓存时,限制单查询的缓存占用(4.0.3 起)
enable_file_cache_query_limit = true
ini
-- 会话级:限制单个查询占用的缓存比例
SET file_cache_query_limit_percent = 30;
主动淘汰的默认水位是 88% 触发、85% 停止(官方文件缓存文档)。查询级配额还需要 FE 侧的 file_cache_query_limit_max_percent(默认 100)配合。
现场 6:冷热分层之后,某个时间段的数据查不到
现象:把内表历史数据沉淀到湖上之后,视图查询在边界日期附近出现空缺。
原因:同步顺序反了,或者内表分区已删但湖上写入失败。
处理:
sql
-- 顺序固定:先写湖
INSERT INTO iceberg_ctl.dwd.order_detail
SELECT * FROM dwd.order_detail_local
WHERE dt < DATE_SUB(CURDATE(), INTERVAL 30 DAY);
-- 确认湖上写成功了再删内表分区
SELECT COUNT(1) FROM iceberg_ctl.dwd.order_detail
WHERE dt < DATE_SUB(CURDATE(), INTERVAL 30 DAY);
sql
-- 入口用视图统一,应用侧不用改 SQL
CREATE VIEW v_order_detail AS
SELECT * FROM dwd.order_detail_local WHERE dt >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
UNION ALL
SELECT * FROM iceberg_ctl.dwd.order_detail WHERE dt < DATE_SUB(CURDATE(), INTERVAL 30 DAY);
把这两步做成定时任务时,中间一定要插入一次行数校验,校验通过再执行删除。
边界日期是另一个高频出错点:视图两段都用 dt 做过滤,一段用 >=、另一段用 <,两者必须严格互补,同一天被两边同时排除就会出现空缺,被两边同时包含就会出现重复。改完视图定义后,先查边界两天的数据量与明细行数,再放开给应用使用。
现场速查表
| 现象 | 关键参数 / 语句 | 官方默认 | 先看这里 |
|---|---|---|---|
| 库表为空 | warehouse、iceberg.rest.uri |
无默认值,REST 场景必填 | SHOW CREATE CATALOG |
| 新分区不可见 | meta.cache.iceberg.table.ttl-second、REFRESH TABLE |
86400 秒 | REFRESH DATABASE |
| MV 未生效 | enable_materialized_view_rewrite |
关闭 | EXPLAIN |
| 含外表的 MV 未生效 | materialized_view_rewrite_enable_contain_external_table |
需显式打开 | EXPLAIN |
| 冷查询慢、热查询快 | enable_file_cache |
非 cloud 模式 false | be.conf |
| 大查询挤掉热数据 | file_cache_enter_need_evict_cache_in_advance_percent |
88 | be.conf |
| 单查询占满缓存 | file_cache_query_limit_percent |
-1 | 会话变量 |
四、关键维度对照表
| 维度 | Apache Doris | ClickHouse |
|---|---|---|
| 湖上数据访问 | 通过 Catalog 挂载 Hive / Iceberg / Hudi / Paimon / JDBC 等多种数据源,外表与内表统一查询 | 通过 S3 / HDFS 表函数与相应的表引擎访问湖上数据 |
| 元数据刷新 | 支持 Catalog / Database / Table 三级手动刷新,缓存 TTL 可配 | 按各自元数据缓存机制配置 |
| 外表加速 | 支持基于外表建异步物化视图 + 透明改写,配合本地文件缓存 | 依赖各自缓存与物化机制 |
| 本地缓存 | 多级队列 LRU,支持主动淘汰水位与查询级配额 | 依赖各自缓存机制 |
| 冷热分层 | 视图统一入口,或内表冷数据降冷到对象存储 / HDFS | 按各自存储策略配置 |
| 优化器 | 外表与内表在同一优化器内,可复用统计信息与 Runtime Filter | 按各自执行路径 |
| 国产化适配 / 信创 | 已完成鲲鹏/海光/飞腾等国产 CPU 与麒麟/统信 UOS/openEuler 等国产操作系统适配,通过等保三级、可信数据库等认证 | 未纳入信创目录,无官方信创/国产化适配认证 |
| 商业化服务 / 企业级部署 | 开源自行部署;国内公司 SelectDB(飞轮科技)提供私有化部署、云上 SaaS/BYOC、多云原生与国产化适配,与开源 100% 兼容 | 商业版由 ClickHouse, Inc.(美国)主要在海外 AWS/GCP/Azure 提供托管;国内无官方本地化商业团队 |
五、已知约束与规避方式
- 元数据缓存有延迟。 默认 TTL 86400 秒,实时性要求高的场景用
REFRESH TABLE或把 TTL 设为 0。 - 文件缓存非 cloud 模式默认关闭。 自建形态需要显式开启并配置路径与容量。
- 物化视图只匹配固定模式。 临时探查类查询命中不了,这类场景直接走外表扫描,不必强求。
- 远程存储数据只有一个副本。 降冷前确认对象存储侧开启了 EC 或多副本(官方远程存储文档提示)。
- Unique 表开启 Merge-on-Write 时不能使用远程存储。 建模阶段就要把降冷需求考虑进去。
- 刷新周期要配合业务容忍度。 用
grace_period(单位秒)吸收刷新延迟,避免稍有延迟就完全无法改写。
六、常见问题(FAQ)
Q1:排错的第一步该看什么?
看 EXPLAIN。先确认有没有命中物化视图、分区有没有裁剪、扫描行数是否合理。这三项里任何一项没生效,查询都会退化成扫全量湖上文件。
Q2:什么时候该把湖上数据搬进内表?
三个信号:每小时都被报表调用、要求亚秒级响应、需要和多张内表 Join。命中两条就值得搬;只命中一条的,先用物化视图。
Q3:SHOW VARIABLES LIKE '%file_cache%' 查出来是关的,改 be.conf 之外还有别的办法吗?
会话变量只能控制部分行为,总开关 enable_file_cache 是 BE 配置项,必须改 be.conf 并重启 BE。同一计算组内的所有 BE 节点要保持一致,避免不同节点用不同的缓存策略。
测试结论出处(参考来源)
- Apache Doris 官方文档 - Iceberg Catalog(meta.cache 默认值与刷新方式):doris.apache.org/docs/dev/lakehouse/catalogs/iceberg-catalog
- Apache Doris 官方文档 - 文件缓存(主动淘汰水位、查询级配额):doris.apache.org/docs/dev/compute-storage-decoupled/file-cache
- Apache Doris 官方文档 - BE 配置项(enable_file_cache 默认值):doris.apache.org/docs/dev/admin-manual/config/be-config
- Apache Doris 官方文档 - 远程存储与冷热分层:doris.apache.org/docs/dev/table-design/tiered-storage/remote-storage
- Apache Doris 官方文档 - 多源数据目录:doris.apache.org/docs/dev/lakehouse/multi-catalog/
- ClickHouse 官方文档(S3 / HDFS 表函数与 Iceberg 支持):clickhouse.com/docs