【问题标题】:How can I generate a unique string per record in a table in Postgres?如何在 Postgres 的表中为每条记录生成唯一的字符串?
【发布时间】:2013-11-01 01:59:21
【问题描述】:

假设我有一个类似帖子的表,其中包含 id、body、created_at 等典型列。我想在创建每个帖子时生成一个唯一的字符串,用于类似 url 缩短器之类的东西。所以可能是一个 10 个字符的字母数字字符串。它需要在表中是唯一的,就像主键一样。

理想情况下,Postgres 应该有办法处理这两个问题:

  1. 生成字符串
  2. 确保其唯一性

它们必须齐头并进,因为我的目标是不必担心我的应用程序中的任何唯一性强制代码。

【问题讨论】:

标签: postgresql


【解决方案1】:

所有现有答案都是错误的,因为它们基于SELECT,同时为每个表记录生成唯一索引。让我们假设在插入时我们需要每条记录的唯一代码:想象两个并发 INSERT 奇迹般地同时发生(这比您想象的经常发生),因为在 SELECT 时刻该代码不存在,因此生成了相同的代码在表中。一个实例将 INSERT,而另一个实例将失败。

首先让我们创建带有代码字段的表并添加唯一索引

CREATE TABLE my_table
(
    code TEXT NOT NULL
);

CREATE UNIQUE INDEX ON my_table (lower(code));

然后我们应该有函数或过程(你也可以使用内部代码作为触发器)我们 1。生成新代码,2. 尝试使用新代码插入新记录,3. 如果插入失败,请从步骤 1 重试

CREATE OR REPLACE PROCEDURE my_table_insert()
AS $$
DECLARE
    new_code TEXT;
BEGIN

    LOOP
        new_code := LOWER(SUBSTRING(MD5(''||NOW()::TEXT||RANDOM()::TEXT) FOR 8));
        BEGIN
            INSERT INTO my_table (code) VALUES (new_code);
            EXIT;
        EXCEPTION WHEN unique_violation THEN

        END;
    END LOOP;

END;
$$ LANGUAGE PLPGSQL;

这是保证没有错误的解决方案,不像这个线程上的其他解决方案

【讨论】:

  • 我想如果你把 FOR 做得足够小,你会想在一定的计数后退出循环,这样它就不会永远持续下去。我同意这是最好的答案,但其他人是错误的
  • 有理由用lower() 创建UNIQUE INDEX 吗?
  • 我们可以将它与触发器一起使用吗?我试过但出错了。如果是,您能否将其包含在答案中或帮助我?我卡住了
【解决方案2】:

可能使用序列来保证唯一性的最简单方法 (所以在 seq 之后添加一个固定的 x 位随机数):

CREATE SEQUENCE test_seq;
CREATE TABLE test_table (
  id bigint NOT NULL DEFAULT (nextval('test_seq')::text || (LPAD(floor(random()*100000000)::text, 8, '0')))::bigint,
  txt TEXT
);
insert into test_table (txt) values ('1');
insert into test_table (txt) values ('2');
select id, txt from test_table;

但是这会浪费大量的记录。 (注意:最大 bigInt 为 9223372036854775807 如果最后使用 8 位随机数,则只能有 922337203 条记录。您可能不需要 8 位。还要检查您的编程环境的最大数!)

或者,您可以使用 varchar 作为 id,甚至使用 to_hex() 转换上述数字或更改为 base36,如下所示(但对于 base36,请尽量不要将其暴露给客户,以避免出现一些有趣的字符串! ):

PostgreSQL: Is there a function that will convert a base-10 int into a base-36 string?

【讨论】:

  • “浪费大量记录”——什么意思?
  • 添加了一些解释
【解决方案3】:

查看 Bruce 的博客。这会让你分道扬镳。您必须确保它不存在。也许将主键连接到它?

Generating Random Data Via Sql

"曾经需要生成随机数据吗?您可以在客户端应用程序和服务器端函数中轻松完成,但可以在 sql 中生成随机数据。以下查询生成 5 行 40 个字符长度的小写字母字符串:"

  SELECT
(
  SELECT string_agg(x, '')
  FROM (
    SELECT chr(ascii('a') + floor(random() * 26)::integer)
    FROM generate_series(1, 40 + b * 0)
  ) AS y(x)
)
FROM generate_series(1,5) as a(b);

【讨论】:

  • 唯一性强制是我更感兴趣的——对我来说生成字符串很容易,但从应用程序端对唯一性强制进行编程很痛苦。我已经更新了我的问题,以便更清楚地解释这一点。
  • 从逻辑上讲有两种方法。检查所有现有记录。或者为您的字符串引入某种有保证的唯一性。我的想法是将主键连接到随机字符串。
【解决方案4】:

使用 Feistel 网络。这种技术可以有效地在恒定时间内生成独特的随机字符串,而不会发生任何冲突。

对于包含大约 20 亿个可能的 6 个字母的字符串 (2^31) 的版本,请参阅 this answer

有关基于bigint9223372036854775808 不同的可能值)的 63 位版本,请参阅this other answer

您可以按照第一个答案中的说明更改 round 函数,以引入一个秘密元素来拥有自己的一系列字符串(不可猜测)。

【讨论】:

    【解决方案5】:

    在数据中使用主键。如果您确实需要字母数字唯一字符串,则可以使用 base-36 编码。在 PostgreSQL 中你可以使用this 函数。

    例子:

    select base36_encode(generate_series(1000000000,1000000010));
    
    GJDGXS
    GJDGXT
    GJDGXU
    GJDGXV
    GJDGXW
    GJDGXX
    GJDGXY
    GJDGXZ
    GJDGY0
    GJDGY1
    GJDGY2
    

    【讨论】:

      【解决方案6】:

      我不认为以下是有效的,但这是我们过去做这类事情的方式。

      CREATE FUNCTION make_uid() RETURNS text AS $$
      DECLARE
          new_uid text;
          done bool;
      BEGIN
          done := false;
          WHILE NOT done LOOP
              new_uid := md5(''||now()::text||random()::text);
              done := NOT exists(SELECT 1 FROM my_table WHERE uid=new_uid);
          END LOOP;
          RETURN new_uid;
      END;
      $$ LANGUAGE PLPGSQL VOLATILE;
      

      make_uid() 可用作my_table 中列的默认值。类似的东西:

      ALTER TABLE my_table ADD COLUMN uid text NOT NULL DEFAULT make_uid();
      

      md5(''||now()::text||random()::text)可以根据口味调整。您可以考虑使用encode(...,'base64'),除非 base-64 中使用的某些字符不是 URL 友好的。

      【讨论】:

      • 这会导致竞争状况吗? (我对 pg 函数的运行时环境了解得不够多,无法思考……)
      • 这是不安全的,因为 2 个并发插入可能会以相同的随机字符串结束,一个会因错误而退出,请在唯一字段上使用插入而不是选择/不存在
      • 一个更好的主意是使用当前时间的数据中的某些内容来生成哈希 md5(now() | email | password) 将确保每个用户都有一个唯一的 id
      猜你喜欢
      • 2017-07-27
      • 1970-01-01
      • 1970-01-01
      • 2015-07-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-02
      相关资源
      最近更新 更多