自增id与其它方案

点击↑上方↑蓝色“编了个程”关注我~

228b39fc8571fe5bc4da691a95b4fecd.png

这是Yasin的第 67 篇原创文章

6bb1416fb39f2d68aa79102d43cf82fa.png

Y说

最近几天辗转了好几个城市。

武汉的生活气息比较浓,这是我第二次到武汉了,很有缘分的是,居然跟我几年前出差的是同一个地方,同一栋楼。热干面还是吃不惯,小龙虾还是很好吃。晚上看到广场上人来人往,突然觉得还有点羡慕他们。

ceed3afdca62abef7e1b60a9e14accfd.png

出差呆了一周,后来回了重庆老家,吃到了想念许久的腊肉回锅和豆花饭,老爸的厨艺还是那么好。第二天去了成都,和老朋友们见了一面,吃了正宗的火锅,不像杭州的火锅那么寡淡。办完事就要回杭州打工了,总是要继续和生活对线 :)

下面是正文~

背景

去年入职这个团队不久后,做了一件比较苦逼的差事,跟数据迁移相关的。由于之前的同事设计得不合理,把两个本该是同一个表的模型,设计成了两个表。而这两个表又都是主表,有一大堆关联表。这两个表都是用的自增id,它们的关联表也是关联的这个id。

两套表和逻辑都有大量相似之处,大大拖慢了整体的迭代速度。我们考虑把它们合并成一套,不过遇到的最头疼的问题就是这个id的问题了。假设我们有主表A和B,现在要把B的数据全部合到A中,但A、B都是自增id,中间有冲突的id。当时的处理办法是新增了一个字段用来保存B的老id,然后copy数据插入到A中。接口维持不变,改变B的数据的时候,双写到A表中,这样双写了一段时间,第二步才让前端改接口,慢慢过渡到使用A表的id。

可即使迁移方案已经足够完善和稳妥了,但仍然会有一些用户通过url直接访问等方式,用老id去查数据。可见自增id在涉及到数据迁移的时候,会带来非常高的复杂性,尤其是如果有下游系统使用了这个id,还得推动下游系统改造甚至洗数据,非常复杂。

Leader要求我们后续业务上都尽量有一个单独的业务code字段,不要复用id字段。比如订单号,通常都不是DB的自增id。那使用自增id和自己生成业务code都有什么优劣呢?自己生成code又有哪几种方式呢?

自增id

自增id是大多数数据库自带的能力,通过建表时使用sql语句AUTO_INCREMENT来设置一个字段为自增。自增id有以下优势:

  • 能保证原子性,不会因为并发产生重复的id

  • 增量增长,有序存放,不会产生”页分裂“的问题

  • 数字型,占用空间小,易排序,在程序中传递也方便

自增id有很多优点,适用于业务稳定,没有分库分表,且不会数据迁移的场景。但它也有一些缺点。

  • 数据迁移会比较麻烦,有可能发生主键冲突。尤其是有下游或关联表的时候,迁移成本非常大

  • 不适用于分库分表,因为自增id单表才能在DB层面保证原子性。分库分表用自增id会发生主键冲突

为了防止id冲突,也可以使用单表和Redis来作为自增id的生成器,但这会带来额外的复杂性。

雪花算法

那有什么好的方案可以代替自增id吗?业界一种比较通用的方案是雪花算法啊,它保留了自增id的一些优点:递增,数字类型,能保证原子性。

雪花算法有好几种,基本上很多大公司都根据自己的业务形态做了一定的变种。我之前写过一篇专门介绍几种雪花算法的文章,在我的个人网站可以搜索到:分布式全局唯一ID生成策略。

雪花算法主要用于「对性能要求比较高」的场景。但它需要引入外部的id生成器,给整体架构带来了额外的复杂性和稳定性风险。如果你的业务场景对性能要求并不高,可以尝试下面的方案。

UUID

UUID全称是Universally Unique Identifier,翻译过来叫通用唯一识别码。标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,示例:9628f6e9-70ca-45aa-9f7c-77afe0d26e05,到目前为止业界一共有5种方式生成UUID,详情见IETF发布的UUID规范《A Universally Unique IDentifier (UUID) URN Namespace》,分别称为UUID的5个版本。

Java自带的UUID.randomUUID()是UUID第4个版本,原生并不支持版本5,不过有开源的库可以支持。一般来说UUID4即可满足大多数需求。

UUID的优势是「实现起来很简单」,用JDK原生的API即可得到。劣势是与基于b-Tree引擎的数据库的主键索引策略不太符合,没有自增的属性,可能会产生页分裂。「不适合作为高性能需求的场景下的数据库主键」

自定义规则生成

还有一种生成code的方式是根据一些自定义的规则去生成id。比如业务上可以定义一个前缀,然后通过年月日+一个n位的自增数来实现code生成。

比如:我们预计用户每天不超过1w个,那这个n可以定义为5位。可以生成U2022070100001这样的code用来作为这个用户的唯一标识。

这里为了防止自增id的重复,也需要使用单表或者Redis来做自增的id生成器。这种也算是递增id的一种扩展吧,只是上面多了业务信息。比如我们通过这个id可以一眼就知道它是一个2022年7月1日创建的第一个用户。

当然,你也可以使用随机数而不是递增数来填充后面的部分,只要位数设置得足够大,发生冲突的概率就非常小,而且也可以从数据库层面设置唯一性校验。

我们用的身份证号就是用的类似的规则,上面有我们的省市区、性别、出生年月等信息,最后一位还可以用来校验号码的正确性。

总结

  • 如果不考虑数据迁移,场景简单,用自增id最方便

  • 对性能要求比较高的场景,用雪花算法

  • 对性能要求不高,考虑数据迁移,又想实现简单,用UUID

  • 对性能要求不高,考虑数据迁移,又想id能够反馈一些业务信息,用自定义规则生成

c9e352ea223cc6b0085a47cbbcdc9bd3.png

关于作者

我是Yasin,一个爱写博客的技术人

微信公众号:编了个程(blgcheng)

个人网站:https://yasinshaw.com

欢迎关注这个公众号5f7ed5405e38d6d203c7fcd8bf1ce5fc.png

da564a04b45fadc7999b7d491dca748f.png

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值