4. Doris 基础表模型:明细、聚合、更新、主键,到底怎么选?

聊到 Doris 建表,绕不开的就是表模型选择。很多刚接触 Doris 的朋友,上来就问我:「我该用哪种模型?」

说实话,这个问题没有标准答案。它完全取决于你的业务场景。我最早用 Doris 的时候,也踩过坑——选错了模型,后面改起来那叫一个痛苦。

今天我就把这四种模型掰开揉碎了讲清楚。你听完就能明白,什么场景该用什么。

一句话总结:明细模型存原始数据,聚合模型做预计算,更新模型处理可变数据,主键模型是更新模型的升级版。选对模型,事半功倍。

Doris 四种表模型对比 Doris 表模型 明细模型 聚合模型 更新模型 主键模型 保留所有原始数据 不做任何聚合 适合日志、审计 按维度预聚合 SUM/MAX/MIN等 适合报表、统计 同Key后覆盖 不支持部分更新 适合订单状态 支持部分列更新 Merge-on-Write 适合实时维表

4.1 明细模型(Duplicate Model)

明细模型,说白了就是「原样存」。你写入什么数据,它就存什么数据。不做任何聚合,也不做任何去重。

适用场景:

  • 原始日志数据(用户行为、点击流、API 调用日志)
  • 审计数据(需要保留完整操作记录)
  • 任何需要「查原始记录」的场景

我个人习惯,只要不确定用哪种模型,就先上明细模型。为什么?因为它最灵活。你随时可以基于明细数据做各种聚合分析,但反过来就不行。

我的经验:有一次做用户行为分析,一开始用了聚合模型。结果业务方说「我要看某用户某次点击的完整上下文」,我当场傻眼。后来全改成明细模型了。所以,保留原始数据永远是最安全的。

-- 明细模型建表示例
CREATE TABLE user_behavior_log (
    user_id BIGINT,
    event_time DATETIME,
    event_type VARCHAR(50),
    page_url VARCHAR(500),
    duration_ms INT
) DUPLICATE KEY(user_id, event_time)
DISTRIBUTED BY HASH(user_id) BUCKETS 10
PROPERTIES("replication_num" = "3");

4.2 聚合模型(Aggregate Model)

聚合模型,就是预先按维度做聚合。你写入多条数据,Doris 会根据你定义的聚合函数,自动合并。

支持的聚合函数:

  • SUM — 求和,适合金额、次数
  • MAX / MIN — 最大/最小值,适合温度、价格
  • REPLACE — 替换,适合状态、属性
  • HLL_UNION / BITMAP_UNION — 去重统计

适用场景:

  • 报表统计(日活、PV/UV、销售额)
  • 多维分析(按时间、地域、品类汇总)
  • 指标看板(实时聚合结果)

注意:聚合模型一旦建好,聚合函数就不能改了。我见过有人建表时用了 SUM,后来想改成 MAX,只能重建表。所以建表前一定要想清楚。

-- 聚合模型建表示例
CREATE TABLE daily_sales_summary (
    sale_date DATE,
    product_id BIGINT,
    total_amount BIGINT SUM,
    max_price DOUBLE MAX,
    min_price DOUBLE MIN,
    order_count BIGINT SUM
) AGGREGATE KEY(sale_date, product_id)
DISTRIBUTED BY HASH(product_id) BUCKETS 10
PROPERTIES("replication_num" = "3");

4.3 更新模型(Unique Model)

更新模型,就是「同 Key 后覆盖」。同一个 Key 下,后写入的数据会覆盖之前的数据。

它的实现原理是:Doris 内部其实还是用聚合模型,只不过聚合函数固定为 REPLACE。

适用场景:

  • 订单状态更新(订单创建→支付→发货→完成)
  • 用户信息变更(手机号、地址修改)
  • 任何需要「按主键更新」的场景

核心要点:更新模型只支持整行覆盖。如果你只想更新某几个字段,它做不到。这时候就需要主键模型了。

-- 更新模型建表示例
CREATE TABLE order_info (
    order_id BIGINT,
    user_id BIGINT,
    status VARCHAR(20),
    total_amount DECIMAL(18,2),
    update_time DATETIME
) UNIQUE KEY(order_id)
DISTRIBUTED BY HASH(order_id) BUCKETS 10
PROPERTIES("replication_num" = "3");

4.4 主键模型(Primary Key Model)

主键模型是 Doris 1.2 版本引入的「新武器」。它解决了更新模型的两个痛点:

  • 支持部分列更新 — 只更新你指定的字段
  • Merge-on-Write 模式 — 写入时直接合并,查询更快

为什么会这样?因为更新模型用的是 Merge-on-Read,查询时才知道哪些数据要覆盖。而主键模型在写入时就完成了合并,查询时直接读最终结果。

适用场景:

  • 实时维表(比如商品信息、用户画像,经常只改一两个字段)
  • 高并发点查(按主键查询最新状态)
  • 需要部分更新的场景

我的建议:如果你用的是 Doris 2.0 以上版本,优先考虑主键模型。它比更新模型更灵活,性能也更好。我最近几个项目,全部用的主键模型。

-- 主键模型建表示例(支持部分更新)
CREATE TABLE product_dim (
    product_id BIGINT,
    product_name VARCHAR(100),
    category_id INT,
    price DECIMAL(18,2),
    stock INT,
    update_time DATETIME
) PRIMARY KEY(product_id)
DISTRIBUTED BY HASH(product_id) BUCKETS 10
PROPERTIES(
    "replication_num" = "3",
    "enable_unique_key_merge_on_write" = "true"
);

-- 部分更新示例
INSERT INTO product_dim (product_id, price, update_time)
VALUES (1001, 29.99, '2024-01-15 10:00:00');

4.5 四种模型对比总结

特性 明细模型 聚合模型 更新模型 主键模型
数据去重 否 按聚合函数 同Key覆盖 同Key覆盖
部分更新 不支持 不支持 不支持 支持
查询性能 一般 聚合查询快 点查快 点查极快
存储空间 最大 最小 中等 中等
典型场景 原始日志 报表统计 订单状态 实时维表

4.6 选型建议

说了这么多,到底怎么选?我总结了一个简单的决策流程:

  1. 需要保留所有原始数据? → 明细模型
  2. 只需要聚合结果? → 聚合模型
  3. 需要按主键更新,且只整行覆盖? → 更新模型
  4. 需要部分更新,或追求最佳查询性能? → 主键模型

避坑指南:我曾经在一个项目里,把订单表建成了明细模型。结果订单状态变更时,我只能用 INSERT INTO SELECT 的方式「模拟更新」,效率极低。后来改成主键模型,问题才解决。所以,有更新需求的表,千万别用明细模型。

嗯,四种模型就讲到这里。记住一句话:没有最好的模型,只有最合适的模型。选型时多想想你的数据长什么样,查询怎么用,就不会出错。