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

Ian*_*lor 9 sql database random postgresql

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

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

bTFTxFDPPq
tcgHAdW3BD
IIo11r9J0D
FUW5I8iCiS

uXolWvg49Co5EfCo
LOscuAZu37yV84Sa
YyrbwLTRDb01TmyE
HoQk3a6atGWRMCSA

HwHSZgGRStDMwnNXHk3FmLDEbWAHE1Q9
qgpDcrNSMg87ngwcXTaZ9iImoUmXhSAv
RVZjqdKvtoafLi1O5HlvlpJoKzGeKJYS
3Rls4DjWxJaLfIJyXIEpcjWuh51aHHtK
Run Code Online (Sandbox Code Playgroud)

(就像Stripe使用ID一样.)

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

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

generate_uid(size integer) returns text
Run Code Online (Sandbox Code Playgroud)

哪里size可以根据您自己的权衡来定制,以降低碰撞的可能性与减少可用性的字符串大小.

从我所知道的,它必须使用gen_random_bytes()而不是random()真正的随机性,以减少他们被猜到的机会.

谢谢!


我知道有gen_random_uuid()UUID,但在这种情况下我不想使用它们.我正在寻找能给我ID类似于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('');
}
Run Code Online (Sandbox Code Playgroud)

Ian*_*lor 17

想通了,这里有一个函数可以做到这一点:

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;
Run Code Online (Sandbox Code Playgroud)

然后运行它只需执行以下操作:

generate_uid(10)
-- '3Rls4DjWxJ'
Run Code Online (Sandbox Code Playgroud)

警告

执行此操作时,您需要确保您创建的 ID 的长度足以避免随着您创建的对象数量的增加而发生冲突,这可能会因为生日悖论而违反直觉。因此,您可能希望长度大于(或远大于)10任何合理通常创建的对象,我只是用作10一个简单示例。


用法

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

CREATE TABLE collections (
  id TEXT PRIMARY KEY DEFAULT generate_uid(10),
  name TEXT NOT NULL,
  ...
);
Run Code Online (Sandbox Code Playgroud)

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

INSERT INTO collections (name) VALUES ('One');
INSERT INTO collections (name) VALUES ('Two');
INSERT INTO collections (name) VALUES ('Three');
SELECT * FROM collections;
Run Code Online (Sandbox Code Playgroud)

它将自动生成id值:

    id     |  name  | ...
-----------+--------+-----
owmCAx552Q | ian    |
ZIofD6l3X9 | victor |
Run Code Online (Sandbox Code Playgroud)

带前缀的用法

或者,您可能想在查看日志或调试器中的单个 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 |
Run Code Online (Sandbox Code Playgroud)


Eva*_*oll 5

评论,

  1. 中的26个字符 [a-z]
  2. 中的26个字符 [A-Z]
  3. 中的10个字符 [0-9]
  4. [a-zA-Z0-9](base62)中的62个字符
  5. 该功能substring(string [from int] [for int])看起来很有用。

所以看起来像这样。首先,我们证明可以采用随机范围并从中拉出。

SELECT substring(
  'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
  1, -- 1 is 'a', 62 is '9'
  1,
);
Run Code Online (Sandbox Code Playgroud)

现在我们需要介于1和之间的范围63

SELECT trunc(random()*62+1)::int+1
FROM generate_series(1,1e2) AS gs(x)
Run Code Online (Sandbox Code Playgroud)

这样就可以到达那里了。现在我们只需要加入两者即可。

SELECT substring(
  'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
  trunc(random()*62)::int+1
  1
)
FROM generate_series(1,1e2) AS gs(x);
Run Code Online (Sandbox Code Playgroud)

然后我们将其包装在ARRAY构造函数中(因为这很快)

SELECT ARRAY(
  SELECT substring(
    'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
    trunc(random()*62)::int+1,
    1
  )
  FROM generate_series(1,1e2) AS gs(x)
);
Run Code Online (Sandbox Code Playgroud)

并且,我们打电话array_to_string()来获取文本。

SELECT array_to_string(
  ARRAY(
      SELECT substring(
        'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
        trunc(random()*62)::int+1,
        1
      )
      FROM generate_series(1,1e2) AS gs(x)
  )
  , ''
);
Run Code Online (Sandbox Code Playgroud)

从这里我们甚至可以将其变成一个函数。

CREATE FUNCTION random_string(randomLength int)
RETURNS text AS $$
SELECT array_to_string(
  ARRAY(
      SELECT substring(
        'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
        trunc(random()*62)::int+1,
        1
      )
      FROM generate_series(1,randomLength) AS gs(x)
  )
  , ''
)
$$ LANGUAGE SQL
RETURNS NULL ON NULL INPUT
VOLATILE LEAKPROOF;
Run Code Online (Sandbox Code Playgroud)

然后

SELECT * FROM random_string(10);
Run Code Online (Sandbox Code Playgroud)


Eva*_*oll 5

我正在寻找能够为我提供尽可能短但仍仅包含字母数字字符的“短代码”(类似于 Youtube 用于视频 ID 的代码)的东西。

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

  • 这将返回 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
Run Code Online (Sandbox Code Playgroud)

该模块还支持盐。