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

表主键使用 BIGINT AUTO_RANDOM(5) 打散写入热点,当前写入 QPS 1.8w,业务按创建时间范围做分页查询。 发现两个现象:

  1. AUTO_RANDOM 主键无序,时间范围查询触发大量跨 Region 扫描,分页 SQL 延迟持续走高;
  2. 如果换回有序自增 ID,会产生单 Region 写入热点。

想请教几个问题:

  1. 方案取舍: 方案 A:保留 AUTO_RANDOM 主键,额外建立 create_time 联合索引; 方案 B:使用自增主键 + SHARD_ROW_ID_BITS,再按时间分区; 两种方案在高并发写入 + 时间范围查询混合负载下,生产环境如何选择?各自典型瓶颈是什么?
  2. AUTO_RANDOM 分配 bit 位数,除了预估总数据量之外,还需要结合写入 QPS 做调整吗?
  3. 针对 “时序写入、时序查询” 这类典型场景,TiDB 官方目前最优的表结构设计范式是什么?
1 个赞

有人知道没

1 个赞

这个是 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 个赞

建议方案A:保留AUTO_RANDOM主键 + 创建create_time联合索引(如INDEX idx_time_status(create_time, status))。这是生产环境最稳妥的做法。

方案A瓶颈:写入时主键打散效果好,但时间范围查询需走二级索引回表,避免全表扫描。注意控制索引长度,create_time用DATETIME(3)类型。

  • AUTO_RANDOM(5) 无序主键 → 写入均匀,但时间范围扫描必须走二级索引,大量跨 Region、频繁回表,分页延迟上涨
  • 纯自增主键 → 写入全部挤压尾部 Region,严重写热点。

自增我觉得不会有明显热点,自增默认配置不同tidb各自30000个自增空间,你可以再调大一些,这样只要保证写入进程连多个tidb节点,实际上就可以落到不同region里面

  • AUTO_RANDOM主键(聚簇索引)→ 主键 KV 随机分散;create_time只是二级索引。时间范围查询走二级索引,索引有序,但索引对应的数据行散落在大量 Region → 大量回表、跨 Region 扫描、分页延迟高;
  • AUTO_INCREMENT有序主键 → 写入全部冲向尾部单个 Region,严重写入热点;
  • SHARD_ROW_ID_BITS 只能用于【非聚簇表】(无 int 主键 / 主键非聚簇);如果你主键是PRIMARY KEY(id BIGINT AUTO_INCREMENT)聚簇表,SHARD_ROW_ID_BITS不生效,这是极易踩的大坑。

“时序写入、时序查询”的场景下,写入热点与查询效率往往是相互矛盾的

学习了

时序查询重:倾向有序时间分区 + 适度打散(SHARD_ROW_ID_BITS/分区键);纯 AUTO_RANDOM 利写入但时间范围易散扫,需 create_time 索引仍可能多 Region。AUTO_RANDOM 位数按总行数与并发预留,不只看 QPS。官方常见范式是时间分区承载范围查,写入热点用分区/打散兼顾。