TIDB热点问题讨论

如果表主键是连续自增的,那在插入数据的时候,只会往一个Region尾部中插入,当这个Region满了之后,自动开辟下一个Region,所有的操作都在一个Region上,也就是一个TIKV上,高并发下请求都是打到同一台TIKV上,有负载风险且不满足分布式资源利用的需求.但是我们一般都是自增主键,或者分布式雪花算法自增主键

如果存在通用的字典表这种也会有热点问题,记得幂等性调用落缓存里

解决方案1: 高并发写入数据时候,放入队列MQ中异步写入

解决方案2: 用离散的随机主键,比如UUID,但是没有自增主键

解决方案3: 用UUID做主键,然后额外在建立一个唯一索引的ID自增列进行冗余

解决方案4: 提前切分N个Region,然后再往里面插入,感觉有点治标不治本,如果提前切分的Region用完了怎么办?

方案3的冗余是否可靠,UUID在TIDB上有性能问题嘛

表分区会影响Region分布吗,假如我以’create_time’按照’年分’进行分区,以’UUID’做主键,假如我同一年有很多数据insert,这种会出现热点问题吗

这个也得看你插入的情况,如果按照年分区,但是你插入的数据都是同一年的,那其实和有没有分区就没关系了。

1 个赞

tidb推荐用increment_random

1 个赞

楼上说的对,要不然就得打散热点

1 个赞

我应该这么举例子, 假如我某一时间点插入2023-01-01的数据100W, 2024-01-01的数据插入2条, 但是我的主键都是离散的, 这种会有热点吗 表分区会不会影响就是TIKV的Region分布

首先,如果你既想主键连续自增,又想避免写热点问题,不需要你的解决方案3,你直接建表时指定使用主键自增(自增缓存设置为1)且是非聚簇索引,同时shard分片,然后预先切片就能达到你要的效果。
建表语句如下:
CREATE TABLE t_autoincre_noclus_sd (a BIGINT PRIMARY KEY NONCLUSTERED AUTO_INCREMENT, b VARCHAR(255)) SHARD_ROW_ID_BITS = 4 pre_split_regions=4 AUTO_ID_CACHE 1;
可以用下面sql测试

INSERT INTO t_autoincre_noclus_sd(b) VALUES ('1');
INSERT INTO t_autoincre_noclus_sd(b) VALUES ('2');
INSERT INTO t_autoincre_noclus_sd(b) VALUES ('3');
INSERT INTO t_autoincre_noclus_sd(b) VALUES ('4');
INSERT INTO t_autoincre_noclus_sd(b) VALUES ('5');
INSERT INTO t_autoincre_noclus_sd(b) VALUES ('6');
INSERT INTO t_autoincre_noclus_sd(b) VALUES ('7');
INSERT INTO t_autoincre_noclus_sd(b) VALUES ('8');
INSERT INTO t_autoincre_noclus_sd(b) VALUES ('9');
INSERT INTO t_autoincre_noclus_sd(b) VALUES ('10');
INSERT INTO t_autoincre_noclus_sd(b) VALUES ('11');
INSERT INTO t_autoincre_noclus_sd(b) VALUES ('12');
INSERT INTO t_autoincre_noclus_sd(b) VALUES ('13');
INSERT INTO t_autoincre_noclus_sd(b) VALUES ('14');
INSERT INTO t_autoincre_noclus_sd(b) VALUES ('15');
INSERT INTO t_autoincre_noclus_sd(b) VALUES ('16');

SELECT _tidb_rowid,t.* FROM t_autoincre_noclus_sd t ORDER BY a;

SELECT * FROM INFORMATION_SCHEMA.`TIKV_REGION_STATUS` b WHERE b.`TABLE_NAME`='t_autoincre_noclus_sd';

你会发现你插入的数据,既符合主键是连续自增,同时也不存在写热点。同时你所担心的提前切分的Region用完其实不存在的,因为预分的region用完了,你SHARD_ROW_ID_BITS还是会保证你后面每插入数据都是分散到不同的region上的。
这个方案其实唯一的缺点就是,非聚簇索引表相对于聚簇索引表的效率其实是稍微有点差的,毕竟没有点查了。

至于你下面这个分区的问题,你只要理解tidb的分区实际上是物理分区,它每个分区相当于1个物理小表,所以你同一年插入很多数据,相当于都插入到了你对应分区的那个小物理表中,该有热点还是有热点的。区别只是和上面一样,你是否已经预先打散到不同的region中。
另外在前面的版本中,主键是必须包含分区键的。

1 个赞

分区表就理解成多个表就行了,一个分区一个表。然后一个表至少一个region。这样的话你就好分析怎么用能避免热点了。

1 个赞

感谢上面的回答,再结以详细文档,总结如下:

欢迎讨论

(推荐)解决方案1: 高并发业务写入数据时候,放入队列MQ中异步写入,可以存在自增id,自增主键,可以自由引进雪花算法,创立自增排序索引,比如插入时间字段等,满足业务要求,同时创建表时,需要合理的分片,在分片上进行region离散,因为分片实际上是拆分成了不同的物理’小表’,不同的物理小表有不同的Region.

例如: 引进一个额外的字段remainder_id,在业务代码层,对id取一下余数

然后存入{id}%10,即可打散原先的热点分布成10份

CREATE TABLE employees (
    id INT NOT NULL,
    name VARCHAR(30),
    age VARCHAR(30),
    remainder_id INT
)

PARTITION BY KEY(remainder_id)
PARTITIONS 10;

#7.0.0版本才支持key分区, 之前的版本可以用HASH

解决方案2: 用离散的随机主键,比如UUID,但是没有自增主键,推荐TIDB自带的auto_random,数据要够分散,才能保证不形成单个节热点,同时不能存在递增的索引字段,否则还是有热点问题,且某些排序查询可能很慢

解决方案3: 换非聚簇索引,aotu_increment使用,提前切分N个Region,然后再往里面插入,其中aotu_increment,只缓存3W个,频繁重启就会有问题,设置的过大会造成 RPC 请求数放大,增加 CPU 和网络开销。同时非聚簇索引需要回表

  • SHARD_ROW_ID_BITS = 4 表示 16 个分片
  • SHARD_ROW_ID_BITS = 6 表示 64 个分片
  • SHARD_ROW_ID_BITS = 0 表示默认值 1 个分片

语句示例:

CREATE TABLE:CREATE TABLE t (c int) SHARD_ROW_ID_BITS = 4;

ALTER TABLE:ALTER TABLE t SHARD_ROW_ID_BITS = 4;

set tidb_scatter_region=‘ON’;另外如果这个参数不是on的话需要先设置成on,因为默认预切分的region在不是on的情况下,是负载到一个tikv节点的,虽然后面会自动均衡,但是你可以提前均衡好,代价是建表会稍微慢一点。

1 个赞