PostgreSQL數(shù)據(jù)庫(kù)自動(dòng)生成主鍵通常有如下幾種方式:
- sequence序列
- serial和bigserial偽類型
- identity columns
- UUIDs
- BEFORE INSERT觸發(fā)器
這些主鍵方式使用了兩個(gè)基本的方法:序列和UUID算法。
這兩種基本方法在其它數(shù)據(jù)庫(kù)使用非常廣泛,UUID更是在軟件開發(fā)中作為最常用的通用標(biāo)識(shí)符。
最近在github看到一個(gè)足可與UUID競(jìng)爭(zhēng)的對(duì)手,它就是Nano ID。
下面是Nano ID的官網(wǎng)介紹:
https://github.com/ai/nanoid/blob/main/README.zh-CN.md
它是一個(gè)小巧、安全、URL友好、唯一的JavaScript字符串ID生成器。
Nano ID的特點(diǎn)
- 小巧. 130 bytes (已壓縮和 gzipped), 沒(méi)有依賴, Size Limit 控制大小。
- 快速. 它比UUID快兩倍。
- 安全. 它使用硬件隨機(jī)生成器,可在集群中使用。
- 緊湊. 它使用比UUID(A-Za-z0-9_-)更大的字母表。 因此,ID大小從36個(gè)符號(hào)減少到21個(gè)符號(hào)。
- 易用. Nano ID 已被移植到20種編程語(yǔ)言,包括C#、C++、Clojure and ClojureScript、ColdFusion/CFML、Crystal、Dart & Flutter、Deno、Go、Elixir、Haskell、Janet、Java、Nim、OCaml、Perl、PHP、Python with dictionaries、Ruby、Rust、Swift、Unison、V
與UUID的比較
Nano ID與UUID v4 (基于隨機(jī)) 相當(dāng)。 它們?cè)?ID 中有相似數(shù)量的隨機(jī)位 (Nano ID 為126,UUID 為122),因此它們的沖突概率相似:
要想有十億分之一的重復(fù)機(jī)會(huì), 必須產(chǎn)生103萬(wàn)億個(gè)版本4的ID.
Nano ID 和 UUID v4之間有三個(gè)主要區(qū)別:
- Nano ID使用更大的字母表,所以類似數(shù)量的隨機(jī)位被包裝在21個(gè)符號(hào)中,而不是36個(gè)。
- Nano ID代碼比uuid/v4 包少 4倍: 130字節(jié)而不是483字節(jié)。
- 由于內(nèi)存分配的技巧,Nano ID 比 UUID 快兩倍。
下面這個(gè)網(wǎng)址顯示這兩者之間npg的趨勢(shì)
https://www.npmtrends.com/nanoid-vs-uuid

下面的統(tǒng)計(jì)數(shù)據(jù)可以看到Nano ID星星數(shù)量(23k)超過(guò)了UUID(14k)。

基準(zhǔn)測(cè)試
$ node ./test/benchmark.js
crypto.randomUUID 25,603,857 ops/sec
@napi-rs/uuid 9,973,819 ops/sec
uid/secure 8,234,798 ops/sec
@lukeed/uuid 7,464,706 ops/sec
nanoid 5,616,592 ops/sec
customAlphabet 3,115,207 ops/sec
uuid v4 1,535,753 ops/sec
secure-random-string 388,226 ops/sec
uid-safe.sync 363,489 ops/sec
cuid 187,343 ops/sec
shortid 45,758 ops/sec
Async:
nanoid/async 96,094 ops/sec
async customAlphabet 97,184 ops/sec
async secure-random-string 92,794 ops/sec
uid-safe 90,684 ops/sec
Non-secure:
uid 67,376,692 ops/sec
nanoid/non-secure 2,849,639 ops/sec
rndm 2,674,806 ops/sec
測(cè)試配置: ThinkPad X1 Carbon Gen 9, Fedora 34, Node.js 16.10.
安全性
請(qǐng)看一篇關(guān)于隨機(jī)生成器理論的好文章: Secure random values (in Node.js)
-
不可預(yù)測(cè)性. 不使用不安全的 Math.random(), Nano ID使用Node.js的crypto模塊和瀏覽器的Web Crypto API。 這些模塊使用不可預(yù)測(cè)的硬件隨機(jī)生成器。
-
統(tǒng)一性. random % alphabet 是編寫ID生成器時(shí)常犯的一個(gè)錯(cuò)誤。 符號(hào)的分布是不均勻的; 有些符號(hào)出現(xiàn)的幾率會(huì)比其他符號(hào)低。因此, 它將減少刷新時(shí)的嘗試次數(shù)。 Nano ID 使用了一種更好的算法,并進(jìn)行了一致性測(cè)試。

-
有據(jù)可查: Nano ID所有的行為都有記錄。參考源代碼中的注釋。
-
漏洞: 報(bào)告安全漏洞,請(qǐng)與安全聯(lián)系人Tidelift security contact協(xié)調(diào)修復(fù)和披露。
PostgreSQL如何支持Nano ID
github上提供了Nano ID的PostgreSQL實(shí)現(xiàn):
先創(chuàng)建pgcrypto擴(kuò)展
CREATE EXTENSION if not exists pgcrypto;
再創(chuàng)建nanoid函數(shù)
CREATE OR REPLACE FUNCTION nanoid(size int DEFAULT 21)
RETURNS text AS $$
DECLARE
id text := '';
i int := 0;
urlAlphabet char(64) := 'ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW';
bytes bytea := gen_random_bytes(size);
byte int;
pos int;
BEGIN
WHILE i < size LOOP
byte := get_byte(bytes, i);
pos := (byte & 63) + 1; -- + 1 because substr starts at 1 for some reason
id := id || substr(urlAlphabet, pos, 1);
i = i + 1;
END LOOP;
RETURN id;
END
$$ LANGUAGE PLPGSQL VOLATILE;
使用nanoid函數(shù),我們可以控制想要的長(zhǎng)度,比如使用nanoid(2)更快,生成更簡(jiǎn)單的id:
postgres=# select nanoid(2);
nanoid
--------
sz
(1 row)
默認(rèn)長(zhǎng)度是21:
postgres=# select nanoid();
nanoid
-----------------------
YRlOhP53juqe-r68Faok3
(1 row)
接著我們?cè)赑ostgreSQL 16.2里對(duì)Nano ID和UUID進(jìn)行簡(jiǎn)單的插入性能對(duì)比測(cè)試,表結(jié)構(gòu)如下:
create unlogged table test_uuid (
id uuid default gen_random_uuid() primary key
);
create unlogged table test_nanoid (
id character varying default nanoid() PRIMARY KEY
);
使用本地虛擬機(jī)(內(nèi)存1G,CPU 1核)單條insert批量插入一萬(wàn)條數(shù)據(jù)。
select 'insert into test_uuid values('||repeat('default),(',99999)||'default);';\gexec
或者
select 'insert into test_nanoid values('||repeat('default),(',99999)||'default);';\gexec
測(cè)試結(jié)果:UUID花銷大約200毫秒,Nano ID花銷大約1474毫秒。
這是因?yàn)閁UID類型在PostgreSQL里可以很好的映射為byte存儲(chǔ),而Nano ID必須存儲(chǔ)在text/varchar,并且PostgreSQL從v13開始已在內(nèi)核層支持UUID生成函數(shù),受這兩個(gè)因素的影響,目前UUID的性能比Nano ID要好。
不過(guò)相比UUID,從上面Nano ID的特性介紹來(lái)看,Nano ID有很大的優(yōu)勢(shì),也許在PostgreSQ里很快能看到對(duì)Nano ID原生或者插件的支持。
參考文章
https://github.com/ai/nanoid
https://www.npmtrends.com/nanoid-vs-uuid
https://blog.bitsrc.io/why-is-nanoid-replacing-uuid-1b5100e62ed2
https://dev.to/bibekkakati/nanoid-alternative-to-uuid-2kgn
https://www.libhunt.com/compare-nanoid-vs-pg_random_id
https://github.com/Jakeii/nanoid-postgres
保持聯(lián)系
現(xiàn)組建了一個(gè)PG樂(lè)知樂(lè)享交流群,歡迎關(guān)注文章的小伙伴加微信進(jìn)群吹牛嘮嗑,交流技術(shù)。





