高并发【批量on duplicate key update】时:AUTO_RANDOM(5) 生成重复

【 TiDB 使用环境】生产环境 /测试/ Poc
【 TiDB 版本】6.5.3 (tikv 机器有3台)
【复现路径】暂时还没重复性复现
【遇到的问题:问题现象及影响】:
我有一张在tidb4中的表【与tidb6.5.3中相同的表结构】:

CREATE TABLE `ads_test` (
  `id` bigint(20) NOT NULL /*T![auto_rand] AUTO_RANDOM(5) */ COMMENT '自增Id',
  `index_dt` datetime(3) DEFAULT '1970-01-01 00:00:00.000',
  `store_id` varchar(64) DEFAULT NULL,
  `visit_date_hour` datetime(3) DEFAULT NULL,
  `visit_date` varchar(64) DEFAULT NULL,
  `visit_hour` varchar(64) DEFAULT NULL,
  `add_payment_info_cnt` bigint(20) DEFAULT NULL,
  `etl_datetime` datetime(3) DEFAULT NULL,
  `dt` varchar(64) DEFAULT '1970-01-01',
  `hr` varchar(64) DEFAULT '00',
  PRIMARY KEY (`id`),
  KEY `dws_index_dt` (`index_dt`,`dt`,`hr`),
  UNIQUE KEY `uk_store_id_visit_date_visit_hr` (`store_id`,`visit_date_hour`,`index_dt`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![auto_rand_base] AUTO_RANDOM_BASE=2011348235 */ COMMENT='测试表' 

我用tidb 4 的ticdc 通过如下链路同步到tidb6.5.3
tidb4 —>ticdc —>kafka ----> java代码(jdbc写) ----->tidb6.5.3

在jdbc写入tidb6时,我用insert into … on duplicate key update …语法且批量insert数据【每批50到100条不等】。 但不包含id字段【期望在tidb6中通过AUTO_RANDOM 自动生成新的id】
数据量有9000万行+,同步完后,发现tidb6中有部分数据丢失找不到了。 但我在java层有打印所有同步的每条数据,发现java层是有记录同步的,只是在tidb6中没有数据。

目前是怀疑那条数据是有写入到tidb6中,但后面插入的数据通过AUTO_RANDOM 生成了重复id,然后被on duplicate key update 机制覆盖了前一条数据。

【资源配置】:无
【附件:截图/日志/监控】:无

可以看一下官网:https://docs.pingcap.com/zh/tidb/v7.3/auto-random
AUTO_RANDOM(5)。而 TiDB 很容易会为 AUTO_RANDOM(5) 的列分配超出该范围的整数,导致应用程序读取该列的值时出现异常。此时,你可以用 AUTO_RANDOM(5, 54) 代替 AUTO_RANDOM(5),使得 TiDB 不会分配出大于 9007199254740991 (2^53-1) 的整数。

注意

分片位长度 (S) 的选取:

  • 由于总位数固定为 64 位,分片位的数量会影响到自增位的数量:当分片位数增加时,自增位数会减少,反之亦然。因此,你需要权衡“自动分配值的随机性”以及“可用空间”。
  • 最佳实践是将分片位设置为 log(2, x),其中 x 为当前集群存储引擎的数量。例如,一个 TiDB 集群中存在 16 个 TiKV,分片位可以设置为 log(2, 16),即 4。在所有 Region 被均匀调度到各个 TiKV 上以后,此时大批量写入的负载可被均匀分布到不同 TiKV 节点,以实现资源最大化利用。

值域长度 (R) 的选取:

  • 通常,在应用程序的数值类型无法表示完整的 64 位整数时需要设置 R 参数。
  • 例如:JSON number 类型的取值范围为 [-(2^53)+1, (2^53)-1],而 TiDB 很容易会为 AUTO_RANDOM(5) 的列分配超出该范围的整数,导致应用程序读取该列的值时出现异常。此时,你可以用 AUTO_RANDOM(5, 54) 代替 AUTO_RANDOM(5),使得 TiDB 不会分配出大于 9007199254740991 (2^53-1) 的整数。

UNIQUE KEY uk_store_id_visit_date_visit_hr (store_id ,visit_date_hour ,index_dt )

有没有可能是唯一键冲突了导致没有插入成功,而不是主键?就目前来说,要佐证你的怀疑,需要进一步排除唯一键影响。

应该没关系,这里说的id超长应用读取可能出问题,但写入数据库肯定没问题

java插入出现唯一索引冲突会报错的

才9000多万行数据,AUTO_RANDOM就出现重复,那就是重大BUG了,应该不大可能;
你可以改下代码,不要使用insert into … on duplicat update key的语法,直接使用insert into ,如果真的因为重复也有异常,便于排查问题,每次insert into时,也可以把ID打印出来,先确认是否是真的AUTO_RANDOM出现了重复。

不会,查java日志看看

你可以看一下我帖子中的给的关键信息哈:insert into … on duplicate key update 时,我是不包含id字段,也就是id字段是tidb6 自己生成。 然后如果此时插入的数据unique key 是重复的,on duplicate key update 会把插入的这条数据和已经存在的重复那条合并。 这个是on duplicate key update 的特性。

on duplicate update key 特性就是不会报错。如果只有是insert into,当id重复才会报错。

嗯,我也在反复测试了,暂时还没复现,所以昨天才发贴咨询。 目前会继续尝试验证。

1 个赞

因为ticdc 发到kfk的数据,目前还有留存,这个过程相同的这批数据【9000+万】我重放过好几次了,暂时没有复现。

暂时是猜测,除了人为删除以及AUTO_RANDOM(5) id重复,想不到还有哪一种场景会让这条数据会丢失。 关于tidb 的on duplicate key update 和 AUTO_RANDOM(5)的资源,目前暂时还比较少。

tidb4----> tidb6整个过程中,每插入一条数据时,都会把tidb4中的id打印出来的,只是因为tidb6中我让id是audo_random生成,所以没有打印tidb6的数据id

AUTO_RANDOM(5) 的话,说明最大值是能够达到 2^59,这是一个极大的值
简单验证下就是: max(id)&0x07ffffff ffffffff 就可以看看到多大了

其次,如果是因为id 生成重复,那比如是溢出之后从头开始,你这9000万没到这个数量级(2^32 就 20亿了,你这9千万不够看的)
那要么就是表中原来就有数据?
或者就是那位仁兄说的 uk 重复?