TiDB v7.5.6,订单流水表,每日新增 3000w 行 表主键使用 BIGINT AUTO_RANDOM(5) 打散写入热点,当前写入 QPS 1.8w,业务按创建时间范围做分页查询。 发现两个现象

这个是 TiDB 时序场景的经典表设计问题。逐个答:
问题 1 方案取舍:你 1.8w QPS + 时序范围查询,选 方案 B(自增 + SHARD + 分区)。

核心原因:

  • AUTO_RANDOM 主键的设计意图是给"无时序字段"或"多业务混合"的场景用的——把随机后缀塞进主键来打散写入。一旦你有明确的 create_time 时序字段,用 AUTO_RANDOM 等于"用主键假装成时序,再额外为时序加索引",多绕一道。
  • 方案 A 的 (create_time, ...) 联合索引在 1.8w QPS 下是新的写入瓶颈——索引本身在 TiKV 里也是有顺序的(按 create_time 排序写入),时序写入会聚到索引的尾部,形成索引热点
  • 方案 B 的 RANGE PARTITION BY (create_time) 让时序查询走 partition pruning 极快,且 SHARD_ROW_ID_BITS 已经把写入分散到 N 个 region,两全。
    问题 2 AUTO_RANDOM bit 位:

AUTO_RANDOM(N) 的 N 是随机后缀的 bit 数,跟 QPS 没直接关系,跟 总数据量 有关:

  • N bit 提供 2^N 个不同随机后缀,**只要远小于总行数就行
  • 默认 5 bit(32 种后缀)实际够用,主要给主键注入随机性打破单调
  • 真正影响写入热点的不是 N 多少,而是主键是不是有序——有序=热点,无序=分散
  • 你既然有时序字段,改走 SHARD + 分区,根本不需要 AUTO_RANDOM 操心 bit

问题 3 时序场景官方推荐范式:
sql
CREATE TABLE metric_log (
id BIGINT NOT NULL AUTO_INCREMENT,
create_time DATETIME(3) NOT NULL,
– 业务字段
device_id BIGINT,
value DOUBLE,
PRIMARY KEY (id) /*T![clustered_index] CLUSTERED */
) SHARD_ROW_ID_BITS = 4
PRE_SPLIT_REGIONS = 16
PARTITION BY RANGE (TO_DAYS(create_time)) (
PARTITION p202506 VALUES LESS THAN (TO_DAYS(‘2025-07-01’)),
PARTITION p202507 VALUES LESS THAN (TO_DAYS(‘2025-08-01’)),
PARTITION p202508 VALUES LESS THAN (TO_DAYS(‘2025-09-01’)),
– 按月分区,定期 ADD PARTITION
);


关键参数:
- `SHARD_ROW_ID_BITS = 4`:写入分散到 2^4 = 16 个 region
- `PRE_SPLIT_REGIONS = 16`:建表时就预分裂,避免开局单 region 热点
- `RANGE (TO_DAYS(create_time))`:时序查询 partition pruning,只扫命中的月分区
- 不用 AUTO_RANDOM(已经有 `create_time` 时序字段)

位宽经验值(QPS 1.8w 这种):
- `SHARD_ROW_ID_BITS = 4`(16 region)— 写入 QPS 2w 上下
- `SHARD_ROW_ID_BITS = 5`(32 region)— 写入 QPS 5w 上下
- `SHARD_ROW_ID_BITS = 6`(64 region)— 写入 QPS 10w+

你 1.8w 用 4 就够,预留 1 位给以后扩容。
操作建议(你眼前能立刻做的):
1. 改回 `AUTO_INCREMENT` 主键 + `SHARD_ROW_ID_BITS=4` + `PRE_SPLIT_REGIONS=16`
2. 按月 `RANGE PARTITION BY (create_time)`
3. 时序范围查询自动 partition pruning,延迟能下来
4. 写热点问题:`SHARD_ROW_ID_BITS` 配合后基本消除
1 个赞