sql - 如何在 Postgres 9.6+ 中生成长度为 N 的随机、唯一、字母数字 ID?

标签 sql database postgresql random

我在 StackOverflow 上看到了很多 different solutions,这些 gen_random_bytes 跨越了很多年和许多 Postgres 版本,但是有了一些像 IDs that Stripe uses 这样的新功能,我想再次问一下,看看在新版本中是否有更简单的解决方案。

给定的 ID 包含 a-zA-Z0-9,并且大小根据它们的使用位置而变化,例如...

bTFTxFDPPq
tcgHAdW3BD
IIo11r9J0D
FUW5I8iCiS

uXolWvg49Co5EfCo
LOscuAZu37yV84Sa
YyrbwLTRDb01TmyE
HoQk3a6atGWRMCSA

HwHSZgGRStDMwnNXHk3FmLDEbWAHE1Q9
qgpDcrNSMg87ngwcXTaZ9iImoUmXhSAv
RVZjqdKvtoafLi1O5HlvlpJoKzGeKJYS
3Rls4DjWxJaLfIJyXIEpcjWuh51aHHtK

(就像 gen_random_uuid() 一样。)

在 Postgres 9.6+ 中,您如何通过一种简单的方法为不同的用例指定不同的长度来随机安全地生成它们(就减少碰撞和降低可预测性而言)?

我认为理想的解决方案具有类似于以下的签名:

generate_uid(size integer) returns text

size 是可自定义的,具体取决于您自己的权衡,以降低冲突的可能性与减小字符串大小以提高可用性。

据我所知,它必须使用 gen_random_bytes() 而不是 random() 来实现真正的随机性,以减少它们被猜到的机会。

谢谢!


我知道 UUID 有 ojit_a,但我不想在这种情况下使用它们。我正在寻找能为我提供类似于 Stripe(或其他人)使用的 ID 的东西,看起来像: .

这个要求也是为什么 encode(gen_random_bytes(), 'hex') 不太适合这种情况,因为它减少了字符集,因此迫使我增加字符串以避免冲突。

我目前在应用程序层执行此操作,但我希望将其移至数据库层以减少相互依赖性。以下是在应用程序层执行此操作的 Node.js 代码可能如下所示:

var crypto = require('crypto');
var set = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

function generate(length) {
  var bytes = crypto.randomBytes(length);
  var chars = [];

  for (var i = 0; i < bytes.length; i++) {
    chars.push(set[bytes[i] % set.length]);
  }

  return chars.join('');
}

最佳答案

想通了,这是一个函数:

CREATE OR REPLACE FUNCTION generate_uid(size INT) RETURNS TEXT AS $$
DECLARE
  characters TEXT := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  bytes BYTEA := gen_random_bytes(size);
  l INT := length(characters);
  i INT := 0;
  output TEXT := '';
BEGIN
  WHILE i < size LOOP
    output := output || substr(characters, get_byte(bytes, i) % l + 1, 1);
    i := i + 1;
  END LOOP;
  RETURN output;
END;
$$ LANGUAGE plpgsql VOLATILE;

然后简单地运行它:

generate_uid(10)
-- '3Rls4DjWxJ'

警告

执行此操作时,您需要确保您创建的 ID 的长度足以避免随着时间的推移随着您创建的对象数量的增加而发生冲突,因为 Birthday Paradox 这可能是违反直觉的。 因此对于任何通常创建的对象,您可能需要比 10 更大(或更大)的长度,我只是使用 10 作为一个简单示例。


用法

定义函数后,您可以在表定义中使用它,如下所示:

CREATE TABLE users (
  id TEXT PRIMARY KEY DEFAULT generate_uid(10),
  name TEXT NOT NULL,
  ...
);

然后在插入数据的时候,像这样:

INSERT INTO users (name) VALUES ('ian');
INSERT INTO users (name) VALUES ('victor');
SELECT * FROM users;

它将自动生成 id 值:

    id     |  name  | ...
-----------+--------+-----
owmCAx552Q | ian    |
ZIofD6l3X9 | victor |

使用前缀

或者您可能想在日志或调试器(类似于 how Stripe does it)中查看单个 ID 时方便地添加前缀,如下所示:

CREATE TABLE users (
  id TEXT PRIMARY KEY DEFAULT ('user_' || generate_uid(10)),
  name TEXT NOT NULL,
  ...
);

INSERT INTO users (name) VALUES ('ian');
INSERT INTO users (name) VALUES ('victor');
SELECT * FROM users;

      id       |  name  | ...
---------------+--------+-----
user_wABNZRD5Zk | ian    |
user_ISzGcTVj8f | victor |

关于sql - 如何在 Postgres 9.6+ 中生成长度为 N 的随机、唯一、字母数字 ID?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41970461/

相关文章:

sql - 我可以授予存储过程 SET IDENTITY_INSERT 权限吗?

java - 在 SQLite 中使用 Blob 字段作为查询参数

sql - 如何在 Apache Derby 中删除有限数量的行

node.js - 如何在 Postgres 中转换 OR 条件以对查询进行 Sequelize

.net - 无法从 .NET 应用程序连接到 Heroku Postgres (Npgsql)

sql - 存储过程和数据导出自动化

MySQL:什么是页面?

SQL Server 存储过程的含义

php - 为每个帖子动态添加列到 WordPress 自定义数据库表

postgresql - 使用 FreeSWITCH 的本地 PostgreSQL 支持编写 Lua 脚本?