【杂谈】主键ID如何选择——自增数 OR UUID?

1.生成位置如何影响选择?

数据库往返时间

使用自增数时,ID是由数据库在执行INSERT操作时生成的;而UUID则可以在应用层生成。

考虑这样的场景:

一个方法需要插入A和B两个实体。其中B的数据需要引用A的ID。

如果使用自增ID,则必须与数据库进行两次网络请求:

1.先插入A,获取A的ID;

2.然后使用A的ID构建B,再插入B。

而如果使用UUID,可以在应用层同时生成A和B的ID,之后一次性将两个实体提交给数据库,从而减少网络往返的次数,提高效率。

ID冲突

自增数在一个表内不会冲突,那两个表呢?

当业务发展到需要分库分表的时候,就不能再使用自增数作为ID了,因为两个库/表会生成相同的ID。这种ID冲突且不报错的问题,会导致数据混乱。

UUID虽然具有全球唯一性,极端情况也会有冲突,所以应用层需要想办法处理,确保ID唯一性,例如IID生成结合机器ID。

2.有序与无序如何影响选择?

ID是否有序对数据库性能有重要影响,特别是使用B-Tree索引。

**有序的ID:**对B-Tree索引来说,键值有序,那么连续插入的数据都会一个或少数几个节点上,这意味着,数据库只需要对少量的物理块进行I/O读写,I/O范围小,性能较好。

eg:连续插入20条数据,都在一个节点上,只要加载一个物理块。

**无序的ID:**键值无序,那么连续插入的数据可能随机分布在各个节点上,数据库就需要对大量的物理块进行I/O读写,性能较差。

eg:连续插入20条数据,经计算它们分布在20个不同节点上,需要加载20个物理块。

显然,自增ID是有序的,所以在大多数情况下,它的性能优于无序的UUID。

值得一提的是,UUID也有多个版本,其中一些是有序的,例如UUIDv7和雪花算法(Snowflake)

而Java JDK默认使用的UUID是v4版本,是无序的。

3.ID大小如何影响选择?

ID大小指占用的存储空间。自增数通常是INT或BIGINT,分别是4字节和8字节。UUID一般由32个字符组成,占16字节。

树的高度

在B-Tree中,对于固定大小的节点,键值大小会影响一个节点能存储的键值数量(这里就是ID数量)。影响了单个节点存储的键值,就会影响节点数量,进而影响树的高度。

而树越高,一次查询需要访问的节点就更多,查询就更慢。

内存开销

相同数量的记录,ID的大小会影响到数据库引擎的缓存(索引和数据行)。同样大小的缓存空间,单个ID越大,能缓存的数据就越少。

例如:InnoDB使用innodb_buffer_pool_size控制缓存池大小

总结

总的来说,有序的UUID(UUIDv7,雪花算法)更适合分布式服务,自增ID更适合单机服务。

但是也要看具体业务,举个例子,如果分表的数据不存在跨表访问的情况,那么ID冲突就冲突了,没有任何影响。

而且,ID策略也能混着用,一个数据库里面不同表可以用不同的ID生成策略。