TiDB自增主键使用限制及避免方法

2024-12-17 14:56 更新

大家好,我是V 哥,在TiDB中使用自增主键时,确实存在一些限制和潜在的热点问题,今天的文章来聊一聊 TiDB中的自增主键要怎么做。

以下是一些使用限制和如何避免它们的方法:

  1. 自增主键的限制
    • 必须在主键或唯一索引列上定义。
    • 只能定义在整型、FLOAT或DOUBLE类型的列上。
    • 自增列不支持DEFAULT定义。
    • 不支持使用ALTER TABLE增加AUTO_INCREMENT属性。
    • 默认不允许移除AUTO_INCREMENT属性,可以通过@@tidb_allow_remove_auto_inc来控制是否允许删除自增属性。
    • 可以保证自增不唯一,无法保证顺序。

  1. 避免自增主键限制的方法
    • 使用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上进行,从而影响性能。

解决方案

  1. 创建表时使用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值。

  1. 插入数据: 当插入新服务实例时,不需要手动指定id值,TiDB会自动为每个实例生成一个唯一的id

   INSERT INTO service_instances (instance_name, created_at) VALUES ('ServiceInstanceName', NOW());

这将利用AUTO_RANDOM属性生成一个随机的id,从而避免写入热点。

  1. 获取最后插入的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节点上,导致写入热点问题。

解决方案

  1. 创建非聚簇索引表

创建订单表时,不使用自增主键,而是使用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。

  1. 插入数据: 当创建订单时,不需要手动指定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值的步骤

  1. 评估当前业务量和增长趋势

  • 监控当前业务对数据库的写入模式和数据增长速度。使用Grafana监控TiDB的写入流量和热点情况。

  1. 决定SHARD_ROW_ID_BITS的值

  • 根据业务增长预测和当前写入模式,决定一个合适的SHARD_ROW_ID_BITS值。例如,如果当前设置为4(分成16个分片),但写入压力仍然很高,可以考虑增加该值。

  1. 修改表属性

  • 使用ALTER TABLE语句来修改SHARD_ROW_ID_BITS值:

     ALTER TABLE your_table_name SHARD_ROW_ID_BITS = new_value;

  • 其中new_value是你基于业务增长评估后决定的新值。

  1. 预切分区域

  • 为了确保数据能够均匀分布,可以使用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。

  1. 监控调整后的效果

  • 调整SHARD_ROW_ID_BITS后,继续使用Grafana监控系统性能,特别注意写入流量的分布情况。

  1. 根据需要进一步调整

  • 如果写入热点仍然存在,可能需要进一步增加SHARD_ROW_ID_BITS的值,或者考虑其他优化措施。

需要注意的是:

  • 增加SHARD_ROW_ID_BITS的值会增加Region的分裂数量,可能会对TiDB集群的性能和资源使用产生影响。
  • 调整SHARD_ROW_ID_BITS值时,需要确保PD调度器不会因为小Region合并策略而将Region重新合并。

最后

关于使用分布式ID生成器,比如雪花算法,还有使用UUID和使用Sequence序列,这里V 哥就不再介绍了,与 MySQL 一致。因此,在 TiDB 中提供了AUTO_RANDOMSHARD_ROW_ID_BITS来解决热点问题,好用的很。

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号