TiDB 建表规范与最佳实践 (V1.0)
核心设计哲学:忘记顺序,拥抱离散!
在开始之前,请把这句话刻在你的 DNA 里:
TiDB 第一天条:打散!打散!还是打散!一切为了避免热点(Hotspot)!
想象一下,TiDB 是一个有无数窗口同时办理业务的大厅(分布式),而热点就是所有人都挤在1号窗口排队,其他窗口都空着。我们的目标就是把人流(数据写入)均匀引导到所有窗口。
一、 主键设计规范 (The MOST IMPORTANT Part!)
主键是数据在 TiDB 中物理分布的决定性因素,也是最容易产生热点的地方。
[ 1.1 ]【强制】高频写入的表,主键必须使用AUTO_RANDOM。
- 说明:这是解决写入热点的最佳、最简单、最常用的手段。
AUTO_RANDOM会生成离散的、随机的 ID,将写入请求自然地分散到不同的 TiKV 节点上。 - 反例:禁止在高频写入的表中使用
AUTO_INCREMENT作为主键。连续递增的 ID 是写入热点的万恶之源。
[ 1.2 ]【强制】所有表都必须有显式的主键。
- 说明:如果没有主键,TiDB 会使用一个隐式的、自增的
_tidb_row_id作为行 ID,这同样会引发严重的写入热点。定义一个显式主键(即使是业务无用)是良好实践。
[ 1.3 ]【推荐】当主键为非整数类型(如VARCHAR,UUID)或联合主键时,使用SHARD_ROW_ID_BITS+PRE_SPLIT_REGIONS来打散数据。
- 说明:这种场景下无法使用
AUTO_RANDOM,SHARD_ROW_ID_BITS成为我们打散隐式_tidb_row_id的武器。 - 用法:
SHARD_ROW_ID_BITS = N:将数据打散到2^N个逻辑分片上。通常建议N的取值为4到6之间。PRE_SPLIT_REGIONS = M:在建表时预先创建M个空的 Region。M的值应略小于2^N。例如,SHARD_ROW_ID_BITS = 4(16个分片),可以设置PRE_SPLIT_REGIONS = 3或4。
二、 索引设计规范
索引也会产生热点!特别是当索引键是时间、状态等具有单调递增属性的字段时。
[ 2.1 ]【推荐】避免在索引的第一列使用单调递增的字段。
- 说明:例如,在
create_time字段上建索引,所有新的写入都会集中更新索引的末尾部分,形成索引热点。 - 解决方案:
- 组合索引:将离散度高的列(如
user_id)放在前面,create_time放在后面,如KEY(user_id, create_time)。 - 函数索引 (v6.2.0+):对索引列进行处理,如
KEY((REVERSE(user_id)))。
- 组合索引:将离散度高的列(如
[ 2.2 ]【建议】单个表的索引数量不宜过多,建议不超过 5 个。
- 说明:索引越多,写入时需要维护的开销就越大,影响写入性能。请只创建真正需要且高效的索引。
三、 字段类型与约束规范
[ 3.1 ]【强制】所有字段都必须指定为NOT NULL,并提供DEFAULT值。
- 说明:
NULL值会带来额外的存储开销和逻辑复杂性,在分布式环境下,明确的默认值是更优的选择。
[ 3.2 ]【推荐】使用VARCHAR代替TEXT或BLOB。
- 说明:如果字段长度可控(如小于 64KB),优先使用
VARCHAR。TiDB 对VARCHAR的处理性能优于TEXT/BLOB。
[ 3.3 ]【强制】统一使用utf8mb4字符集。
- 说明:
utf8mb4是未来的标准,支持 Emoji 等各类字符,避免后续字符集转换的痛苦。
四、 表参数与其他
[ 4.1 ]【强制】所有表和字段都必须添加COMMENT注释。
- 说明:今天你偷的懒,就是明天同事骂你的原因。清晰的注释是团队协作的基石。
TiDB 建表示例与模板 (抄作业区✍️)
下面是两个最经典的场景,你可以直接复制粘贴,然后修改表名和字段名!
模板一:最佳实践 - 使用AUTO_RANDOM主键(90% 的场景都用它!)
CREATETABLE`t_user_info`(`id`BIGINTNOTNULLPRIMARYKEYAUTO_RANDOM,-- 魔法在这里!`user_id`VARCHAR(64)NOTNULLDEFAULT''COMMENT'用户唯一标识',`nickname`VARCHAR(100)NOTNULLDEFAULT''COMMENT'用户昵称',`create_time`DATETIMENOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',`update_time`DATETIMENOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'更新时间',KEY`idx_userid`(`user_id`)-- 业务需要,可以创建普通索引)ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_binCOMMENT='用户信息表';模板二:备选方案 - 非整数主键或无主键表
CREATETABLE`t_operation_log`(`log_id`VARCHAR(128)NOTNULLPRIMARYKEY,-- 使用 UUID 或其他非整数做主键`operator`VARCHAR(100)NOTNULLDEFAULT''COMMENT'操作人',`content`TEXTCOMMENT'操作内容',`create_time`DATETIMENOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间')ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_binCOMMENT='操作日志表'-- 魔法在这里!SHARD_ROW_ID_BITS=4PRE_SPLIT_REGIONS=3;错误示例:千万不要这样建表!(MySQL 老习惯)
-- 这是一个典型的反面教材!CREATETABLE`t_hotspot_orders`(`id`BIGINTNOTNULLAUTO_INCREMENT,-- ❌ 热点元凶!`user_id`BIGINTNOTNULL,`amount`DECIMAL(10,2),`create_time`TIMESTAMP,PRIMARYKEY(`id`),-- ❌ 把自增列做主键KEY`idx_createtime`(`create_time`)-- ❌ 在时间列上单独建索引,也是索引热点);总结
好了,这份宝典的核心精髓已经传授给你了。记住,在 TiDB 的世界里,“随机”和“离散”是最高贵的品质。每次CREATE TABLE前,都默念一遍“打散大法好”,你就离 TiDB 大神又近了一步!