← 返回内容列表

数据库分区设计:按主键分区而非时间列,告别手动运维

数据库分区设计:按主键分区而非时间列,告别手动运维

一篇引起广泛讨论的文章主张按主键而非 created_at 时间列进行数据库分区,通过后台服务自动管理分区边界。这样查询无需修改即可自动获得分区裁剪,避免泄漏抽象和查询性能退化。

数据库分区是处理大数据量的常见手段,但分区键的选择往往决定了系统是"自动运行"还是"需要 babysit"。一篇在 Hacker News 上引起广泛讨论的文章提出了一个反直觉的建议:按主键分区,而非按 created_at 时间列分区

按 created_at 分区的陷阱

按时间列分区看似自然——每月一个分区,老数据归档方便。但它带来了一个严重的副作用:主键被迫变为 PRIMARY KEY (id, created_at),这意味着任何不包含 created_at 过滤条件的查询会扫描所有分区

一个 WHERE id = 12345 查询从一次索引查找退化为 36 次索引查找(假设有 36 个月度分区)。更糟糕的是,id 不再保证唯一——数据库可以接受两个相同 id 但不同时间戳的行。

图1:主键分区vs时间分区

图1:按主键分区(左)自动裁剪 vs 按时间分区(右)扫描所有分区

团队的"修复"方式通常是在每个查询中加上日期过滤,但这是一种泄漏抽象——存储决策变成了每个调用者必须遵守的契约。没有报错,只有慢查询,通常在仪表盘超时后才被发现。

推荐策略:按主键 RANGE 分区

idBIGINT AUTO_INCREMENT)进行 RANGE 分区的优势:

  • 主键本身单调递增,满足范围分区需求
  • 几乎所有查询都按 id 过滤,自动获得分区裁剪partition pruning
  • 主键保持 PRIMARY KEY (id),唯一性不受影响
  • 应用代码零改动

时间对齐但不在键中使用日期

id 分区不等于放弃时间对齐。后台服务通过查询推导时间对应的 ID 边界:

SELECT MAX(id) FROM orders WHERE created_at < '2026-03-01'

这样分区名称可以是 p2026_03,内容大致对应 2026 年 3 月的数据。created_at 仅在 DDL 时使用一次,不进入主键,不进入查询 WHERE 子句。

后台自动管理服务

一个轻量级定时服务负责:

步骤说明
盘点读取当前分区布局、名称、边界、行数
大小检查活跃分区超过阈值时触发切割
边界选择查询时间对应的 ID 边界
拆分REORGANIZE PARTITION 拆分兜底分区
保留期清理将保留窗口转为 ID 边界,删除过期分区
并发保护advisory lock 防止多实例同时操作

核心决策原则

在选择分区键之前,先问:哪个列已经存在于每个重要查询中?

  • OLTP 系统 → 答案通常是主键,按主键分区零成本
  • 多租户系统且每个查询都带 tenant_id → 按 tenant_id 分区合理
  • 真正的时序工作负载且每个查询都按日期过滤 → 按时间列分区合理

最糟糕的做法:按一个不在查询中的列分区,然后逆向改造查询层去适配它——最终以 AND created_at >= ? 遍布所有与日期无关的查询而告终。

关联推荐

评论 (0)

加载评论中…