PostgreSQL 的 random()
函数用于生成一个随机的双精度浮点数,可以将其转换为较小的随机整数,但是双精度浮点数无法表示 bigint 中的所有值1,本文介绍几种 PostgreSQL 生成 bigint 随机数的方法。
UUID
我们可以提取 gen_random_uuid
生成的随机位将其转换为 bigint,如:
postgres=# select ('x'||right(uuid_send(gen_random_uuid())::text, 16))::bit(64)::int8;
int8
----------------------
-8048312917378578936
(1 row)
但了解 UUIDv4 的同学一眼就能看出问题,虽然 UUIDv4 中的 122 位都是随机数,但上述方法使用的后 64 位中的前两位一定是 0b10
(参考: PostgreSQL 引入 UUIDv7),因此上述方法生成的随机 bigint 一定是个负数。所以我们需要从 UUIDv4 中截取两段数据才能生成一个随机的 bigint:
postgres=# CREATE OR REPLACE FUNCTION gen_random_bigint_from_uuid() RETURNS INT8 AS $$
DECLARE
bytes text;
BEGIN
bytes := uuid_send(gen_random_uuid())::text;
RETURN ('x' || substring(bytes, 3, 2) || right(bytes, 14))::bit(64)::int8;
END;
$$ LANGUAGE plpgsql;
CREATE FUNCTION
postgres=# select gen_random_bigint_from_uuid() from generate_series(1,5);
gen_random_bigint_from_uuid
-----------------------------
2561119870940783961
-8836220962687819417
-5760399065689643664
-8861236575235818608
7084792777938647791
(5 rows)
注意 UUIDv7 因为只有 62 位的随机值,因此不能直接用来生成 bigint 随机值,不过可以结合 random()
函数进行拼接,此不详述。
pg_random_bytes
内置插件 pgcrypto 提供的 pg_random_bytes()
函数可以生成随机 bytea,可以将此拼接为随机 bigint:
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE OR REPLACE FUNCTION gen_random_int8() RETURNS INT8 AS $$
DECLARE
bytes bytea;
BEGIN
bytes := gen_random_bytes(8);
RETURN
(get_byte(bytes,0)::int8 << 8*0) |
(get_byte(bytes,1)::int8 << 8*1) |
(get_byte(bytes,2)::int8 << 8*2) |
(get_byte(bytes,3)::int8 << 8*3) |
(get_byte(bytes,4)::int8 << 8*4) |
(get_byte(bytes,5)::int8 << 8*5) |
(get_byte(bytes,6)::int8 << 8*6) |
(get_byte(bytes,7)::int8 << 8*7);
END;
$$ LANGUAGE plpgsql;
或者直接用 encode
函数将其编码为 hex 再显示转为 bigint:
postgres=# select ('x'||encode(gen_random_bytes(8), 'hex'))::bit(64)::int8 from generate_series(1,5);
int8
----------------------
-8930476623996945156
-5126984886511757661
-6072786556490332588
3278019797672496176
-2060333909321021593
(5 rows)
/dev/urandom
/dev/urandom
是一个在类 Unix 系统中用于生成高质量伪随机数据的特殊文件,常用于各种安全应用和加密操作。PostgreSQL 提供的 pg_read_binary_file
可以从该文件中读取随机数据,进而转换成随机 bigint:
postgres=# select ('x'||encode(pg_read_binary_file('/dev/urandom', 0, 8), 'hex'))::bit(64)::bigint from generate_series(1, 5);
int8
----------------------
1349504348694981204
4053722996198320118
7178451800259674372
-1207924631398290525
3857806568552511469
(5 rows)
random()
既然 random()
能完整表示 32 位整数,那将两次 random()
获得的 32 位整数进行拼接自然可以获得一个随机的 64 位整数:
postgres=# select ((random() * 4294967296)::int8::bit(32) || (random() * 4294967296)::int8::bit(32))::bit(64)::int8 from generate_series(1,5);
int8
----------------------
-6168096176574416042
8362256655007089958
4100237736368010094
5317107880510500415
7978814854907299951
(5 rows)
性能对比
本文介绍了 5 种生成随机 bigint 值的方法,对这五种方法的执行效率做一个对比:
postgres=# explain analyze select gen_random_bigint_from_uuid() from generate_series(1, 1000000);
Time: 6097.280 ms (00:06.097)
postgres=# explain analyze select gen_random_int8() from generate_series(1, 1000000);
Time: 4712.437 ms (00:04.712)
postgres=# explain analyze select ('x'||encode(gen_random_bytes(8), 'hex'))::bit(64)::int8 from generate_series(1, 1000000);
Time: 3080.222 ms (00:03.080)
postgres=# explain analyze select ('x'||encode(pg_read_binary_file('/dev/urandom', 0, 8), 'hex'))::bit(64)::bigint from generate_series(1, 1000000);
Time: 19539.961 ms (00:19.540)
postgres=# explain analyze select ((random() * 4294967296)::int8::bit(32) || (random() * 4294967296)::int8::bit(32))::bit(64)::int8 from generate_series(1, 1000000);
Time: 1170.033 ms (00:01.170)
可见,将两个 random()
生成的值进行拼接生成 bigint 的性能最好。
本文灵感来自 pg-general 邮件列表的问题:How to generate random bigint