3. MergeTree引擎建表与分区键设计

MergeTree引擎建表与分区键设计 学习卡片

为什么日志表的分区键不能随便选

日志数据按天持续写入,如果分区键选得过细(例如按小时),分区数量会迅速膨胀;选得过粗(例如按月),单分区数据量过大,查询时无法有效裁剪。对“按日期增长的日志”这类场景,分区键通常取日期粒度(toYYYYMM 或 toDate),排序键则按“先过滤维度、后时间”的顺序排列,让高频查询条件落在主键前缀上。

日志数据源 event_time service_name level / msg MergeTree 建表设计 PARTITION BY toYYYYMM(event_time) → 按月分区,控制分区数量 ORDER BY (service_name, event_time) → 高频过滤维度在前,时间在后 PRIMARY KEY (service_name, event_time) → 默认与 ORDER BY 一致 ENGINE = MergeTree() 按 event_time 自动合并排序 分区目录结构 202401 1月日志分区 202402 2月日志分区 202403 3月日志分区 每个分区内 按 ORDER BY 排序存储 查询裁剪 WHERE event_time → 只扫相关分区 分区键决定数据物理切分粒度,排序键决定分区内有序性,两者共同影响查询裁剪效率

建表语句与关键参数

下面针对日志表 app_log 给出完整建表语句。分区键使用 toYYYYMM(event_time) 按月分区,排序键使用 (service_name, event_time),让按服务名过滤的查询能命中主键前缀。

CREATE TABLE app_log
(
    event_time   DateTime,
    service_name LowCardinality(String),
    level        LowCardinality(String),
    message      String,
    trace_id     String
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_time)
ORDER BY (service_name, event_time)
SETTINGS index_granularity = 8192;
设计要点:分区键用 toYYYYMM 而非 toDate,是因为日志按月增长时,月分区数量可控(一年 12 个),而按天分区一年会产生 365 个分区,元数据开销和合并压力都会明显上升。排序键把 service_name 放在 event_time 前面,是因为日志查询通常先按服务名过滤,再按时间范围筛选。

导入样本数据并观察分区目录

插入跨两个月的样本数据,然后查看分区目录结构,验证分区键是否按预期切分。

INSERT INTO app_log VALUES
('2024-01-15 08:30:00', 'order-service', 'INFO',  'order created',   't-001'),
('2024-01-20 14:12:00', 'order-service', 'ERROR', 'payment timeout', 't-002'),
('2024-02-03 09:45:00', 'user-service',  'INFO',  'user login',      't-003'),
('2024-02-11 22:05:00', 'user-service',  'WARN',  'slow query',      't-004'),
('2024-02-28 16:30:00', 'order-service', 'INFO',  'order shipped',   't-005');

查询分区信息:

SELECT
    partition,
    name,
    rows,
    bytes_on_disk
FROM system.parts
WHERE table = 'app_log' AND active = 1
ORDER BY partition;

预期结果中会出现两个分区:202401 和 202402。每个分区对应一个独立目录,目录名格式为 202401_1_1_0(分区ID_最小块号_最大块号_层级)。

验证要点:执行 SELECT partition, rows FROM system.parts WHERE table='app_log' AND active=1,应看到 202401 分区 2 行、202402 分区 3 行。如果分区数量与预期不符,检查 PARTITION BY 表达式是否与插入数据的时间字段匹配。

分区键与排序键的常见误用

误用方式问题正确做法
PARTITION BY toDate(event_time)按天分区,一年 365 个分区,元数据膨胀按月分区 toYYYYMM(event_time)
ORDER BY (event_time, service_name)时间在前,按服务名过滤无法命中主键前缀高频过滤维度放前面
PARTITION BY service_name分区数随服务数量增长,且时间范围查询无法裁剪分区键用时间,服务名放排序键
ORDER BY 与 PRIMARY KEY 不一致且未显式指定主键默认取 ORDER BY 前缀,可能不符合预期显式声明 PRIMARY KEY 或保持默认一致
注意:分区键一旦建表后修改成本很高,需要重建表并迁移数据。设计阶段应结合数据增长速度和查询模式确定分区粒度,日志类数据通常按月或按周分区,避免按天或按小时。