表类型
明细模型(Duplicate Key)
明细模型是 Doris 中的默认建表模型,用于保存每条原始数据记录。在建表时,通过 DUPLICATE KEY 指定数据存储的排序列,以优化常用查询。
特点:
- 不去重也不聚合:与聚合模型与主键模型不同,明细模型不会对数据进行去重与聚合操作。即使两条相同的数据,每次插入时也会被完整保留。
聚合模型 (Aggregate Key)
特点:
- 导入数据时,相同的 Key 会自动合并,Value 列按照指定函数(SUM, MAX, MIN, REPLACE)聚合。
- 可以使用
BITMAP_UNION()、HLL_UNION()等函数对这些类型进行聚合。
主键模型 (Unique Key)
特点:
- 保证设置的 Unique Key 的唯一性,新数据覆盖旧数据,类似MySQL。
- Merge-on-Write (MoW): 支持写时合并。写入时做去重标记,查询速度极快(接近 Duplicate 模型)。
部分列更新
背景 : OLAP 数据库通常不支持 Update,或者 Update 代价极大(需要重写整行)。
机制 : 在 Unique Key (Merge-on-Write) 模型下,支持只更新部分字段,Doris 会自动补全其他字段的历史值。
使用方式(举例):
mysql
-- 需要先设置 SET enable_unique_key_partial_update = true;
INSERT INTO tbl (id, balance) VALUES (1, 500); -- 只更新余额,其他列不变
分区分桶
分区
作用 : 主要用于数据生命周期管理 (如按天、按月归档)和减少扫描范围。
自动设置(举例):
mysql
PARTITION BY RANGE(event_time) ()
PROPERTIES (
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.start" = "-5",
"dynamic_partition.end" = "3",
"dynamic_partition.prefix" = "p",
"dynamic_partition.create_history_partition" = "true" -- 创建历史分区
);
分桶
数据倾斜
现象 : 某个 BE 节点的负载远高于其他节点,或者某几个 Bucket 的数据量极大,导致长尾效应(整个查询速度取决于最慢的那个节点)。
排查 : 使用 SHOW DATA SKEW FROM table_name; 查看。
解决方案 :
-
更换分桶键 : 不要使用类似 city_id 或 date 这种低基数列做分桶,改用 uuid 或组合键。
-
增加分桶数 : 将大 Bucket 拆得更细。
-
使用 Random 分桶: 如果是聚合模型且不需要利用分桶键做 Join,可以使用随机分桶让数据绝对均匀。
分桶键(Distribution Key)错误
现象 :DISTRIBUTED BY HASH(comment_id)。但 video 表和 comment_agg 表都是按video_id 分布的。
如果 comment_table 按 comment_id 分布,那么同一个视频下的评论会被打散到集群的所有机器上。
Join 灾难 :当你执行 JOIN 查某个视频的评论+聚合数据时,数据库必须进行 Shuffle(网络重分布),这会产生巨大的网络开销,导致查询慢几倍甚至几十倍。
修正:必须改为 DISTRIBUTED BY HASH(video_id)。
Colocation Group (同组策略)
概念 : 将两张或多张表的分桶键(Bucket Key)、分桶数量、副本数量 完全一致,并将它们显式地归属于同一个 Group。
原理 : Doris 会保证这些表对应的 Bucket 落在相同的 BE 节点上。
优势 : 当这两张表进行 Join(且 Join Key = Bucket Key)时,直接在本地节点完成 Join,完全避免了网络 Shuffle,查询性能提升数倍。
用法:
mysql
PROPERTIES (
"colocate_with" = "group_video_core" -- 同组方便 Join
);
JOB机制
功能 : Doris 内置的任务调度器,可以定时执行 SQL。以前做 "定时调度"需要依赖外部定时任务,现在不需要了。
用法(举例):
mysql
CREATE JOB my_daily_etl
ON SCHEDULE EVERY 1 DAY
STARTS '2025-01-01 02:00:00'
DO
INSERT INTO target_table
SELECT * FROM source_table
WHERE date = yesterday();
注意STARTS时间不能用函数的形式,例如CURDATE() 或 DATE_FORMAT(NOW(), '%Y-%m-%d 00:00:00')
索引加速
前缀索引
Doris 会根据表的前 36 个字节(由列定义顺序决定)自动作为前缀索引。
建表顺序至关重要 : 将查询频率高、过滤度高的列(键)放在表、键定义的最前面。
倒排索引
用于解决非主键列 、文本模糊匹配 、高维多列任意组合查询 的性能问题。
支持等值、范围、列表查询加速。
支持全文检索(MATCH_ALL, MATCH_ANY)。
优势 : 相比前缀索引,它不依赖列的顺序;相比 BloomFilter,它不仅能过滤还能快速定位行号。
用法(举例):
mysql
CREATE INDEX idx_log_msg ON log_table(message) USING INVERTED PROPERTIES("parser" = "english"); -- 用 parser 可以指定分词
```
### 布隆过滤器
Doris 的数据是按列存储的,且被切分为多个 Page(页面)。Doris 会为指定的列在每个 Page 级别生成一个 Bloom Filter。查询时,系统先查这个小的索引结构,如果命中"不存在",则整个 Page 都不需要加载到内存。
**开启内置布隆过滤器(举例)**:
```mysql
PROPERTIES (
"bloom_filter_columns" = "city,event_type", -- 指定哪些列建 Bloom Filter
);
使用建议
- 如果你的磁盘空间非常紧张,或者只是偶尔查询该列,用 布隆过滤器。
- 如果你对查询速度要求极高,且该列是核心查询条件,请优先使用 倒排索引。
Array 类型
Apache Doris 中,如果你有一个 ARRAY<STRING> 类型的列(例如 video_tags ARRAY<STRING>),想要展开数组中的每个元素 (即"拿到每个数据"),需要使用 LATERAL VIEW + EXPLODE 函数。
mysql
SELECT
video_id, -- 假设你有视频 ID
tag -- 展开后的单个标签
FROM your_table
LATERAL VIEW EXPLODE(video_tags) t AS tag;
说明:
EXPLODE(video_tags):将video_tags数组中的每个元素拆成一行。LATERAL VIEW:用于将表生成函数(如EXPLODE)的结果与原表关联。t AS tag:t是虚拟表别名(可省略或任意命名),tag是展开后每个元素的列名(你自定义)。
Bitmap 对比 HLL
| 特性 | Bitmap (Roaring Bitmap) | HLL (HyperLogLog) |
|---|---|---|
| 精确度 | 100% 精确 (跟 count(distinct) 一样准) | 非精确 (约 1%~2% 的误差) |
| 数据类型要求 | 必须是整型 (Int/BigInt)。如果是字符串需构建全局字典映射。 | 任意类型 (String, Int, IP等均可) |
| 存储空间 | 较小(但在高基数极其稀疏时比 HLL 大) | 极小 (固定大小,非常节省) |
| 是否支持分析 | 支持集合运算 (交集、并集、差集)。 能回答"看过A且看过B的人"。 | 不支持。只能回答"有多少人",无法知道"是谁"或做人群圈选。 |
| 计算速度 | 极快 (位运算) | 极快 (概率算法) |
什么时候必须要用 HLL?
- ID 是非数字且无法维护字典:
如果你的统计口径是 Device ID (IMEI/IDFA/UUID),这些长字符串。Bitmap 只能存 Int。想用 Bitmap,你需要维护一张 String -> Int 的全局映射表(Global Dictionary)。在亿级实时流中,维护这个字典成本极高(需要 Redis 或 HBase 配合)。这时候用 HLL 最省事,直接 HLL_HASH(device_id) 即可。 - 超大规模、非关键指标的 Dashboard:
比如全站实时总 PV/UV 大屏,数字在跳动,老板只看那个"量级"(是1亿还是1.1亿)。这时候为了节省内存和极致的写入速度,可以用 HLL。
踩坑指南
WSL启动脚本问题
网络模式问题 (WSL) :在 Windows 的 WSL 环境中,脚本检测为 Linux 系统,于是使用了 network_mode: host 。但在 Docker Desktop for Windows (WSL 2 backend) 中, host 网络模式并不能像原生 Linux 那样直接把端口暴露给 localhost (或者说支持得不够完美),这会导致你无法通过 127.0.0.1:9030 连接。
修改:统一 Linux 和 Mac 的配置,都使用 Bridge 网络 + 端口映射 ( ports ) 方式。这种方式在 WSL 2 下最稳定,也能确保你可以通过 127.0.0.1 正常访问。
ERROR 1105 (HY000): errCode = 2, detailMessage = replication num should be less than the number of available backends. replication num is 3, available backend num is 1
解释:Apache Doris 是一个 分布式数据库 ,设计之初就是为了生产环境的高可用性,默认 3 副本 是为了保证当其中一个节点宕机时,数据依然可用且不丢失。使用 Docker 单节点部署(只有 1 个 FE 和 1 个 BE),无法物理存储 3 个副本 (因为它需要 3 个不同的 BE 节点),所以使用默认设置建表会报错或无法成功。
解决方法:显式指定副本数为 1,末尾加上 PROPERTIES ("replication_num" = "1")
ERROR 1105 (HY000): errCode = 2, detailMessage = Merge-on-Write table's partition column must be KEY column
解释:进行分区时,如果用主键模型,分区列必须是主键列
解决方法:如果要用分区,必须把分区列加到主键列中,如果不想在主键中添加无关的属性就不要分区。
ERROR 1105 (HY000): errCode = 2, detailMessage = Time series compaction policy is not supported for unique key table
解释 :Doris 目前的 time_series Compaction 策略 仅支持 Aggregate 模型和 Duplicate 模型 , 不支持 Unique Key 模型 。
ERROR 1105 (HY000): errCode = 2, detailMessage = String Type should not be used in key column[main_category]
解释:STRING 类型在 Doris 中是 "大文本"(类似 Text/Blob),不支持作为主键。可以改成 VARCHAR 做主键,或者作为聚合列用 REPLACE 函数。
ERROR 1105 (HY000): errCode = 2, detailMessage = Insert has filtered data in strict mode. first_error_msg: no partition for this tuple.
解释:你的插入语句报错 no partition for this tuple ,这是因为:
- 你插入的数据时间是 21:00 (晚上9点)。
- 而表定义的动态分区参数是 "dynamic_partition.end" = "2" (小时)。
- 如果当前系统时间是上午(比如10点),分区只会预创建到 12:00 ,因此无法容纳 21:00 的数据。
同时 Doris 的动态分区(Dynamic Partition)默认配置下,不会自动创建"历史分区"。(虽然你设置了 "dynamic_partition.start" = "-1",但在建表初始时刻,Doris 默认不回溯创建历史分区(默认参数 create_history_partition 为 false))
需要设置 "dynamic_partition.create_history_partition" = "true"
ERROR 1105 (HY000): errCode = 2, detailMessage = Merge-on-Write table's partition column must be KEY column
解析:如果用主键模型,分区列必须是主键列
解决方法:如果要用分区,必须把分区列加到主键列中,如果不想在主键中添加无关的属性就不要分区。
ERROR 1105 (HY000): errCode = 2, detailMessage = Cannot invoke "org.apache.doris.nereids.trees.expressions.Expression.arity()" because "expr" is null
解释:Doris 的查询优化器(Nereids)在处理包含 CASE WHEN 的子查询分组时遇到了内部异常。
解决方法:我这个错误是因为内波查询用了 CASE WHEN 触发的,改成用 WITH 处理就可以了。
ERROR 1105 (HY000): errCode = 2, detailMessage = delete predicate on value column only supports Unique table with merge-on-write enabled and Duplicate table, but Table[video_event_agg] is an Aggregate table.
解释:在 Doris 中,Aggregate 表的设计初衷是用于预聚合写入,不支持对已聚合数据的任意删除。因为删除条件若涉及 value 列(如 cnt > 10),系统无法逆向还原原始明细数据,从而无法安全删除。