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

新手上路,请多包涵

我在 StackOverflow 上看到了许多 不同的解决方案,这些解决方案跨越了多年和许多 Postgres 版本,但有一些较新的功能,例如 gen_random_bytes 我想再次询问是否有更新的更简单的解决方案版本。

给定的 ID 包含 a-zA-Z0-9 ,并且大小取决于它们的使用位置,例如…

 bTFTxFDPPq
tcgHAdW3BD
IIo11r9J0D
FUW5I8iCiS

uXolWvg49Co5EfCo
LOscuAZu37yV84Sa
YyrbwLTRDb01TmyE
HoQk3a6atGWRMCSA

HwHSZgGRStDMwnNXHk3FmLDEbWAHE1Q9
qgpDcrNSMg87ngwcXTaZ9iImoUmXhSAv
RVZjqdKvtoafLi1O5HlvlpJoKzGeKJYS
3Rls4DjWxJaLfIJyXIEpcjWuh51aHHtK

(就像 Stripe 使用的 ID 一样。)

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

我认为理想情况下,该解决方案的签名类似于:

 generate_uid(size integer) returns text

其中 size 可根据您自己的权衡来定制,以降低冲突的机会与减小字符串大小以提高可用性。

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

谢谢!


我知道有 gen_random_uuid() 用于 UUID,但我不想在这种情况下使用它们。我正在寻找能够给我提供类似于 Stripe(或其他人)使用的 ID 的东西,看起来像: "id": "ch_19iRv22eZvKYlo2CAxkjuHxZ" 尽可能短,同时仍然只包含字母数字字符。

这个要求也是为什么 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('');
}

原文由 Ian Storm Taylor 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 929
2 个回答

想通了,这是一个功能:

 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 的长度足以避免随着时间的推移随着您创建的对象数量的增加而发生冲突,由于 生日悖论,这可能是违反直觉的。 因此,对于任何合理普遍创建的对象,您可能希望长度大于(或远大于) 10 ,我只是使用 10 作为一个简单的例子。


用法

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

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

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

 INSERT INTO collections (name) VALUES ('One');
INSERT INTO collections (name) VALUES ('Two');
INSERT INTO collections (name) VALUES ('Three');
SELECT * FROM collections;

它将自动生成 id 值:

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


带前缀的用法

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

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

INSERT INTO collections (name) VALUES ('One');
INSERT INTO collections (name) VALUES ('Two');
INSERT INTO collections (name) VALUES ('Three');
SELECT * FROM collections;

      id       |  name  | ...
---------------+--------+-----
col_wABNZRD5Zk | ian    |
col_ISzGcTVj8f | victor |

原文由 Ian Storm Taylor 发布,翻译遵循 CC BY-SA 3.0 许可协议

我正在寻找能够为我提供“短代码”(类似于 Youtube 用于视频 ID 的内容)的东西,它们尽可能短,同时仍然只包含字母数字字符。

这是一个与您最初提出的问题根本不同的问题。那么你在这里想要的是在表格上放置一个 serial 类型,并使用 hashids.org 代码用于 PostgreSQL

  • 这将返回 1:1 和唯一编号(序列号)
  • 永远不会重复或有碰撞的机会。
  • 还有base62 [a-zA-Z0-9]

代码看起来像这样,

 SELECT id, hash_encode(foo.id)
FROM foo; -- Result: jNl for 1001

SELECT hash_decode('jNl') -- returns 1001

该模块还支持盐。

原文由 Evan Carroll 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进