如何自动增加作用于另一个表的非主ID列?

时间:2016-08-17 22:12:36

标签: sql postgresql

让我们想象一下,我们正在构建GitHub,我们有两个表:reposissues。每个GitHub仓库都有一系列问题,因此issues表的外键为repo_id

现在,当您浏览GitHub回购问题时,您不希望被曝光到内部id。相反,您需要像number这样的东西,它仅从1..n递增到该存储库。您希望新回购中的第一个问题编号为1,而不是GitHub上的问题的下一个id

当然,您需要一种增量方式,并且您希望确保number在作用于回购时的唯一性。因此,您特别希望避免任何可以生成两次相同数字的竞争条件。

最直接的处理方法是什么?触发器?还有其他什么呢?

我正在使用PostgreSQL,但更喜欢可能的vanilla SQL方法,例如:触发。如果有一个更简单的Postgres方法,那么这也是有用的。

任何展示您的方法的代码都会非常有用。谢谢!

4 个答案:

答案 0 :(得分:1)

我认为如果没有竞争条件的可能性,我有办法做到这一点。这应该最小化竞争条件,但不能消除它们。在特定的数据库体系结构中可能有更好的方法。假设" REPOSITORY_ID"由您的应用程序代码提供:

insert into issues (repo_id,line_id) values (
    REPOSITORY_ID,
    coalesce((select max(line_id)+1 from issues where repo_id=REPOSITORY_ID),0)
);

这会拉出当前最高的line_id,并在插入时将其递增。如果没有记录,则默认为0.如果两个插入在同一时间点击,则竞争的可能性很小,但似乎不太可能。如果强制使用唯一性,则可以检查插入时是否出错,并在失败时重试。

答案 1 :(得分:1)

假设您要向某个issue添加新的repo,您可以执行以下操作:

  1. 开始新的显式交易;
  2. 使用repo选择要修改的SELECT ... FOR UPDATE。这将对其进行行级锁定,并阻止其他想要为issue添加新repo的事务同时进行;
  3. 以某种方式获取repo的新问题编号(例如,您可以在latest_issue中添加issue列,就像在其中一个答案中一样,或者您可以执行查询找到它);
  4. 使用正确的问题编号插入新的issue;
  5. 终止交易:这将释放锁定并允许其他想要在同一repo上工作的交易继续。
  6. 因此,您可以通过这种方式定义存储过程,并在每次要插入新的issue时调用它。假设没有太多并发事务试图为同一个存储库插入新问题,这将阻止竞争条件并仍然以合理的效率运行。

答案 2 :(得分:0)

我会在latest_issue中保留repos列,并将其初始化为0 在issues我要在nr上创建一个UNIQUE个约束(repo_id, nr),其中repo_idissues中的外键列}。

每当创建问题时,latest_issue中的repos都会增加。然后,此号码将用作nr的{​​{1}}。

答案 3 :(得分:0)

以下是我将如何处理它:

create table repo(
  id serial primary key
);

create table issue(
  id integer not null,
  id_repo integer not null references repo(id),
  primary key (id, id_repo)
);

create function create_issue_seq() returns trigger as $$
  begin
    execute format('create sequence issue_%s_seq', new.id);
    return new;
  end
$$ language plpgsql;

create trigger create_issue_seq after insert on repo
  for each row execute procedure create_issue_seq();

create function assign_issue_id() returns trigger as $$
  begin
    new.id = nextval(format('issue_%s_seq', new.id_repo));
    return new;
  end
$$ language plpgsql;

create trigger assign_issue_id before insert on issue
  for each row execute procedure assign_issue_id();

一个触发器在创建repo后创建了问题ID序列(专用于给定的repo),第二个触发器利用现有的专用序列在插入问题之前正确填充问题ID。

优点:

  • 无竞赛,因为它实际上是使用序列
  • 无需独占锁定

缺点:

  • 它会创建大量序列(尽管使用1M repos进行的快速测试显示它不会导致任何严重的性能损失)

注意:

  • 可能明智地实现一些在repo上删除后删除序列的触发器。
  • 我假设不可变的repo id(我坚信这是正确的方式:拥有不可变的PK)