202111项目总结:取MySQL UUID_SHORT后几位做流水号带来的重复问题

目录

问题背景:

问题爆发:

分析原因及临时解决方法:

分析原因

临时解决方法:

使用UUID_SHORT的一些其他限制或可能发生的问题:

MySQL中的Sequence问题解决


问题背景:

老项目从Oracle --> MySQL,原来Oracle中有Sequence,而MySQL中没有。项目工期又很紧,得知MySQL有UUID_SHORT,就拿来用了;不过UUID_SHORT返回的位数太多,值太大,就取后10位作为流水号。

问题爆发:

不过在使用了1、2年后,系统突然报了唯一键冲突。根据log分析,发现流水号重复了,又从很小的数值开始了。还好问题爆发在配置表中,没造成太大的生产问题(业务表的流水号生成规则:yyyyMMdd + UUID_SHORT后十位,虽然也造成了业务表流水号的混乱,但对业务几乎没影响  )。

分析原因及临时解决方法:

 

分析原因

问题发生后,对UUID_SHORT进行了一下研究,发现了问题发生的原因:

按照官方的说法,UUID_SHORT返回的值为64位无符号整数,也就是unsigned long long类型【长度为:(0 ~ 2^64-1) 10^19 20位数字】,MySQL启动后第一次执行的值是通过server_id << 56 + server_start_time << 24来初始化。server_start_time单位是秒。 之后每次执行都加1。由于使用到了时间戳,这个的初始值会很大(通常都会到17位数字)。但是,这并不保证其不会返回18,19,20位的数据,只能保证在2^64-1以内(最大达到20位数字)。

从我们的规则来看,取最后10位数字,落在了server_start_time<<24的区间内了;查历史数据,从第一次调用的数值来看,返回的已经是10位数字,不是我们理解的sequence其实位置从0开始。而且不幸的是,从实际情况来看,生产环境开始的数值是一个很大的10位数字;在每天调用几百万次、几千万次的情况下,用了不到2年,最终发生了UUDI_SHORT最后10位数累加到 9999999999(10个9),再调用1次就发生了进位,最后11位变成了1000000000(10个0),如果取后10位的话,就是10个0了,转成数字,就是0,又重新从0、1、2、3。。。。重头开始了; 这样新获得的流水号就和老流水号,原来有Oracle的Sequence产生的流水号重复了,发生唯一键冲突;
 

临时解决方法:

从UUID_SHORT的生成规则,以及MySQL的官方说明来看,UUID_SHORT不像Oracle Sequence一样,可以通过SQL语句来调整起始值,只能通过一次次的调用,来不断的把数值增加上去。因此通过调整UUID_SHORT的起始位置来解决问题的方法行不通。

还好,系统中自定义的流水号使用的都是一个类型,既然业务表的流水号能容纳  y yyyMMdd + UUID_SHORT 后10位数字,那么配置表也没问题。这样就修改了一下程序,拿业务表的流水号生成规则给配置表用,解决了当前问题。

隐含问题:

配置表流水号的规则,要求每次修改后,流水号的数值不断增大,这样修改解决了当前的问题,如果万一发生在一天内配置表修改2次之间,发生了UUID_SHORT后10位流水号进位从0开始,依旧会有问题发生(数据入库没问题,会发生业务问题,流水号不是递增了),这个就暂时不考虑了。目前刚发生进位,从0开始了,取后10位,那要使用100亿次才会在发生类似的情况,按照系统目前的流量,到下一次发生问题的时候,系统是否存在还是一个问题。

流量大的系统,只通过类似Sequence的方法获取递增的流水号,总可能会发生问题。如果不需要有递增的要求,只需要唯一,可以通过雪花算法等方式来解决唯一性的问题。对于配置表这种变动比较少的情况,可能要有递增的要求,可以单独配置一个sequence(MySQL可以通过编程方式实现Sequence,下面有简略描述),这样避免和业务流水表使用同一个sequence,造成流水号增长过快的问题;

使用UUID_SHORT的一些其他限制或可能发生的问题:

  • 初始值太大,无法重设(我们在测试环境下,想要增大UUID_SHORT返回值,自己写了一个脚本,循环调用UUID_SHORT,一般几千万次调用速度还是很快的;而Oracle的Sequence只要一条SQL语句就能搞定)
  • 存在一个问题是每次重启后第一次执行的值不是重启前的那个值+1
  • 而且如果重启在1s内完成,可能出现不单调递增(虽然这个可能性微乎其微)
  • 在server_id当前服务器的值介于0和255之间,您的设置主从服务器中是唯一的
  • 您不会在mysqld restarts 之间设置服务器主机的系统时间
  • UUID_SHORT()在mysqld重启之间, 你平均每秒调用的次数少于1600万次

MySQL中的Sequence问题解决

现在网上有很多的资料,一般解决方法都是: 

建一个表:至少包括这3个字段,sequence名字,当前值,每次增加值(步长)

写一个MySQL函数,或者在一个事务中,进行

  • update:新的当前值 = 当前值 + 步长
  • 返回新的当前值

就是这么简单;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值