【问题标题】:Postgres function create unique string and insert into tablePostgres 函数创建唯一的字符串并插入到表中
【发布时间】:2020-09-24 17:33:51
【问题描述】:

我在 Postgres 中有一个表,其中包含一个用户 ID 列和一个外部 ID,这些列将用于对外部服务的 API 调用。我必须在我这边创建外部 id,验证它是唯一的,然后在调用外部 API 之前将其存储在 PG 中。 这篇文章接近我想要的: How can I generate a unique string per record in a table in Postgres? 但是,如果两个并发调用生成相同的 id,则可能会发生冲突。我想做的是有一个生成随机字符串的循环,然后尝试将带有用户 ID 的字符串插入表中。如果随机字符串已经存在(列上有唯一约束),它应该会失败。如果失败,它应该生成另一个 id 并尝试插入它(一旦我得到工作代码,我将添加一个计数器以防止敲击 db)。

您将如何编写该循环?如果 INSERT 返回错误(约束检查),则循环应继续,否则再次循环。我检查了 Postgres 文档,似乎找不到(或丢失)检查查询错误代码/状态的方法。

更新

我想出了一个可能的解决方案,但需要充实它。以下是pidgeon-sql,只是我在思考这个问题:

success = true;
LOOP
-- create random string function
BEGIN
  insert string
EXCEPTION
  success = false;
EXIT WHEN success;
END;

【问题讨论】:

  • UUID 扩展是否有效?
  • 很遗憾外部 API 不使用 UUID
  • 是的,但是:select uuid_generate_v1mc()::varchar; 3c464166-fe95-11ea-87f2-0fd30065b715 select pg_typeof(uuid_generate_v1mc()::varchar); character varying
  • @JEPrice:然后将 uuid 转换为字符串
  • 它太长了。不知道为什么外部 api 开发人员会这样,但它最多 10 个字符:/

标签: postgresql function loops unique-constraint


【解决方案1】:

如果不需要外部ID的随机性,则

CREATE SEQUENCE base_seq;
ALTER TABLE thetable
    ALTER COLUMN ext_id SET DEFAULT LPAD(nextval('base_seq')::text, 64, '0');

将在 ext_id 列中提供强唯一(数据库范围)字符串

但是如果你唯一的选择是try-in-a-loop,那么plpgsql函数中的循环会是这样的:

LOOP
  new_try_ext_id := some randomization magic here...
  INSERT INTO thetable(userid,ext_id)
    VALUES (someid, new_try_ext_id)
  ON CONFLICT DO NOTHING;
  GET DIAGNOSTICS some_integer_var = ROW_COUNT;
  EXIT WHEN some_integer_var > 0;
END LOOP;

【讨论】:

  • 这将在面向外部的应用程序中使用,因此我不想使用序列
  • 嗯,这看起来比我上面的 pidgeon-sql 更好。我需要花一些时间用随机字符串生成器将其包装在一个函数中,添加一些其他内容,这可能就是我需要的...
【解决方案2】:

修订: 您对使用序列的安全担忧可能有一定的道理,尽管我不记得即使在安全审计中也会出现这种情况。但是,如果这是业务需求,那么您必须接受它。在我看来,您需要处理多个表的键冲突,因此广义函数广义生成似乎适合每个表的特定插入函数。您需要为每个表编写插入函数,并且不能只使用插入语句,您必须使用函数(如果您使用的是 Postgres V12 或更高版本,则必须使用过程)。您还必须将每一列作为参数传递给插入函数。以下基本上是“充实”您的伪代码。

create or replace function generate_random_id
                    ( lower_value_in bigint default 1  
                    , upper_value_in bigint default 10000000000)
                                                     
   returns bigint
  language sql
  volatile strict 
as $$
    select floor(random()*(upper_value_in-lower_value_in+1)+1)::bigint ;
$$; 
 
create or replace function insert_atable(col_x_in atable.colx%type)
   returns void 
  language plpgsql 
as $$
declare
    l_invalid_id boolean := true;
begin 
    while l_invalid_id
    loop
       begin
           insert into atable( id, colx)
             values ( generate_random_id(),col_x_in); 
           l_invalid_id := false;
       exception 
          when unique_violation then null;         
       end;
    end loop;
end;
$$;   

修改demo

当然你可以放弃这个想法,否则 2 个 id 实际上是相同的。

原文: 所以面向外部的 id 必须是唯一的,但为什么是随机的。然后从序列生成 id,将该序列最大值限制为 9999999999。然后将生成的序列转换为文本并存储该结果。这样,内部和外部 id 都是唯一的,但具有相同的值(至少在外部转换类型为 id 时)。更好的是,如果您有更高版本的 Postgres 12,您可以将外部 id 定义为 id 上的生成列,从而保证它们始终相同。表定义变成这样:

create table atable
             ( id integer  generated always as identity (maxvalue 999999999)
             , ext_id text generated always as  (id::text) stored
             , colx text 
             ) ;

demo。注意:Demo 将 id 定义为“默认生成”。这仅用于演示目的。

【讨论】:

  • 随机防范钓鱼。如果 id 是连续的,那么很容易猜到一个 id,而学习一个 id 会打开一系列 id 进行攻击。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-16
相关资源
最近更新 更多