编程中,我们常常需要为“数据”指定 ID,那什么样的类型才“够大”呢?int? long? UUID? 这篇文章里,咱们从直觉的角度聊一聊“数字”有多大。

首先为了简便计算,我们需要一个将 $2^a$ 转成 $10^b$ 的方法: $b \approx 0.3 \times a$。如 $ 2^{10} \approx 10^3 $ 就是我们熟悉的 $ 1024 \approx 1000 $。证明的方法也就是简单地算算对数,这里不展开了。

#32 位

Java 中的 int 类型默认 32 位;C/C++ 中的类型具体长度与机器相关,但一般认为 int 占 4 个字节,即 32 位;SQL 中的 INT 型也是 32 位。可以说 32 位是现代计算机系统中最常见的数据类型了。问题是,用它当 ID,“够大” 吗?

2^32 = 4,294,967,296 (42亿) ~= 10^9

那么用 int 来给全世界每人发一个 ID 肯定是不够用了,给中国每人发一个 ID 还是够用的,但考虑到每年的死亡和新增人口,恐怕不久 ID 也会用完。

另外地球的年龄是 45.43 亿年,因此用 int 也是没法满足的。

32 位还很容易换算成 4G,也因此 32 位计算机最多只能使用 4G 的内存(不完全正确,有一些其它的手段能超过 4G)。后来计算机推广到 64 位也与此有关。

尽管 32 位二进制看起来“不够大”,它也“不小”。假如记录心跳,假设一分钟 60 下,那么 32 位二进制可以用 2^32/(60*60*24*365) = 136 年。

因此 32 位适用于一些直觉上“不大”的数据。一般来说,如果数据不会随时间持续增加,就想想有没有中国人口多;随时间不断新增的数据,想想有没有心跳快。如果不会,都可以用 32 位来标识。

#64 位

Java 中的 long,C/C++ 中的 long long,SQL 中的 BIGINT 都指的是 64 位。那么它有多大呢?

2^64 = 18,446,744,073,709,551,616 (1千亿亿) ~= 10^19

这是多大呢?例如 2014 年双十一期间,支付宝的交易峰值是 285 万笔/分钟, 假设用64位的 ID 来标记每一笔交易,可以用 2^64/(2850000 * 60 * 24 * 365) = 12,314,577 约一千万年。

另外,你知道宇宙中有多少星星吗?当然目前并没有权威的说明,但大概在 $10^{20}$ ~ $10^{24}$ 左右。显然用 64 位二进制并不足以标注宇宙中的每一颗星星,但它们基本是在一个量级上的。

因此这里的建议是,只要不是想数星星,用 64 位二进制来标识数据都是足够的。

#128 位

既然 64 位已经足够标识几乎所有的数据类型,128 位有用武之地吗?首先,64 位用来数星星还是不够的,其次是,我们还会有随机生成 ID 的需求,这也是 UUID/GUID 的用处。

新建数据时,我们常常需要为它们赋上 ID,且需要全局唯一。java 的 Hibernate 允许我们在数据生成时先暂时将 ID 置为 0,待存入数据库时再由数据库生成全局唯一的ID。但如果新生成的数据的 ID 需要被立即引用时(如生成一个图,每个节点都需要全局唯一 ID,且新生成的节点会被其它节点引用),这个方法就行不通。当然可以专门提供一个ID 生成器的服务来获取 ID,但它的复杂性就极大地上升了。

这时,使用 UUID 就是一个极佳的选择。它允许我们随机生成一个 UUID,且保证随机生成的 ID 是全局唯一的。当然,严格来说生成的 ID 还是会重复的,但概率极低,低到在实际使用时可以忽略。

参考维基百科 ,利用生日导论,可以计算两个随机生成的 UUID 重复的概率(真正有效的是 122 位,其它位被用来标识 UUID 的版本)是:

$$p(n) \approx 1-e^{- n^2/2x}$$

取 $x = 2^{122}$ 有下表:

随机生成个数 重复概率
$68,719,476,736 = 2^{36}$ $0.0000000000000004 (4 \times 10^{-16})$
$2,199,023,255,552 = 2^{41}$ $0.0000000000004 (4 \times 10^{-13})$
$70,368,744,177,664 = 2^{46}$ $0.0000000004 (4 \times 10^{-10})$

而如果要达到生成的 UUID 相同的概率超过 50% 则需要生成 $2.71 \times 10^{18} \approx 10^{60}$ 个 UUID。

因此,UUID 适用于需要随机生成全局唯一 ID 的情形。当然如果是密码学相关的数会需要更多的位数,这里就不讨论了。

顺代一提,IPv6 地址是 128 位的,号称能给整个地球的每一粒沙子都赋一个地址,其实 128 位($2^{128} \approx 10^{38}$)是远远超过地球的沙子数 (约 $10^{23}$)的,当然地址有一定规则,所以可用地址会少于 $2^{128}$ 个,这里也不讨论了。

#小结

如果数据量在直觉上不太,32 位的 ID 就足够;如果 32 位不够,则 64 位(几乎)一定够用;如果需要随机生成全局唯一 ID,使用 128 位的 UUID。

希望上面的讨论能指导大家选择 ID 的位数,同时增加一些选择时的信心。