自增还是UUID?这个问题看似简单,但是能诱发很多思考,也涉及到了很多细节。网上的确有很多有关这方面的资料,但是比较乱,今天我结合这些资料和自己的心得,单独对这个话题写一篇博客出来,希望对大家有所帮助哈。
先说下uuid和 auto_increment(数据库自增主键)的优缺点吧,因为是个人理解,如有错误恳请指出:
auto_incremen的优点:
- 字段长度较uuid小很多,可以是bigint甚至是int类型,这对检索的性能会有所影响。我们平时数据库一般用的都是innodb引擎的表,这种表格检索数据的时候,哪怕走索引,也是先根据索引找到主键,然后由主键找到这条记录。所以主键的长度短的话,读性能是会好一点的。
- 在写的方面,因为是自增的,所以主键是趋势自增的,也就是说新增的数据永远在后面,这点对于性能有很大的提升(这点我接下来会在uuid的优缺点分析中解释,虽然用词可能不太专业)。
auto_incremen的缺点:
- 最致命的一个缺点就是,很容易被别人知晓业务量,然后很容易被网络爬虫教做人
- 高并发的情况下,竞争自增锁会降低数据库的吞吐能力
- 数据迁移的时候,特别是发生表格合并这种操作的时候,会非常蛋疼
接下来说下uuid的优点:
- 地球唯一的guid,绝对不会冲突
- 可以在应用层生成,提高数据库吞吐能力
- 是string类型,写代码的时候方便很多
uuid的缺点
- 与自增相比,最大的缺陷就是随机io。这一点又要谈到我们的innodb了,因为这个默认引擎,表中数据是按照主键顺序存放的。也就是说,如果发生了随机io,那么就会频繁地移动磁盘块。当数据量大的时候,写的短板将非常明显。当然,这个缺点可以通过nosql那些产品解决。
- 读取出来的数据也是没有规律的,通常需要order by,其实也很消耗数据库资源
- 看起来比较丑
这样子一看,似乎两种主键方案都有其致命的缺点,那怎么搞呢?比如遇到必须用mysql但是量比较大,并发比较高的情况?其实行业内大鳄已经给出了一个比较好的解决方案,那就是推特的雪花算法(snowflake):
雪花算法简单描述:
+ 最高位是符号位,始终为0,不可用。
+ 41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。
+ 10位的机器标识,10位的长度最多支持部署1024个节点。
+ 12位的计数***,***即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号,12位的计数***支持每个节点每毫秒产生4096个ID序号。
看的出来,这个算法很简洁也很简单,但依旧是一个很好的ID生成策略。最高的那几位是时间戳,这能保证不管是几台机子在跑主键的生成必然是趋势自增的,基本上下一毫秒产生的主键必然比上一毫秒的大。最后的几位是加锁的方法生成的,一台机器一毫秒钟4096个,够用了吧?唯一的难点是机器号的生成。我想了想,大概有几个方法:
- 根据机器的mac地址等信息生成,简单点的话,就根据机器的ip生成(这样子做是有风险的,哪怕保证买来的机器都是连号的,根据ip计算一样可能导致算出同样的机器id)
- 用zookeeper给机器发号,这是最稳妥的,真要用雪花算法生成线上数据的id,我觉得还是得靠这个方案
- 用其他中间组件生成,比如redis自增等原子性操作获取机器编号。
zk那些我不是很会,我就拿第三种方案,结合spring data jpa写个小demo:
然后是主键生成器的实现类,需要实现接口:
当然判断的逻辑可以再写详细点。
这里要注意的是,redis的加载必须要在db之前完成,所以redis和db的配置文件需要改变下:
这种方案比较简单,但是的确也存在一个危险周期(如果这个机器编号是取模生成的),而且万一redis数据被清空了,可能机器编号也会重复…网上关于这方面的资料比较少,除了zk发号(其实我也不会),我也想不到有更好的办法,如果大家有更好的方案,可以随时留言或者找我…
我这边有新的好的想法也会随时更新这篇博客~