【问题标题】:postgres - trigger before transaction commitpostgres - 在事务提交之前触发
【发布时间】:2011-10-08 11:23:47
【问题描述】:

我想知道是否可以在事务即将提交之前间接执行触发器?在此触发器中,我将进行一致性检查并在需要时回滚事务。

例如,我有三个表:

users (id, name)
groups (id, name)
user_in_group (user_id, group_id)

我想创建一个触发器来验证用户是否始终是组的一部分。不允许孤儿用户。每次插入用户时,此触发器都会验证相应的插入 user_in_group 是否也发生了。如果没有,事务将不会提交。

这不能使用简单的基于行或基于语句的触发器来完成,因为上述场景需要两个单独的语句。

反过来,当从 user_in_group 中删除时,可以通过基于行的触发器轻松完成。

【问题讨论】:

    标签: postgresql transactions triggers


    【解决方案1】:

    您是否使用DEFERRABLE (INITIALLY DEFERRED) 选项查看了CREATE CONSTRAINT TRIGGER

    【讨论】:

    • +1,同样的deferrable initially deferred 用于外键、唯一性和排除约束。
    • 谢谢。我没有意识到可以在约束触发器上设置 DEFERRABLE。我认为这最能解决我的问题。
    • 如果他们有这个工作,为什么他们不允许 CHECK() 约束被推迟?嗯,很好的答案。
    【解决方案2】:

    在数据库中建立约束真的不是正确的方法吗?这似乎正是约束的用途。添加一个外键约束和一个非空,看起来你应该做生意了。

    现在针对对称约束进行了修订:

    drop table foousers cascade;
    drop table foogroups cascade;
    drop table foousergrps cascade;
    create table foousers (id int primary key, name text);
    create table foogroups  (id int primary key, name text);
    create table foousergrps (user_id int unique references foousers not null, group_id int unique references foogroups not null);
    alter table foogroups add foreign key (id) references foousergrps (group_id) deferrable initially deferred;
    alter table foousers add foreign key (id) references foousergrps (user_id) deferrable initially deferred;
    begin;
    insert into foousers values (0, 'root');
    insert into foousers values (1, 'daemon');
    insert into foogroups values (0, 'wheel');
    insert into foogroups values (1, 'daemon');
    insert into foousergrps values (0,0);
    insert into foousergrps values (1,1);
    commit;
    

    禁止:

    insert into foousers values (2, 'bad');
    insert into foousergrps values (2,2);
    

    (non-deferable, boo) 校验函数示例:

    create table foousergrps (user_id int unique references foousers not null, group_id int not null);
    create function fooorphangroupcheck(int) returns boolean as $$
    declare
      gid alias for $1;
    begin
      perform 1 from foousergrps where group_id = gid limit 1;
      if NOT FOUND then return false;
      end if;
      return true;
    end;
    $$
    LANGUAGE 'plpgsql';
    alter table foogroups add check (fooorphangroupcheck(id));
    

    【讨论】:

    • 请再次阅读说明。这是关于插入用户而不将其插入到 user_in_group 中。与从 user_in_group 中删除或使用无效引用无关。
    • @Appelsien S:请再次阅读我的回答。我在添加示例后立即指出,我的约束以错误的方式工作,现在已修复它,因此不允许孤儿用户(同时仍然禁止错误的 user_in_group 条目 - 不禁止这些条目会更容易创建表,因为没有将需要更改表)。
    • 在您修改后的版本中,foousergrps 中的 user_id 和 group_id 是唯一的。这当然是不希望的:foousergrps 是一个 N 对 N 关系。你把它变成了一对一的关系。
    • @Appelsein:我必须将其归结为未表达的明确要求。它回答了书面的问题。 postgresql 需要唯一性很烦人,但这样做并不致命。您可以创建两个物化视图表(意思是由 foousergrps 上的触发器更新)foousergrpsuniqueusers 和 foousergrpsuniquegroups,它们包含用户和组的唯一列表,并让外键引用这些影子表。带有函数的检查约束可能有效,但不可延迟(意味着您不能拥有反向外键,必须先添加用户组)
    【解决方案3】:

    查看文档,似乎没有这样的触发器选项......所以实现“无孤儿用户”规则的一种方法是不允许直接插入 usersuser_in_group 表。而是创建一个带有update rule 的视图(它结合了这些表,即user_id, user_name, group_id),它将数据插入到正确的表中。

    或者只允许通过存储过程插入新用户,该存储过程将所有需要的数据作为输入,因此不允许没有组的用户。

    顺便说一句,为什么你有单独的用户和组关系表?为什么不将 group_id 字段添加到带有 FK / NOT NULL 约束的 users 表中?

    【讨论】:

    • 使用更新规则查看是个好主意,谢谢!关于你的其他建议,这怎么可能?与在过程之外执行查询有什么区别?
    • SP 的想法是将两个插入合并到一个命令中 - 如果它在任何时候失败(即您已经插入到 users 表中但没有插入到 user_group 中)所有操作都将滚动回来,你不会得到孤立的记录。
    • 啊好吧。但是只使用SP,我不能阻止用户直接访问表吗?
    • 好吧,您不能授予用户对这些表的 INSERT / UPDATE 权限。或者,再次使用更新规则丢弃任何直接更新,只留下您的特殊视图或 SP 作为插入新用户的手段。
    • 您可以定义函数(存储过程)在调用者的权限或所有者的权限下运行。这是 PostgreSQL 的 CREATE FUNCTION 语法的一部分。
    【解决方案4】:

    来自the docs。 . .

    触发器可以定义为在任何 INSERT 之前或之后执行, UPDATE 或 DELETE 操作,每个修改的行一次,或者每个修改的行一次 SQL 语句。

    【讨论】:

    • 我知道。使用这些类型的触发器无法完成问题中描述的场景。这就是为什么我一直在寻找一种间接的方式。你碰巧认识一个吗?
    【解决方案5】:

    您可以使用 sql WITH 运算符,如下所示:

    WITH insert_user AS (
      INSERT INTO users(name) VALUES ('bla-bla-user') RETURNING id
    )
    INSERT INTO user_in_group(user_id, group_id) 
      SELECT id, 999 FROM insert_user UNION
      SELECT id, 888 FROM insert_user;
    
    SELECT groups (id, name) user_in_group (user_id, group_id)
    

    【讨论】:

      猜你喜欢
      • 2022-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-10
      • 2014-11-01
      • 1970-01-01
      • 2023-04-02
      相关资源
      最近更新 更多