查询引擎层:Trino / Doris / ClickHouse

查询引擎层是湖仓一体面向分析师与应用系统的"门面":存储层负责把数据以开放表格式低成本地存起来, 查询引擎负责在秒级甚至亚秒级把这些数据"算出来"。本页深入讲解三类主流引擎——联邦查询引擎 Trino、 高并发实时 OLAP 引擎 Doris、时序列存引擎 ClickHouse——的架构原理、关键配置、调优要点与选型决策, 帮助你在同一份湖仓数据之上构建合理的多引擎查询体系。

本层定位:三类查询引擎的角色与组合

在存算分离的湖仓架构中,数据统一落在对象存储 + 开放表格式(Iceberg/Delta/Hudi)之上, 计算与查询引擎作为无状态(或轻状态)的消费者按需扩缩。此时"查询引擎"不再是一个单体组件, 而是按查询模式分化出的三类角色,它们共享同一份湖仓数据,各司其职:

联邦查询引擎 · Trino

不落存储、只做计算。通过 Connector 体系同时连接 Iceberg 湖仓、业务库、ES 等异构数据源, 支撑跨源 JOIN 与 Ad-hoc 探索分析,是"数据不动、查询动"的典型实现。

实时 OLAP 引擎 · Doris

FE/BE 架构 + 列存 + 预聚合,面向高并发报表、实时大屏、多维分析。 通过 Multi-Catalog 直接读取湖仓外表,也可将热点数据沉淀为内表获得极致性能。

时序列存引擎 · ClickHouse

MergeTree 列存家族 + 向量化执行,单表扫描性能极致。面向日志、监控指标、 用户行为流等时序场景,以极低的存储成本支撑海量明细的即席圈选与聚合。

三类引擎在湖仓整体分层中的位置如下图所示——上层按 SLA 路由查询,下层共享一份数据:

查询接入层:BI 报表 / Ad-hoc 分析 / 数据 API / Notebook
查询引擎层:Trino 联邦查询 · Doris 高并发 OLAP · ClickHouse 时序分析
统一存储层:对象存储 + Iceberg 开放表格式(一份数据,多引擎共享)
BI 报表 / Ad-hoc 分析 / 数据 API / Notebook Trino 联邦查询 / Ad-hoc Doris 高并发实时 OLAP ClickHouse 时序 / 日志列存 统一存储:对象存储 + Iceberg 开放表格式

组合使用策略

注意:多引擎共读一份湖仓数据的前提是表格式提供快照隔离(如 Iceberg 的 snapshot isolation)。引擎读到的都是某个一致快照,不会出现"读到半个事务"的脏数据。

Apache Trino:MPP 联邦查询引擎

架构定位:coordinator + worker 的 MPP 模型

Trino 源自 Presto,是为交互式分析设计的 MPP(Massively Parallel Processing)查询引擎, 存算分离的天然搭档。集群由两类进程组成:

Trino 不自己管理存储,数据在哪里由 Connector 决定——这正是它联邦查询能力的根基。 查询生命周期为:SQL → AST → 逻辑计划 → 优化(谓词下推、列裁剪、join 分布策略选择)→ 分片调度(NodeScheduler)→ 并行执行 → Exchange 汇聚 → 返回。

Connector 体系与联邦查询

Trino 中每个 Catalog 绑定一个 Connector,表名采用 catalog.schema.table 三段式。一条 SQL 可以同时引用多个 catalog, Trino 将各源数据拉入集群内做统一分布式 JOIN,这就是"联邦查询"。核心 Connector 包括: iceberg(湖仓一等公民)、hive、delta-lake、mysql、 postgresql、doris、elasticsearch、kafka 等 50 余种。

为湖仓配置 Iceberg catalog(Hive Metastore 型),文件 etc/catalog/iceberg.properties:

# 连接 Hive Metastore 作为 Iceberg catalog
connector.name=iceberg
iceberg.catalog.type=hive_metastore
hive.metastore.uri=thrift://hive-meta:9083

# 原生 S3 客户端访问对象存储(免 Hadoop 依赖,性能更好)
fs.native-s3.enabled=true
s3.endpoint=https://oss.internal:443
s3.path-style-access=true
s3.aws-access-key=${ENV:AWS_ACCESS_KEY_ID}
s3.aws-secret-key=${ENV:AWS_SECRET_ACCESS_KEY}

# 读取优化:文件级统计与 Bloom 过滤
hive.parquet.use-bloom-filters=true

同时挂载 PostgreSQL 与 Doris 两个数据源(联邦查询的另外两端):

# etc/catalog/pg.properties —— 业务库(CRM)只读账号
connector.name=postgresql
connection-url=jdbc:postgresql://pg-host:5432/crm
connection-user=ro_user
connection-password=******

# etc/catalog/doris.properties —— Doris 加速层
connector.name=doris
doris.host=fe-host
doris.port=9030
doris.user=trino
doris.password=*******

跨源 JOIN 示例:一条 SQL 同时访问 Iceberg 湖仓、PostgreSQL 业务库与Doris 加速层:

-- 月度 GMV 归因:明细在湖仓,维表在业务库,用户分层在 Doris
SELECT c.region                                   AS 大区,
       substr(o.dt, 1, 7)                        AS 月份,
       sum(o.amount)                              AS gmv
FROM iceberg.lake.dwd_sales_orders o             -- 湖仓明细(Iceberg,主体事实表)
JOIN pg.crm.customers c                          -- PostgreSQL 业务库维表
  ON o.customer_id = c.id
JOIN doris.doris.user_profile u                  -- Doris 中的实时用户分层结果
  ON u.uid = c.uid
WHERE o.dt BETWEEN DATE '2026-07-01' AND DATE '2026-09-30'
  AND u.level IN ('V1', 'V2')
GROUP BY c.region, substr(o.dt, 1, 7)
ORDER BY gmv DESC;
提示:跨源 JOIN 时,小表(如维表)应尽量被自动识别为 build 侧并广播(broadcast); 若维表较大,建议先把维表定时同步入湖,避免每条查询都拉取远端数据。

内存管理:query.max-memory 三级限制

Trino 是内存 pinned 执行模型,用户内存(User Memory)在编译期即可预估。 集群通过三级配额防止单条 Ad-hoc 查询拖垮整体: query.max-memory(单查询全集群)、query.max-memory-per-node(单查询单节点用户内存)、 query.max-total-memory-per-node(单查询单节点用户 + 系统内存,含 exchange 缓冲)。

# etc/config.properties(coordinator 侧核心配置)
coordinator=true
node-scheduler.include-coordinator=false
http-server.http.port=8080
discovery.uri=http://trino-coord:8080

# 内存三级限制(示例:16 节点集群,每节点 64G 堆)
query.max-memory=600GB
query.max-memory-per-node=12GB
query.max-total-memory-per-node=16GB

# 集群内存紧张时,优先杀掉占用最多且被阻塞的查询,保护小查询
query.low-memory-killer.policy=total-reservation-on-blocked-nodes
# etc/jvm.config(worker 与 coordinator:堆外直内存 + G1)
-server
-Xmx64G
-XX:InitialRAMPercentage=80
-XX:MaxRAMPercentage=80
-XX:G1HeapRegionSize=32M
-XX:+ExplicitGCInvokesConcurrent
-XX:+HeapDumpOnOutOfMemoryError
-XX:+ExitOnOutOfMemoryError

Exchange 与执行并发调优

Exchange 是 Trino 节点间 Shuffle 数据的通道。大表 JOIN/聚合时 exchange 缓冲会占用大量内存, 2022 之后版本支持 spooling exchange(交换数据落盘),让超大 shuffle 不再 OOM:

# etc/config.properties(worker 侧)
# 超大 shuffle 落盘,用磁盘换内存稳定性
exchange.spooling.enabled=true
exchange.spooling.shared-storage-path=file:///data1/trino-spool,file:///data2/trino-spool
spiller.max-used-space=500GB

# 调度并发:单节点同时执行的 split 数与排队上限
node-scheduler.max-splits-per-node=256
node-scheduler.max-queued-splits-per-task=2000
最佳实践:Trino 调优的第一工具永远是 EXPLAIN ANALYZE——先看哪个 Stage 最慢、数据量在哪个算子膨胀,再针对性下推谓词、调整 join 策略或加分区过滤,而不是盲目改参数。
适用边界:Trino 擅长"读多写少"的交互式查询,不适合作为 ETL 主引擎—— 它没有容错重试的 job 级语义,大 shuffle 与大规模写入场景应交给 Spark/Flink。

Apache Doris:高并发实时 OLAP

架构定位:FE + BE 的实时分析引擎

Doris 是 MPP 架构的实时分析型数据库,MySQL 协议兼容让 BI 工具零成本接入。 集群由两类组件构成:

与 Trino"只算不存"不同,Doris 通常以"内表"形式持有数据(存算耦合部署,亦可选存算分离模式), 因此既承担查询也承担实时写入(Stream Load / Routine Load / INSERT INTO SELECT)。 在湖仓架构中,Doris 的典型角色是高并发加速层:湖仓保存全量冷数据, Doris 内表保存近期热数据,或通过 Multi-Catalog 直查湖仓。

表模型选型:Duplicate / Aggregate / Unique

建表时必须先回答"这份数据要不要去重、要不要预聚合",三种模型对应三类语义:

表模型 语义 典型场景 注意事项
Duplicate(明细模型) 不做任何聚合,重复行全部保留 ODS 明细入湖、日志宽表、明细圈选 最通用;聚合靠查询时算,配合 Rollup/MV 加速
Aggregate(聚合模型) Key 相同时 Value 按 AGG 类型合并(SUM/REPLACE/MAX/MIN/BITMAP) 流量统计、用户行为预聚合、PV/UV 查询结果与明细不完全等价;无法回查明细
Unique(主键模型) 主键唯一,新数据覆盖旧数据 订单状态流转、用户画像、维表同步 推荐 merge-on-write(写时合并),点查与聚合性能更好

三种模型的建表示例(同一份订单数据的三种建模姿势):

-- 1) Duplicate 明细模型:湖仓 ODS 层标准姿势,明细全保留
CREATE TABLE lake.dwd_order_detail (
    dt        DATE        NOT NULL,
    order_id  BIGINT      NOT NULL,
    user_id   BIGINT,
    sku_id    BIGINT,
    qty       INT,
    amount    DECIMAL(12, 2)
) ENGINE = OLAP
DUPLICATE KEY(dt, order_id)
PARTITION BY RANGE(dt) (
    PARTITION p202610 VALUES [("2026-10-01"), ("2026-11-01")),
    PARTITION p202611 VALUES [("2026-11-01"), ("2026-12-01"))
)
DISTRIBUTED BY HASH(user_id) BUCKETS 32
PROPERTIES (
    "replication_num" = "3",
    "store_row_column" = "true"      -- 行列混存:高频点查大宽表时显著提速
);

-- 2) Aggregate 聚合模型:按天按用户预聚合,写入即聚合
CREATE TABLE lake.dws_user_daily (
    dt          DATE        NOT NULL,
    user_id     BIGINT      NOT NULL,
    order_cnt   BIGINT      SUM,
    gmv         DECIMAL(16, 2) SUM,
    last_tag    VARCHAR(64) REPLACE,    -- 非 key 列必须声明聚合类型
    first_time  DATETIME    MIN
) ENGINE = OLAP
AGGREGATE KEY(dt, user_id)
PARTITION BY RANGE(dt) ()
DISTRIBUTED BY HASH(user_id) BUCKETS 16
PROPERTIES ("replication_num" = "3");

-- 3) Unique 主键模型:merge-on-write,CDC 同步订单状态首选
CREATE TABLE lake.ods_orders_pk (
    order_id   BIGINT      NOT NULL,
    user_id    BIGINT,
    status     TINYINT,
    amount     DECIMAL(12, 2),
    updated_at DATETIME
) ENGINE = OLAP
UNIQUE KEY(order_id)
DISTRIBUTED BY HASH(order_id) BUCKETS 32
PROPERTIES (
    "replication_num" = "3",
    "enable_unique_key_merge_on_write" = "true"
);
注意:Aggregate / Unique 模型中,Value 列的聚合类型一旦建表无法修改; 模型选错只能重建表导数。建模评审时请先确认消费方的查询语义(要看明细还是看聚合结果)。

Rollup 与物化视图加速

两者的共同思想是"空间换时间",由优化器自动匹配改写查询,对业务方完全透明:

-- Rollup:按天+商品聚合,报表按商品维度查询自动命中
ALTER TABLE lake.dwd_order_detail
ADD ROLLUP rollup_sku_day(dt, sku_id, qty, amount);

-- 异步物化视图:每小时刷新的分区级 GMV 汇总(支持湖仓外表参与)
CREATE MATERIALIZED VIEW lake.mv_city_gmv
BUILD IMMEDIATE
REFRESH AUTO ON SCHEDULE EVERY 1 HOUR
PROPERTIES ("grace_period" = "600")
AS
SELECT dt, city_id, sum(amount) AS gmv, count(DISTINCT user_id) AS uv
FROM lake.dwd_order_detail
GROUP BY dt, city_id;

-- 验证:查询被改写命中物化视图
EXPLAIN SELECT dt, sum(amount) FROM lake.dwd_order_detail
WHERE dt = '2026-11-01' GROUP BY dt;   -- 计划中出现 mv_city_gmv 即命中

湖仓外表:Multi-Catalog 直查 Iceberg/Hive/Hudi

Doris 的 Multi-Catalog 机制把外部数据源(Iceberg、Hive、Hudi、ES、JDBC 等)挂载为独立 catalog, 无需导入即可查询,还支持与内表 JOIN、作为物化视图的基表——这是它扮演 湖仓查询门户的关键能力:

-- 挂载 Iceberg catalog(Hive Metastore 型)
CREATE CATALOG iceberg_lake PROPERTIES (
    "type" = "iceberg",
    "iceberg.catalog.type" = "hive",
    "hive.metastore.uris" = "thrift://hive-meta:9083",
    "aws.s3.enable_path_style_access" = "true",
    "aws.s3.endpoint" = "https://oss.internal",
    "aws.s3.access_key" = "******",
    "aws.s3.secret_key" = "******"
);

-- 湖仓外表与 Doris 内表直接 JOIN:明细在湖,维表在库
SELECT o.dt, c.city_name, sum(o.amount) AS gmv
FROM iceberg_lake.lake.dwd_order_detail o   -- Iceberg 外表
JOIN lake.dim_city c ON o.city_id = c.id    -- Doris 内表维表
WHERE o.dt >= '2026-10-01'
GROUP BY o.dt, c.city_name;

-- 外表数据下沉:加热点分区到内表,获得极致并发
INSERT INTO lake.dwd_order_detail_hot
SELECT * FROM iceberg_lake.lake.dwd_order_detail
WHERE dt >= date_sub(current_date(), INTERVAL 7 DAY);
最佳实践:外表查询适合"低频全量/探查";高 QPS 报表请把热点分区下沉为内表 (可丢弃缓存语义,随时从湖仓重建),并为内表配上物化视图——三层递进:湖仓外表 → 内表热数据 → MV 聚合。

分区/分桶设计与 Colocate Join

-- 动态分区:按天滚动,保留 90 天,预建 7 天
ALTER TABLE lake.dwd_order_detail SET (
    "dynamic_partition.enable" = "true",
    "dynamic_partition.time_unit" = "DAY",
    "dynamic_partition.start" = "-90",
    "dynamic_partition.end" = "7",
    "dynamic_partition.prefix" = "p"
);

-- 订单表与用户画像表同组同桶,本地 JOIN 免 Shuffle
CREATE TABLE lake.dws_user_profile (
    user_id     BIGINT      NOT NULL,
    level       VARCHAR(8),
    city_id     INT,
    updated_at  DATETIME
) ENGINE = OLAP
UNIQUE KEY(user_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 32
PROPERTIES (
    "replication_num" = "3",
    "colocate_with" = "shop_user_group"   -- 与 dwd_order_detail 同组
);

-- 验证计划:出现 COLOCATE JOIN 即命中本地 JOIN
EXPLAIN SELECT /*+ SET_VAR(enable_nereids_planner=true) */
       p.level, count(*) AS orders
FROM lake.dwd_order_detail o
JOIN lake.dws_user_profile p ON o.user_id = p.user_id
GROUP BY p.level;
适用边界:Doris 的强项是高并发报表与实时更新查询;超大表复杂 Ad-hoc JOIN、 跨异构源联邦(几十个源)仍建议交给 Trino;Doris 内表副本数 × 数据量决定了它的持有成本, 不适合把全量历史冷数据都装进来。

ClickHouse:时序列存分析

架构定位与 MergeTree 家族

ClickHouse(CH)是面向真正的列式存储的 OLAP 引擎,以向量化执行、 稀疏索引、极致压缩著称。数据按 分片(Shard)→ 副本(Replica) 组织, DDL 建在本地表(如 MergeTree)之上,再由 Distributed 表做分布式路由。 其核心引擎是 MergeTree 家族——数据按 ORDER BY 键排序存储,后台不断合并(Merge)数据分块(Part), 家族成员按"合并时做什么"分化:

引擎 合并行为 典型场景
MergeTree 仅排序合并,不去重不聚合 通用明细表:日志、事件、行为流水
ReplacingMergeTree 按 ORDER BY 键去重,保留版本(ver)最大的行 有更新语义的同步表(订单状态),需最终一致
AggregatingMergeTree 按 ORDER BY 键合并 AggregateFunction 状态 预聚合中间层(配合物化视图做增量聚合)
SummingMergeTree 数值列按 ORDER BY 键求和 简单指标累加(金额、次数)
CollapsingMergeTree 用 sign=1/-1 折叠行,删撤事件 不断变化的统计(实时正确计数)
ReplicatedMergeTree 系 上述引擎 + 副本复制(基于 Replication Log) 所有生产表的标配前缀 Replicated*
-- 日志明细表:ReplicatedMergeTree + 跳数索引 + TTL
CREATE TABLE logs.log_events ON CLUSTER log_cluster
(
    event_time  DateTime64(3),
    server      LowCardinality(String),
    level       Enum8('DEBUG' = 1, 'INFO' = 2, 'WARN' = 3, 'ERROR' = 4),
    trace_id    String,
    message     String CODEC(ZSTD(3)),          -- 列级压缩算法,文本列高压缩比
    attrs       Map(String, String),
    INDEX idx_trace trace_id TYPE bloom_filter GRANULARITY 4   -- 跳数索引:等值查 trace_id
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/log_events', '{replica}')
PARTITION BY toDate(event_time)
ORDER BY (server, event_time, trace_id)        -- 排序键前缀决定查询快慢
TTL toDateTime(event_time) + INTERVAL 90 DAY   -- 行级 TTL:过期自动物理删除
SETTINGS index_granularity = 8192;

-- 分布式表:业务查询入口,按 sharding_key 路由到分片
CREATE TABLE logs.log_events_all ON CLUSTER log_cluster
AS logs.log_events
ENGINE = Distributed(log_cluster, logs, log_events, rand());

时序 / 日志场景为何占优

物化视图与 TTL

CH 的物化视图是插入触发的增量计算:源表每批写入,MV 按查询逻辑把结果增量写入目标表, 搭配 AggregatingMergeTree 可实现"边写边聚合",查询时用 *Merge 函数合并中间状态:

-- 错误指标按分钟预聚合,写入即触发,无需定时任务
CREATE MATERIALIZED VIEW logs.log_error_1m
ENGINE = AggregatingMergeTree
PARTITION BY toYYYYMM(window_start)
ORDER BY (window_start, server)
AS
SELECT toStartOfMinute(event_time) AS window_start,
       server,
       countState()                AS error_cnt,   -- 存聚合状态而非结果
       maxState(event_time)        AS last_err
FROM logs.log_events_all
WHERE level >= 3
GROUP BY window_start, server;

-- 查询:用 *Merge 函数合并状态,得到最终指标
SELECT window_start, server,
       countMerge(error_cnt) AS errors,
       maxMerge(last_err)    AS last_error_time
FROM logs.log_error_1m
WHERE window_start >= now() - INTERVAL 1 DAY
GROUP BY window_start, server
ORDER BY errors DESC;

-- 分层 TTL:热数据 SSD 保留 7 天,冷数据 HDD 保留 90 天后删除
ALTER TABLE logs.log_events
MODIFY TTL toDateTime(event_time) + INTERVAL 7 DAY TO VOLUME 'ssd_hot',
           toDateTime(event_time) + INTERVAL 90 DAY TO VOLUME 'hdd_cold',
           toDateTime(event_time) + INTERVAL 365 DAY DELETE;

ClickHouse vs Doris 详细对比

维度 Doris ClickHouse
架构 FE(元数据/计划)+ BE(存储/执行),自动分桶副本,部署简单 分片 + 副本,本地表 + Distributed 表,ZooKeeper/Keeper 协调复制
高并发 强:简单查询可承载数百~上千 QPS,并发模型友好 中:大查询吃满 CPU,高并发需限流/读写分离/预聚合兜底
JOIN 能力 强:多表 JOIN、Colocate、Bucket Shuffle、MV 透明改写 偏弱:推荐大宽表;JOIN 需谨慎选择算法与内存
数据更新 Unique MoW 支持高频 upsert,准实时可见 ReplacingMergeTree 异步合并去重,查询需 FINAL(有代价)
湖仓生态 Multi-Catalog 直查 Iceberg/Hive/Hudi,可回写 Iceberg/HDFS 引擎表可查但能力较浅,主打自有表格式
协议与工具 MySQL 协议,BI/应用无缝接入 原生 TCP + HTTP + JDBC,生态偏观测/日志平台(Grafana 友好)
运维复杂度 中:扩缩容自动均衡,学习曲线平缓 高:合并调优、分片规划、ZK 依赖、故障手工介入多
适用边界:ClickHouse 在"单表大宽表扫描 + 时序日志检索"上无出其右; 但高并发固定报表、频繁 upsert、多表关联的"经典数仓语义"场景,Doris 通常更省心。 湖仓体系中 CH 数据一般通过同步链路引入(自建副本),而非直读湖仓。

三引擎选型对比

三个引擎并非"三选一"的竞争关系,而是按查询模式分工的协作关系。 下表从六个维度给出横向对比,作为选型决策的速查表:

维度 Trino Doris ClickHouse
查询延迟定位 秒 ~ 分钟级(交互式 Ad-hoc) 亚秒 ~ 秒级(固定报表) 毫秒 ~ 秒级(单表扫描聚合)
并发能力 中:数十~数百并发,受大查询挤占影响 高:简单查询可达千级 QPS 中低:大查询独占资源,需预聚合兜底
JOIN 能力 最强:跨源、大表、CBO 优化成熟 强:Colocate/Bucket Shuffle/MV 改写 偏弱:大宽表哲学,JOIN 是短板
湖仓支持 原生 Connector 一等公民,读 Iceberg/Delta/Hudi Multi-Catalog 读 Iceberg/Hive/Hudi + 数据下沉 Iceberg 等外表可查但较浅,多同步自建
数据更新 依赖表格式自身(写入场景弱) Unique MoW 高频 upsert 后台合并去重,异步最终一致
运维复杂度 中:进程无状态,K8s 友好 中:FE/BE 自动化程度高 高:合并/分片/副本/ZK 需持续关注

场景决策树

交互式联邦 → Trino

数据分析师在 Notebook 里做探索、需要 JOIN 业务库/湖仓/第三方库、 查询模式不可预知、数据规模在 PB 级以内但延迟容忍秒级。

Ad-hoc 跨源 JOIN SQL 方言标准

高并发报表 → Doris

固定报表、实时大屏、面向 C 端的数据服务 API:查询模式收敛、要求亚秒与高 QPS、 维表关联与指标口径统一管理。

实时大屏 QPS 敏感 upsert 语义

海量日志时序 → ClickHouse

日志检索、APM 指标、安全风控的行为流分析:数据体量巨大、按时间追加、 以扫描聚合与圈选为主,几乎不做复杂 JOIN。

日志/APM 高压缩比 明细圈选
推荐组合:湖仓统一存储(Iceberg)+ Trino 做联邦与探查入口 + Doris 承接高并发固定消费 + ClickHouse 承接日志时序类专用负载。 每个引擎只做它最擅长的事,且都可从湖仓重建加速数据。
反模式:把 Doris 当 ETL 引擎跑复杂多阶段转换;用 ClickHouse 做高并发多表 JOIN 报表; 让 Trino 承载千 QPS 的线上 API 查询——这些都是把引擎用在短板上的典型事故来源。

查询加速共性手段

无论选用哪个引擎,查询加速的底层逻辑是相通的:少扫数据、少算数据、不重复算。 以下五类手段在 Trino/Doris/ClickHouse 以及表格式侧各有对应实现:

1. 缓存

2. 分区裁剪

查询条件命中分区列时,引擎在计划期直接跳过无关分区的全部文件。Iceberg 的隐藏分区 (partition by days(ts))让业务方按原始列 WHERE ts >= ... 查询也能裁剪, 避免了 Hive 时代"分区函数写错导致全表扫"的经典事故。

3. 谓词下推与文件跳过

引擎把 WHERE 条件尽量下推到存储层:Parquet 的 Row Group 级 min/max 统计、Bloom Filter, 加上 Iceberg 记录在 manifest 里的文件级统计,可以在不读文件的情况下判断"这个文件要不要打开"。

4. 物化视图与预聚合

高频固定的聚合查询(大屏、日报)应沉到物化层:Doris Async MV、CH AggregatingMergeTree+MV、 或直接在湖仓侧用 Spark/Flink 定时生成 DWS 汇总表(引擎无关,Trino/Doris 都能查)。 判断标准:查询模式收敛 + 消费频率高 + 口径稳定。

5. 排序与聚簇

Iceberg 统计信息(write.metrics)对引擎的价值

Iceberg 在 manifest 中为每个数据文件记录列级统计(min/max、null 计数、NaN 计数、值计数), 由写入侧表属性 write.metadata.metrics.default 控制收集粒度。 Trino/Doris/Spark 读表时利用这些统计做文件跳过(File Skipping), 统计越精细,跳过越激进,扫描 I/O 越少:

-- 建表时设置统计收集模式
CREATE TABLE lake.dwd_sales_orders (
    dt          DATE,
    order_id    BIGINT,
    city_id     INT,
    user_id     BIGINT,
    amount      DECIMAL(12, 2)
) USING iceberg
PARTITIONED BY (days(dt))
TBLPROPERTIES (
    'write.metadata.metrics.default' = 'truncate(16)',          -- 字符串列截断统计,均衡体积与精度
    'write.metadata.metrics.column.structures'  = 'full',       -- 高基数 ID 列用全文统计
    'write.metadata.metrics.column.contents'    = 'none'        -- 大字段不收集,控制元数据膨胀
);
metrics 模式 收集内容 适用建议
none 仅记录行数 大对象/超宽表,元数据优先
counts 行数 + null/NaN 计数 过滤主要靠分区与排序列时
truncate(N) counts + 截断到 N 字节的 min/max 默认推荐:字符串列防元数据膨胀
full counts + 完整 min/max 定长数值/高基数过滤列,跳过率最优
最佳实践:湖仓表的"排序键 + 分区键 + metrics 精度"三件套应在建模评审时一并确定—— 它们决定了所有下游引擎的文件跳过效率,是投入产出比最高的查询加速投资。

适用边界

何时不引入新引擎

何时不该用 OLAP 引擎做 ETL

总结:查询引擎层的设计准则是——以开放表格式为统一底座,按查询模式(联邦/并发/时序) 选择引擎,用共性加速手段(分区、下推、MV、排序、缓存)把每个引擎的能力兑现, 并时刻警惕引擎蔓延与角色错配。