TiDB 中的自增主键有哪些使用限制,应该如何避免?
大家好,我是V 哥,在TiDB中使用自增主键时,确实存在一些限制和潜在的热点问题,今天的文章来聊一聊 TiDB中的自增主键要怎么做。
以下是一些使用限制和如何避免它们的方法:
-
自增主键的限制:
- 必须在主键或唯一索引列上定义。
- 只能定义在整型、FLOAT或DOUBLE类型的列上。
- 自增列不支持DEFAULT定义。
- 不支持使用ALTER TABLE增加AUTO_INCREMENT属性。
- 默认不允许移除AUTO_INCREMENT属性,可以通过
@@tidb_allow_remove_auto_inc
来控制是否允许删除自增属性。 - 可以保证自增不唯一,无法保证顺序。
-
避免自增主键限制的方法:
- 使用
AUTO_RANDOM
:TiDB提供了AUTO_RANDOM
属性,可以在建表时替代AUTO_INCREMENT
使用,这样TiDB会生成随机分布的ID,从而避免写入热点问题 。 - 使用
SHARD_ROW_ID_BITS
:对于非聚簇索引主键或没有主键的表,TiDB会使用一个隐式的自增RowID。通过设置SHARD_ROW_ID_BITS
,可以把RowID打散写入多个不同的Region,缓解写入热点问题 。 - 使用分布式ID生成器:例如Snowflake算法,可以在应用层生成唯一的ID,避免依赖数据库的自增主键。
- 使用UUID:可以在应用层生成UUID作为主键,但需要注意UUID的性能影响。
- 使用Sequence序列:TiDB支持Sequence序列,可以在创建表时定义Sequence,然后使用Sequence来生成唯一的ID。
- 使用
使用 AUTO_RANDOM
在TiDB中,AUTO_RANDOM
是用于解决自增主键热点问题的一种方法。以下是一个具体的业务场景案例和操作步骤:
业务场景:
假设你有一个高并发的在线服务,需要为每个服务实例生成一个唯一的标识符。如果使用传统的AUTO_INCREMENT
自增主键,大量的写入操作可能会导致写入热点,因为所有的写入都会尝试在最后一个Region上进行,从而影响性能。
解决方案:
- 创建表时使用
AUTO_RANDOM
:
在创建表时,将主键列设置为AUTO_RANDOM
。例如,你可以执行以下SQL语句来创建一个新表:
CREATE TABLE service_instances (id BIGINT PRIMARY KEY AUTO_RANDOM,instance_name VARCHAR(255),created_at TIMESTAMP);
这样,每当插入新行而没有指定id
值时,TiDB会自动生成一个随机的id
值。
- 插入数据:
当插入新服务实例时,不需要手动指定id
值,TiDB会自动为每个实例生成一个唯一的id
:
INSERT INTO service_instances (instance_name, created_at) VALUES ('ServiceInstanceName', NOW());
这将利用AUTO_RANDOM
属性生成一个随机的id
,从而避免写入热点。
- 获取最后插入的ID:
如果你需要获取最后插入的id
,可以使用LAST_INSERT_ID()
函数:
SELECT LAST_INSERT_ID();
这将返回最近一次由TiDB隐式分配的AUTO_RANDOM
值。
咱们可以使用TiDB的监控工具,如Grafana,监控AUTO_RANDOM
字段的性能。注意观察是否有任何写入热点的迹象,如某个TiKV节点的负载明显高于其他节点。根据监控结果调整策略。
需要注意的是:
- 不要显式地为
AUTO_RANDOM
字段插入值,除非打开了@@allow_auto_random_explicit_insert
系统变量,并且你知道你在做什么。错误的显式赋值可能会导致值耗尽。 AUTO_RANDOM
只能用于BIGINT
类型的主键列,并且不支持与AUTO_INCREMENT
同时使用。
使用 SHARD_ROW_ID_BITS
业务场景案例:
假设你运营一个电商平台,需要处理大量的订单数据。每个订单都需要一个唯一的订单号,而且订单数据写入数据库时必须均匀分布,以避免写入热点。如果使用自增主键,大量的写入操作可能会集中在单个TiKV节点上,导致写入热点问题。
解决方案:
-
创建非聚簇索引表:
创建订单表时,不使用自增主键,而是使用
SHARD_ROW_ID_BITS
来打散行ID,从而避免写入热点。
CREATE TABLE orders (order_id INT PRIMARY KEY NONCLUSTERED,product_id INT,quantity INT,created_at TIMESTAMP,INDEX product_idx (product_id)) SHARD_ROW_ID_BITS = 4 PRE_SPLIT_REGIONS=3;
这里SHARD_ROW_ID_BITS = 4
表示行ID会被打散到16个分片中,PRE_SPLIT_REGIONS=3
表示在表创建后会预先切分为8个Region。
- 插入数据:
当创建订单时,不需要手动指定order_id
,TiDB会自动为每个订单分配一个唯一的行ID。
INSERT INTO orders (product_id, quantity, created_at) VALUES (101, 2, NOW());
同样,我们使用Grafana,监控orders
表的性能。注意观察是否有任何写入热点的迹象,如某个TiKV节点的负载明显高于其他节点。根据监控结果调整SHARD_ROW_ID_BITS
的值。
需要注意的是:
- 不要显式地为行ID插入值,让TiDB自动分配。
- 根据业务并发度来设置合适的
SHARD_ROW_ID_BITS
值,以尽量解决热点Region无法打散的问题。
问题来了,如何根据业务增长调整 'SHARD_ROW_ID_BITS' 的值以优化数据库性能?
在TiDB中,SHARD_ROW_ID_BITS
是一个表属性,用于设置隐式_tidb_rowid
分片数量的bit位数,以此来解决写入热点问题。这个属性可以在创建表时指定,也可以用来修改现有表的行为。
调整SHARD_ROW_ID_BITS
值的步骤:
- 评估当前业务量和增长趋势:
- 监控当前业务对数据库的写入模式和数据增长速度。使用Grafana监控TiDB的写入流量和热点情况。
- 决定
SHARD_ROW_ID_BITS
的值:
- 根据业务增长预测和当前写入模式,决定一个合适的
SHARD_ROW_ID_BITS
值。例如,如果当前设置为4(分成16个分片),但写入压力仍然很高,可以考虑增加该值。
- 修改表属性:
- 使用
ALTER TABLE
语句来修改SHARD_ROW_ID_BITS
值:
ALTER TABLE your_table_name SHARD_ROW_ID_BITS = new_value;
- 其中
new_value
是你基于业务增长评估后决定的新值。
- 预切分区域:
- 为了确保数据能够均匀分布,可以使用
PRE_SPLIT_REGIONS
选项在建表后预先切分出一定数量的Region:
CREATE TABLE t (a int, b int) SHARD_ROW_ID_BITS = 4 PRE_SPLIT_REGIONS=3;
- 这里的
PRE_SPLIT_REGIONS=3
表示建完表后提前切分出8个Region。
- 监控调整后的效果:
- 调整
SHARD_ROW_ID_BITS
后,继续使用Grafana监控系统性能,特别注意写入流量的分布情况。
- 根据需要进一步调整:
- 如果写入热点仍然存在,可能需要进一步增加
SHARD_ROW_ID_BITS
的值,或者考虑其他优化措施。
需要注意的是:
- 增加
SHARD_ROW_ID_BITS
的值会增加Region的分裂数量,可能会对TiDB集群的性能和资源使用产生影响。 - 调整
SHARD_ROW_ID_BITS
值时,需要确保PD调度器不会因为小Region合并策略而将Region重新合并。
最后
关于使用分布式ID生成器,比如雪花算法,还有使用UUID和使用Sequence序列,这里V 哥就不再介绍了,与 MySQL 一致。因此,在 TiDB 中提供了AUTO_RANDOM 和 SHARD_ROW_ID_BITS来解决热点问题,好用的很。