Doris 学习笔记(一):基础与架构

写在前面

本文是 Doris 学习笔记系列的第一篇,介绍 Apache Doris 的核心定位、FE/BE 架构、四种数据模型的差异,以及 Docker 部署和基础 SQL 操作。基于 Doris 2.1.x 版本。


一、Doris 是什么

1.1 定义

Apache Doris 是一个高性能、实时的分析型数据库(OLAP MPP 数据库),最初由百度 PALO 项目开源,2018 年进入 Apache 孵化器,2022 年成为顶级项目。

1
2
3
4
5
6
核心特征:
1. MPP 架构      — 大规模并行处理,查询可水平扩展
2. MySQL 协议    — 兼容 MySQL 语法,可用 MySQL 客户端直接连接
3. 实时写入      — Stream Load / Routine Load 支持秒级数据可见
4. 列式存储      — 高压缩比 + 向量化执行,OLAP 查询效率高
5. 不需要外部依赖 — FE + BE 两类节点,部署简单(不需要 Hadoop/Spark)

1.2 解决什么问题

1
2
3
4
5
6
7
8
9
传统离线数仓(Hive / HDFS + Spark):
  - 数据延迟:T+1,今天看昨天的数据
  - 查询慢:交互式 BI 查询几秒到几十秒
  - 链路复杂:HDFS、YARN、Hive、Spark、Presto……

Doris 试图解决:
  - 实时数仓:Flink/写入 → 秒级 / 分钟级数据可见
  - 高并发查询:单集群可扛上千 QPS 的多维分析
  - 极简运维:FE + BE 两类角色,二进制部署

1.3 同类产品对比

维度 Doris ClickHouse StarRocks Druid
出身 百度 → Apache Yandex Doris 商业分支 Apache
SQL 兼容 MySQL 协议 自有方言 MySQL 协议 有限
实时写入 强(Stream Load) 弱(合并机制)
JOIN 能力 中上(Colocate/Broadcast/Shuffle) 强(大表 JOIN)
物化视图 支持 不支持(用 Projection) 支持 支持 Rollup
运维复杂度 简单 中等 简单 复杂
1
2
3
4
一句话总结:
  - 选 Doris:开箱即用、运维简单、实时 + OLAP 兼顾、生态完整
  - 选 ClickHouse:单表极致性能、JOIN 强大、超大规模
  - 选 StarRocks:Doris 升级版,外表联邦查询更好,性能更激进

1.4 什么是联邦查询

联邦查询(Federated Query) 指查询引擎在查询时直接访问外部异构数据源(MySQL、Hive、Iceberg、Hudi、Kafka、ES 等),把外部表当本地表来查,甚至跨源 JOIN,不实际搬移数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
订单表在 MySQL + 行为日志在 Doris 本地 + 用户画像在 Hive

→ 一条 SQL 直接 JOIN,Doris/StarRocks 自己协调怎么拉、怎么算

  SELECT m.province, SUM(m.amount), COUNT(DISTINCT l.user_id)
  FROM mysql_catalog.db.orders  m          -- MySQL 外表
  JOIN local.behavior_log       l USING(user_id)  -- 本地表
  JOIN hive_catalog.db.profile  h USING(user_id)  -- Hive 外表
  WHERE m.dt >= '2026-06-07'
  GROUP BY m.province;

联邦查询 vs 传统 ETL:

方式 数据搬移 实时性 维护成本
ETL 进数仓 需要 有延迟(T+1 / 分钟级) 高(多任务 + 调度 + 对账)
联邦查询 不需要 实时 低(建个外表即可)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
典型应用:
  - 数据湖分析:直接查 S3/HDFS 上的 Iceberg/Hudi/Delta/Paimon
  - 跨库报表:业务库 MySQL + 数据仓库 Hive 不想 ETL 又想统一查询
  - 避免重复存储:数据湖一份,多个引擎查询

StarRocks 为什么在联邦查询上更强:
  - Catalog 体系成熟早,Iceberg/Hudi/Delta Native Reader 优化激进
    (直接读 Parquet/ORC,不经 Hive Metastore 中转)
  - CBO 优化器对外表统计信息、谓词下推、分区裁剪做得更细
  - 实测 Iceberg 大表查询比 Doris 快 1.5~2 倍

Doris 也补齐了大部分外表能力(Hive / Iceberg / JDBC Catalog 等),
但对数据湖 + 联邦这一块仍以 StarRocks 为优。

二、架构

2.1 整体架构

Doris 是存算耦合架构(2.1 起开始支持存算分离模式,但默认还是存算耦合)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
                    ┌────────────────────────────────────┐
                    │            客户端 / 应用             │
                    │   (MySQL Client / JDBC / Stream)    │
                    └─────────────────┬──────────────────┘
                  ┌───────────────────────────────────────┐
                  │             FE (Frontend)             │
                  │  元数据 / SQL 解析 / 查询计划 / 调度    │
                  │                                       │
                  │  ┌──────┐  ┌──────┐  ┌──────┐         │
                  │  │ FE-1 │  │ FE-2 │  │ FE-3 │         │
                  │  │Master│←→│Follw.│←→│Follw.│         │
                  │  └──────┘  └──────┘  └──────┘         │
                  └─────────────────┬─────────────────────┘
              ┌─────────────────────┼─────────────────────┐
              ▼                     ▼                     ▼
        ┌──────────┐          ┌──────────┐          ┌──────────┐
        │   BE-1   │          │   BE-2   │          │   BE-3   │
        │ 存储+计算 │          │ 存储+计算 │          │ 存储+计算 │
        └──────────┘          └──────────┘          └──────────┘

2.2 FE(Frontend)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
职责:
  - 元数据管理(数据库、表、分区、副本位置)
  - SQL 解析、查询优化(CBO 优化器)
  - 查询计划生成与调度
  - 元数据高可用(基于 BDB-JE)

角色:
  - Master FE:唯一,负责元数据写入
  - Follower FE:参与选主,可读元数据
  - Observer FE:只读,仅用于扩展查询能力

推荐部署:
  - 至少 3 个 FE(1 Master + 2 Follower),保证多数派可用
  - Observer 用于读扩展,可以任意数量

2.3 BE(Backend)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
职责:
  - 数据存储(按 Tablet 分片,副本存放)
  - 查询执行(向量化执行引擎)
  - 数据导入(Stream Load / Routine Load 实际处理者)

存储结构:
  - 物理上:Table → Partition → Tablet → Rowset → Segment
  - Tablet 是数据分片和副本管理的最小单位
  - 默认每张表 3 副本(可配置)

推荐部署:
  - 至少 3 个 BE(满足默认 3 副本分布)
  - 每个 BE 单独部署在一台物理机 / 容器

2.4 一条查询的执行流程

1
2
3
4
5
6
7
8
9
1. 客户端发送 SQL 到任意 FE
2. FE 解析 SQL → 生成逻辑计划
3. CBO 优化器生成最优物理计划
4. FE 将计划分发到 BE 节点
5. BE 并行执行(MPP)
   - 扫描节点(Scan Node):读本地数据
   - 计算:过滤、聚合、JOIN
   - 数据通过网络 Shuffle / Broadcast
6. 汇总结果到 FE,返回客户端

三、Docker 部署

最快的体验方式是用官方的 Docker 镜像跑一个单机版(1 FE + 1 BE)。

3.1 网络

1
docker network create doris-net

3.2 启动 FE

1
2
3
4
5
6
7
docker run -d \
  --name doris-fe \
  --network doris-net \
  -p 8030:8030 \
  -p 9030:9030 \
  -e FE_SERVERS="fe1:172.20.80.2:9010:9020:9030" \
  apache/doris:fe-2.1.7
1
2
3
4
5
端口说明:
  - 8030:FE Web UI
  - 9030:MySQL 协议端口(用 mysql 客户端连接)
  - 9010:FE 内部通信
  - 9020:FE 元数据编辑日志

3.3 启动 BE

1
2
3
4
5
6
7
docker run -d \
  --name doris-be \
  --network doris-net \
  -p 8040:8040 \
  -e BE_ADDR="172.20.80.3:9050" \
  -e FE_SERVERS="fe1:172.20.80.2:9010:9020:9030" \
  apache/doris:be-2.1.7

3.4 添加 BE 到集群

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 用 mysql 客户端连接 FE
mysql -h 127.0.0.1 -P 9030 -uroot
# 默认无密码

# 查看 FE 状态
SHOW FRONTENDS;
# Expected:Alive = true

# 查看 BE 状态(首次为空)
SHOW BACKENDS;

# 添加 BE
ALTER SYSTEM ADD BACKEND "172.20.80.3:9050";

# 再次查看
SHOW BACKENDS;
# Expected:Alive = true,HeartbeatPort = 9050

3.5 Web UI

访问 http://localhost:8030 查看集群状态:

  • System Info → 看 FE / BE 列表
  • PlayGround → 直接在网页上跑 SQL

四、数据模型

Doris 提供 4 种数据模型,区别在于"同一主键(或维度)的数据如何处理"。选对模型是 Doris 表设计的核心。

4.1 明细模型(Duplicate Key)

最简单:所有写入的数据原样保留,不合并。适合日志、明细数据。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
CREATE TABLE event_log (
    event_time DATETIME NOT NULL,
    user_id    BIGINT,
    event_type VARCHAR(32),
    page       VARCHAR(128),
    duration   INT
)
DUPLICATE KEY(event_time, user_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 8
PROPERTIES("replication_num" = "1");
1
2
3
4
特点:
  - DUPLICATE KEY 仅用于数据排序,不合并
  - 写入即可查,无需任何聚合
  - 适合:原始日志、明细数据

4.2 聚合模型(Aggregate)

预先声明聚合方式(SUM / MIN / MAX / REPLACE / BITMAP_UNION / HLL_UNION),相同 Key 的数据自动合并。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
CREATE TABLE user_metric_agg (
    dt          DATE,
    user_id     BIGINT,
    pv          BIGINT SUM,        -- 求和
    uv          BITMAP BITMAP_UNION, -- BITMAP_UNION 计数
    stay_sec    INT MAX              -- 取最大
)
AGGREGATE KEY(dt, user_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 8
PROPERTIES("replication_num" = "1");
1
2
3
4
5
6
7
8
应用场景:
  - 数据仓库的汇总表(按天/小时聚合)
  - UV 统计(BITMAP_UNION)
  - 适合:查询前数据需要聚合的场景

注意:
  - 写入相同 Key 多次,只保留聚合结果
  - 读取时自动按 Key 再做一次合并

4.3 唯一模型(Unique Key,旧版)

保证主键唯一,后写覆盖前写。适合主键更新场景,但读性能不如主键模型

1
2
3
4
5
6
7
8
9
CREATE TABLE user_profile (
    user_id     BIGINT,
    name        VARCHAR(32),
    age         INT,
    updated_at  DATETIME
)
UNIQUE KEY(user_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 8
PROPERTIES("replication_num" = "1");
1
2
3
4
5
6
7
8
特点(旧版 Unique):
  - 实现:Read 时合并(Merge on Read)
  - 写入:多次写同主键,最后写入的"胜出"
  - 适合:维度表、用户画像(不频繁更新)

性能提示:
  - 旧版 Unique 查询会触发合并,性能不如主键模型
  - 新版本建议优先用 Primary Key 模型

4.4 主键模型(Primary Key,新版推荐)

Doris 1.2+ 引入,真正支持高效 UPSERT 和部分列更新

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
CREATE TABLE orders (
    order_id    BIGINT,
    user_id     BIGINT,
    status      VARCHAR(16),
    amount      DECIMAL(10, 2),
    created_at  DATETIME
)
PRIMARY KEY(order_id)
DISTRIBUTED BY HASH(order_id) BUCKETS 8
PROPERTIES(
  "replication_num" = "1",
  "enable_unique_key_merge_on_write" = "true"
);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
特点:
  - 主键唯一约束,UPSERT 自动覆盖
  - enable_unique_key_merge_on_write = true 时
    → 写时合并(Copy on Write),读性能接近明细模型
  - 支持部分列更新(UPDATE col1 = ... WHERE pk = ...)

应用场景:
  - 订单表(订单状态机:created → paid → shipped)
  - 实时画像(频繁更新某些字段)
  - CDC 同步(MySQL binlog 实时写入)

4.5 四种模型对比

模型 主键语义 合并时机 适合场景 UPSERT 支持
Duplicate 仅排序 不合并 日志、明细
Aggregate 聚合维度 写入 + 读取 汇总表、UV/PV ❌(覆盖语义)
Unique(旧) 唯一 读取时合并 维度表 ✅(但慢)
Primary(新) 唯一 写入时合并 订单、画像、CDC ✅(高性能)
1
2
3
4
5
选型建议:
  - 日志/原始明细 → Duplicate
  - 已经按维度聚合过的统计表 → Aggregate
  - 需要主键更新(订单/画像/CDC) → Primary Key
  - 旧版 Unique 除非兼容老系统,否则用 Primary 替代

五、基础 SQL 操作

5.1 建库

1
2
CREATE DATABASE demo;
USE demo;

5.2 建表(示例:明细模型)

 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) (
    PARTITION p20260601 VALUES [('2026-06-01'), ('2026-06-02')),
    PARTITION p20260602 VALUES [('2026-06-02'), ('2026-06-03'))
)
DISTRIBUTED BY HASH(user_id) BUCKETS 4
PROPERTIES("replication_num" = "1");
1
2
3
4
关键概念:
  PARTITION(分区):按 dt 按天分区,便于按时间裁剪和管理
  BUCKETS(分桶):分区内按 user_id 哈希分桶,控制并行度
  副本:replication_num = 1(单机测试,生产至少 3)

5.3 数据导入

Doris 提供多种导入方式,最常用的是 Stream Load(HTTP 推送):

1
2
3
4
5
# 通过 curl 推送 CSV
curl -u root: -H "label:load_001" \
     -H "column_separator:," \
     -T data.csv \
     http://localhost:8030/api/demo/access_log/_stream_load
1
2
3
4
5
-- 也可以在 SQL 客户端用 INSERT 插入
INSERT INTO access_log VALUES
('2026-06-01', '2026-06-01 10:00:00', 1001, '/home', 12),
('2026-06-01', '2026-06-01 10:01:00', 1001, '/list',  35),
('2026-06-01', '2026-06-01 10:02:00', 1002, '/home',  10);
1
2
3
4
5
导入方式对比:
  Stream Load   — HTTP 推送,适合小批量 / 实时写入
  Routine Load  — 持续消费 Kafka,做实时数仓
  Broker Load   — 从 HDFS / S3 批量导入
  INSERT INTO SELECT — 从一张表导到另一张表

5.4 查询

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
-- 普通查询,标准 SQL
SELECT * FROM access_log ORDER BY ts DESC LIMIT 10;

-- 按天聚合
SELECT dt, COUNT(*) AS pv, COUNT(DISTINCT user_id) AS uv
FROM access_log
GROUP BY dt
ORDER BY dt;

-- 窗口函数
SELECT
    user_id,
    dt,
    cost_ms,
    SUM(cost_ms) OVER (PARTITION BY user_id ORDER BY dt) AS cum_cost
FROM access_log;

-- 查看执行计划
EXPLAIN
SELECT dt, COUNT(*) FROM access_log GROUP BY dt;

5.5 表管理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
-- 动态新增分区(按天滚动)
ALTER TABLE access_log
ADD PARTITION p20260603 VALUES [('2026-06-03'), ('2026-06-04'));

-- 查看分区
SHOW PARTITIONS FROM access_log;

-- 修改副本数
ALTER TABLE access_log SET ("default.replication_num" = "3");

-- 删除分区(不影响其他分区)
DROP PARTITION p20260601;

六、小结

本文学习了 Doris 的基础:

  • Doris 是什么,与 ClickHouse / StarRocks 的差异
  • FE / BE 架构,存算耦合的设计
  • Docker 部署单机版
  • 四种数据模型(明细、聚合、唯一、主键)的差异和选型
  • 基础 SQL:建库建表、导入、查询、分区管理

下一篇将学习 Doris 的进阶实战:表设计、索引、查询优化、物化视图和典型应用场景。