Doris 学习笔记(二):进阶与实战

写在前面

承接上一篇,本文聚焦 Doris 的实战核心:表设计、分区与分桶、索引体系、查询优化、物化视图、Colocate Join,以及典型应用场景(实时数仓 / 多维分析)。基于 Doris 2.1.x。


一、表设计核心:分区与分桶

Doris 一张表的物理结构是 Partition(分区)→ Bucket(分桶)→ Tablet(数据片)。表设计是否合理,80% 取决于分区和分桶。

1.1 分区(Partition)

分区是逻辑切分,最常见的是按时间分区。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
CREATE TABLE access_log (
    dt DATE, ts DATETIME, user_id BIGINT, page VARCHAR(128), cost_ms INT
)
DUPLICATE KEY(dt, ts, user_id)
PARTITION BY RANGE(dt) ()
DISTRIBUTED BY HASH(user_id) BUCKETS 4
PROPERTIES(
  "dynamic_partition.enable" = "true",
  "dynamic_partition.time_unit" = "DAY",
  "dynamic_partition.start" = "-30",       -- 保留最近 30 天
  "dynamic_partition.end"   = "3",         -- 预创建未来 3 天
  "dynamic_partition.prefix" = "p",
  "dynamic_partition.replication_num" = "1"
);
1
2
3
4
5
6
7
8
动态分区:
  - 自动按天创建/删除分区
  - 常用配置:start=-30(删历史 30 天前的),end=3(预创建 3 天)

为什么必须分区:
  - 按时间裁剪:SELECT ... WHERE dt='2026-06-01' 只扫一个分区
  - 生命周期管理:TTL 删旧分区,比逐行删除快几个数量级
  - 避免扫描全表:OLAP 查询 99% 都是按时间筛选

1.2 分桶(Bucket / Tablet)

分桶是物理切分,决定数据如何分布到 BE 节点,以及查询并行度。

1
DISTRIBUTED BY HASH(user_id) BUCKETS 8
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
关键选择:
  1. 分桶列:
     - 选高基数、常用于过滤/JOIN 的列
     - 一般选 user_id、order_id 等
     - 不要选 dt(dt 已经是分区列,重复使用没意义)
     - 不要选低基数列(如性别:会造成数据倾斜)

  2. 分桶数(BUCKETS):
     - 单 Tablet 大小推荐 1~10GB
     - 估算:每天数据量 / (单 Tablet 大小) = 分桶数
     - 例:每天 100GB 数据,单 Tablet 2GB → BUCKETS = 50

  3. 并发度:
     - 一条查询的并行度 ≈ 涉及 Tablet 数
     - 太少(如 1 个)查询无法并行
     - 太多(如几千个)元数据开销大

1.3 估算分桶数的公式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
假设:
  - 日增量 100GB(含索引、压缩后实际存储约 30GB)
  - 单 Tablet 推荐 1~2GB
  - 按天分区

分桶数 = 单分区数据量 / 单 Tablet 大小
       = 30GB / 1.5GB
       ≈ 20 桶

后验:通过 SHOW PARTITIONS 查看每个 Tablet 的大小,调整。

二、索引体系

Doris 的索引分两类:内置索引(建表自动)和显式索引(用户创建)。

2.1 前缀索引(Prefix Index)

Doris 默认对Key 列的前 36 字节建立前缀索引,类似 MySQL 的聚簇索引。

1
2
3
4
5
CREATE TABLE t (
    dt DATE, ts DATETIME, user_id BIGINT, page VARCHAR(128)
)
DUPLICATE KEY(dt, ts, user_id)   -- 这三列自动建立前缀索引
...
1
2
3
4
5
6
7
8
9
含义:
  WHERE dt='2026-06-01'                      ✅ 走前缀索引
  WHERE dt='2026-06-01' AND ts > '10:00'      ✅ 走前缀索引
  WHERE user_id = 1001                        ❌ 不走(跳过了 dt、ts)

设计建议:
  - Key 列顺序按"查询过滤频率从高到低"排
  - 通常顺序:dt → ts → user_id → ...
  - Key 列总长度 ≤ 36 字节(超出部分不进入前缀索引)

2.2 倒排索引(Inverted Index)

Doris 2.0+ 引入,对字符串/数值列做全文检索,类似 Elasticsearch。

1
2
3
4
5
6
7
ALTER TABLE access_log
ADD INDEX idx_page (page) USING INVERTED
PROPERTIES("parser" = "english")
;

ALTER TABLE access_log ADD INDEX idx_user (user_id) USING INVERTED;
ALTER TABLE access_log BUILD INDEX;
1
2
3
4
5
-- 用法:直接 WHERE,自动用索引
SELECT * FROM access_log
WHERE page MATCH_ANY 'home list detail';

SELECT * FROM access_log WHERE user_id = 1001;
1
2
3
4
特点:
  - 大幅提升等值/范围查询
  - 支持全文检索(MATCH_ALL / MATCH_ANY)
  - 替代了老版本的 BITMAP_INDEX(更通用)

2.3 Bloom Filter 索引

针对高基数列的等值查询,简单且高效:

1
2
3
ALTER TABLE access_log SET (
  "bloom_filter_columns" = "user_id,page"
);
1
2
3
4
5
6
7
8
9
适用:
  - 高基数列(user_id, trace_id 等)
  - 等值查询(=)
  - IN 列表查询

不适用:
  - 低基数列(性别、状态)
  - 范围查询(>, <)
  - 加入了反而浪费空间

2.4 索引选型

场景 推荐索引
Key 列前缀范围查询 前缀索引(自动)
高基数列等值 Bloom Filter
字符串全文检索 倒排索引
低基数列等值/范围 倒排索引

三、查询优化

3.1 CBO 优化器

Doris 默认开启 CBO(Cost-Based Optimizer)。

1
2
3
-- 查看实际执行计划
EXPLAIN VERBOSE
SELECT dt, COUNT(*) FROM access_log GROUP BY dt;
1
2
3
4
5
6
CBO 会考虑:
  - 表的统计信息(行数、基数、选择率)
  - JOIN 顺序(小表驱动大表)
  - 是否广播 vs 重分布

需要:定期 ANALYZE TABLE 让统计信息新鲜
1
ANALYZE TABLE access_log WITH SYNC;

3.2 JOIN 策略

Doris 有三种 JOIN 执行方式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
1. Broadcast Join(广播)
   小表(< 1GB 默认)广播到所有 BE 节点
   适合:大表 JOIN 小表(如事实表 JOIN 维度表)

2. Shuffle Join(重分布)
   两表都按 JOIN KEY 重分布,再本地 JOIN
   适合:大表 JOIN 大表

3. Colocate Join(同分布,零 Shuffle)
   两表 JOIN KEY 类型一致、分桶方式一致、副本一致
   适合:频繁 JOIN 的大表(如订单表 JOIN 用户表)

Colocate Join 配置示例

1
2
3
4
5
6
7
8
-- 1. 建一个 colocate group
ALTER TABLE orders SET ("colocate_with" = "user_grp");
ALTER TABLE users  SET ("colocate_with" = "user_grp");

-- 2. 两表都按 user_id 分桶,自动按相同方式分布
-- 3. JOIN 时 FE 识别到 colocate,跳过网络 Shuffle
SELECT a.order_id, b.name
FROM orders a JOIN users b USING(user_id);
1
2
3
性能差异(实测):
  Shuffle Join:  ~10s(网络 + 落盘)
  Colocate Join: ~1s(零 Shuffle)

3.3 Runtime Filter

大表 JOIN 小表时,FE 会动态生成一个过滤条件下推到扫描节点。

1
2
3
4
5
-- 大表 JOIN 小表,会自动启用 Runtime Filter
SELECT count(*)
FROM access_log a
JOIN user_dim b ON a.user_id = b.user_id
WHERE b.country = 'CN';
1
2
3
4
5
6
原理:
  1. 先扫描 user_dim(小表)过滤后拿到 user_id 集合
  2. 把这个集合推给 BE 节点扫描 access_log 时使用
  3. 大表扫描时直接过滤掉不匹配的行

效果:扫描数据量减少 10~1000 倍

3.4 常见慢查询排查

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
-- 1. 看查询 Profile
SET enable_profile = true;
-- 跑一次慢查询,然后 Web UI → QueryProfile 查看每一步耗时

-- 2. 慢的常见原因
--    - 没走分区裁剪:扫描了所有分区
--    - 没走索引:Key 列顺序设计不当
--    - 数据倾斜:某个 Tablet 特别大
--    - JOIN 错:用了 Shuffle 而非 Colocate

-- 3. 看 Tablet 分布是否均衡
SHOW TABLET FROM access_log;

四、物化视图(Materialized View)

物化视图 = 预计算 + 自动重写查询。

4.1 同步物化视图(Rollup)

适合单表聚合加速,写入时自动维护:

1
2
3
4
CREATE MATERIALIZED VIEW mv_user_pv AS
SELECT dt, user_id, COUNT(*) AS pv
FROM access_log
GROUP BY dt, user_id;
1
2
3
4
原理:
  - 创建后,新写入的数据会同时写入主表和物化视图
  - 查询时优化器自动判断是否使用物化视图
  - 用户无需改 SQL,对应用透明

4.2 异步物化视图(Multi-Table)

Doris 2.1+ 支持,可以跨表、跨库聚合:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
CREATE MATERIALIZED VIEW mv_daily_summary
DISTRIBUTED BY HASH(dt) BUCKETS 4
REFRESH ASYNC EVERY(INTERVAL 1 HOUR)  -- 每小时异步刷新
AS
SELECT
    a.dt,
    b.country,
    COUNT(*) AS pv,
    COUNT(DISTINCT a.user_id) AS uv,
    SUM(a.cost_ms) AS total_cost
FROM access_log a
JOIN user_dim b ON a.user_id = b.user_id
GROUP BY a.dt, b.country;
1
2
3
4
适用场景:
  - 复杂的实时大盘报表
  - 上百个 Dashboard 的指标预聚合
  - 流量减少:把 1TB 大表查询变成 10GB 物化视图查询

4.3 物化视图 vs Rollup

维度 Rollup(同步) 异步物化视图
单表/多表 单表 单/多表
维护 写入时同步 异步刷新
延迟 实时 分钟级
复杂度 简单 较复杂
推荐 简单聚合 复杂报表

五、数据导入实战

5.1 Stream Load(实时小批量)

1
2
3
4
5
6
curl -u root: \
     -H "label:order_20260614_001" \
     -H "column_separator:," \
     -H "columns:order_id,user_id,amount,created_at" \
     -T /tmp/orders.csv \
     http://localhost:8030/api/demo/orders/_stream_load
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
关键参数:
  - label:导入的唯一标识,保证幂等(同一 label 不会重复导入)
  - column_separator:CSV 分隔符
  - columns:CSV 列顺序 → 表列映射

返回值(成功示例):
  {
    "Status": "Success",
    "NumberTotalRows": 1000,
    "NumberLoadedRows": 1000
  }

5.2 Routine Load(消费 Kafka)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
CREATE ROUTINE LOAD rl_orders ON orders
WITH PROPERTIES (
    "format" = "json",
    "jsonpaths" = "[\"$.order_id\",\"$.user_id\",\"$.amount\",\"$.ts\"]"
)
FROM KAFKA (
    "kafka_broker_list" = "kafka:9092",
    "kafka_topic" = "orders",
    "kafka_group_id" = "doris_orders"
);
1
2
3
4
5
6
7
特点:
  - 持续消费 Kafka topic
  - 支持 Exactly-Once(通过 label + Kafka offset)
  - 自动容错:BE 失败自动重试

典型链路:
  MySQL → Canal/Debezium → Kafka → Doris Routine Load → 实时数仓

5.3 Broker Load(批量导入)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
LOAD LABEL demo.broker_load_001
(
    DATA INFILE("hdfs://namenode:8020/data/orders/*.parquet")
    INTO TABLE orders
    FORMAT AS PARQUET
)
WITH BROKER "broker1"
PROPERTIES (
    "timeout" = "3600"
);

适合一次性大规模历史数据迁移(数百 GB ~ TB)。


六、典型应用场景

6.1 实时数仓

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
数据链路:
  业务 DB → Canal → Kafka → Doris Routine Load
  Doris 实时表(明细模型 / 主键模型)
  Doris 物化视图(按小时/天聚合)
  BI / 报表查询

特点:
  - 秒级延迟(数据写入即可查)
  - 主键模型保证 UPSERT 一致性
  - BI 直接用 MySQL 协议连接

6.2 多维分析(OLAP)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-- 用户行为分析:UV / PV / 漏斗
SELECT
    dt,
    page,
    COUNT(DISTINCT user_id) AS uv,
    COUNT(*) AS pv,
    AVG(cost_ms) AS avg_cost
FROM access_log
WHERE dt >= '2026-06-01'
GROUP BY dt, page
ORDER BY dt, uv DESC;
1
2
3
4
为什么 Doris 适合:
  - BITMAP_UNION 高效去重(UV 计算)
  - 列存 + 向量化扫描快
  - 物化视图加速常用维度组合

6.3 日志检索

1
2
3
4
5
6
-- 利用倒排索引做日志搜索
SELECT * FROM access_log
WHERE page MATCH_ALL 'home error'
  AND ts >= '2026-06-14 00:00:00'
ORDER BY ts DESC
LIMIT 100;

新版本 Doris 的倒排索引能力,可以部分替代 Elasticsearch 做日志场景。


七、小结

本文学习了 Doris 的进阶内容:

  • 表设计核心:分区(按时间)+ 分桶(按基数列)+ 副本
  • 索引体系:前缀索引、倒排索引、Bloom Filter
  • 查询优化:CBO 优化器、JOIN 策略、Colocate Join、Runtime Filter
  • 物化视图:同步 Rollup(单表)和异步物化视图(多表)
  • 数据导入:Stream Load、Routine Load、Broker Load
  • 典型场景:实时数仓、多维分析、日志检索
1
2
3
4
5
6
落地建议:
  - 表设计:先按时间分区,再选高基数列分桶,BUCKETS 数按数据量算
  - 模型选择:明细用 Duplicate,更新用 Primary Key
  - 索引:低基数 + 字符串检索 → 倒排;高基数等值 → Bloom Filter
  - JOIN:维度表用 Broadcast,事实表频繁 JOIN 用 Colocate
  - 加速:高频聚合查询用物化视图,复杂报表用异步物化视图

Doris 的核心价值在于用极简的部署 + MySQL 协议,覆盖实时数仓和 OLAP 分析两大场景。后续如果需要进一步深入,可以研究:

  • FE 元数据选主机制(BDB-JE)
  • BE 向量化执行引擎
  • CBO 优化器的代价模型
  • 存算分离架构(Doris 3.x Cloud 版)