最近在工作中编写业务sql的时候,突然对于gen_random_uuid() 这个方法比较好奇,他在高并发的情况下是否拥有强一致性的特点(就是保证主键唯一性),趁着感兴趣研究了一波,发现有不少有意思的东西可以讨论,所以出了这篇文章来聊聊。
我估计很多读者根本不知道postgreSql是啥玩意,个人起初接触这个数据库也很别扭,并且这个名字很难记,所以业内人士一般叫读这块数据库为:post-gres-s-q-l,个人比较习惯叫做 pg-sql,关于这一款数据个人认为几个比较突出的特点:
然而遗憾的是虽然postgresql看起来全面强于mysql但是不如mysql流行,并且mysql看上去是有很多令人诟病的历史遗留问题,但是依然不可否认他依然是现在的主流数据库。好了关于pg-sql这款数据库就唠叨到这里,今天的主题不是介绍这个数据库,所以我们来看下重点关于postgre-sql生成uuid的方法。
关于本文,我们将会讨论下面几个话题:
Gen_random_uuid()
怎么来的?❝PostgreSQL 13: 新增内置函数
Gen_random_uuid()
生成UUID数据,换句话说这个版本之前需要用手动的安装形式❞
uuid_generate_v4()
有没有可能重复?❝答案是肯定的,哪怕是sql原始的gen_randowm_uuid方法也是存在重复的可能性的,但是存在某些“特殊条件”,下面来一起探讨一下原因。❞
❝差异主要是生成随机数的方式上,其他工作基本一致。❞
Gen_random_uuid()
怎么来的?如果postgre-sql的版本使用的是13之前,会抛出下面的问题:
function gen_random_uuid() does not exist
如果想要能够使用此方法,需要使用如下的命令,也就是使用 pgcrypto :
CREATE EXTENSION pgcrypto;
下面是postgresql-sql 12版本,会出现如下的提示。
# SELECT gen_random_uuid();
ERROR: function gen_random_uuid() does not exist
LINE 1: select gen_random_uuid();
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
# CREATE EXTENSION pgcrypto;
CREATE EXTENSION
# SELECT gen_random_uuid();
gen_random_uuid
--------------------------------------
19a12b49-a57a-4f1e-8e66-152be08e6165
(1 row)
但是需要注意的是13版本添加的并不是这个库,而是「依赖于OSSP库的uuid函数」。其实就是gen_random_uuid_v4()
这个函数,下面我们来看下这个函数的介绍。
函数有什么作用不必多说,下面是关于官方的介绍:
We create a uuid_t object just once per session and re-use it for all
operations in this module. OSSP UUID caches the system MAC address and
other state in this object. Reusing the object has a number of benefits:
saving the cycles needed to fetch the system MAC address over and over,
reducing the amount of entropy we draw from /dev/urandom, and providing a
positive guarantee that successive generated V1-style UUIDs don't collide.
(On a machine fast enough to generate multiple UUIDs per microsecond,
or whatever the system's wall-clock resolution is, we'd otherwise risk
collisions whenever random initialization of the uuid_t's clock sequence
value chanced to produce duplicates.)
如果看不懂,下面是谷歌翻译之后的介绍:
我们每个会话只创建一个 uuid_t 对象,并为所有人重新使用它本模块中的操作。OSSP UUID 缓存系统 MAC 地址和此对象中的其他状态。
重用对象有很多好处:
1. 节省一遍又一遍地获取系统 MAC 地址所需的周期,
2. 减少我们从 /dev/urandom 中提取的熵量,并提供一个积极保证连续生成的 V1 风格的 UUID 不会发生冲突。
(在足够快的机器上每微秒生成多个 UUID,或者无论系统的时钟分辨率是多少,否则我们会冒险
每当随机初始化 uuid_t 的时钟序列时发生冲突值机会产生重复。)
翻译之后发现依然比较拗口,个人其实也看不懂,所以这里又找了一篇文章,下面简单说明一下这篇文章的内容和含义。
❝参考文章:Is Postgres's uuid_generate_v4 securely random?❞
问题:这位老哥的大致问题就是他使用了postgresql v4版本的uuid() 来生成一个access token的密钥令牌,并且询问是否线程安全(uuid是否唯一),以及是否需要使用应用端保证唯一性,这正好符合这篇文章的主题。
下面是分析之后的个人总结出来的答案(每个人理解能力不同,不一定完全正确):
总结来说就是,基于上面三个点,虽然uuid-ossp在通常情况下可以保证强唯一性,但是存在退化为弱唯一性的可能性,甚至最坏的情况是使用机器的时钟点来生成uuid造成重复uuid,所以这位答主最终的建议是:「谨慎建议不要依赖 PostgreSQL 生成的 UUID 的强随机性,而是在应用程序端明确使用强随机源」 。
❝有读者会问「PRNG是啥?伪随机数生成器」 (pseudo random number generator,「PRNG」 ),又被称为「确定性随机比特生成器」 (deterministic random bit generator,「DRBG」 ),[1]是一个生成数字序列的算法,其特性近似于随机数序列的特性。PRNG生成的序列并不是真随机,因此它完全由一个初始值决定,这个初始值被称为PRNG的随机种子(seed,但这个种子可能包含真随机数)。尽管接近于真随机的序列可以通过硬件随机数生成器生成,但伪随机数生成器因为其生成速度和可再现的优势,在实践中也很重要。[2]。 话外题:其实很多的策略游戏就是用了伪随机数的算法。❞
通过上面的铺垫,我们大致了解了uuid_generate_v4() 的基本实现,下面我们来进行一个简单的总结。
产生情况:
gen_random_uuid()
和 uuid_generate_v4()
没错,这个也是参考文章的,并且对比了很多资料发现下面这个答案简洁明了:
❝参考文章:PostgreSQL 生成 UUID 的两种不同方式:gen_random_uuid 与 uuid_generate_v4s❞
首先是关于这两个函数的直接区别:
gen_random_uuid()
扩展提供pgcrypto
uuid_generate_v4()
扩展提供uuid-ossp
从这篇参考文章得出的根本结论就是:
uuid_generate_v4()
使用 「arc4random」 来确定随机部分。gen_random_uuid()
使用「fortuna」代替实现。关于这两个算法区别这里就不再进行展开了,有条件的可以看一下下面的维基的链接介绍,如果访问不了也可以自行上网查阅资料,都是一些比较理论化的东西,这里就不再继续深入追究了。
❝参考文章:https://en.wikipedia.org/wiki/RC4#RC4-based_random_number_generators❞
用这篇文章其实是想告诉各位,对待数据库的uuid生成方法需要结合实际的业务是否需要保证uuid的强唯一性,如果需要则强烈建议不要依赖数据库的实现方式,特别是在并发量十分高的情况下,是十分不可靠的。
最后如果发现有任何错误欢迎指正。