Postgresql分区表删除触发器失败

时间:2012-10-29 09:53:24

标签: postgresql triggers partitioning

我基于另一个SE问题(PostgreSql上的哈希环)

CREATE TABLE sms.tablename
(
  id uuid,
  mdate date
)

和分区。

CREATE TABLE sms.tablename_partition_1 ( CHECK ( sms.hash(id) = '1' ) ) INHERITS (sms.tablename);
...
CREATE TABLE sms.tablename_partition_f ( CHECK ( sms.hash(id) = 'f' ) ) INHERITS (sms.tablename);

现在是问题所在。

当我添加此触发器时。

CREATE TRIGGER "delete_me"
  BEFORE DELETE
  ON sms.tablename
  FOR EACH ROW
  EXECUTE PROCEDURE sms.delete_me(E'\\x');

CREATE OR REPLACE FUNCTION sms.delete_me()
  RETURNS trigger AS
$BODY$

begin

    RAISE NOTICE 'HERE !!!';
    return OLD;

end;
$BODY$
  LANGUAGE plpgsql VOLATILE SECURITY DEFINER
  COST 100;

这个触发器永远不会运行...... 我看不到NOTICE消息。 现在,如果我将相同的触发器应用于另一个表(非分区),它可以正常工作,删除行并弹出通知消息。

  

更多信息:“i6486-pc-linux-gnu上的PostgreSQL 9.1.6,由。编译   gcc-4.4.real(Ubuntu 4.4.3-4ubuntu5.1)4.4.3,32位“

我只是想避免在表上使用存储过程并保持ORM清理。

已编辑:

1)表中没有规则或任何继承的表,相同的索引或PK的表。

2)运行以下命令。

DELETE FROM sms.tablename WHERE id = 'a5e52a04-282f-4cf4-8347-a43d68725e6b';

3)完整SQL显示此问题。

http://pastebin.com/mZBFEtaY

2 个答案:

答案 0 :(得分:2)

删除触发器在包含行而不是父行的子表上触发。您必须将触发器添加到每个子表。

DO                                                                
$$
DECLARE
  h text;
BEGIN
  FOR h IN SELECT to_hex(x) FROM generate_series(0,15) x LOOP
    EXECUTE format('CREATE TRIGGER "DeleteRedirector" BEFORE DELETE ON pproblem.%I FOR EACH ROW EXECUTE PROCEDURE pproblem.delete_me('''');',
                   'tablename_partition_'||h);
  END LOOP;
END;
$$;

演示:

regress=# begin;
BEGIN
regress=# DELETE FROM pproblem.tablename;
NOTICE:  HERE !!!
NOTICE:  HERE !!!
NOTICE:  HERE !!!
DELETE 3
regress=# rollback;
ROLLBACK

答案 1 :(得分:0)

这里发生的事情可能是您的触发器以意外的方式与另一个触发器或规则进行交互。

如果某个分区的较早触发器DELETE然后执行RETURN NULL以防止该操作被应用于其自身的基表,则稍后的触发器不会触发,因为该操作已被取消(并重定向,但后来的触发器不知道那个。)

视图出现了类似但更令人困惑的情况,其中DO INSTEAD表示查询被重写,因此它们从不首先引用基表。

如果您在分区表上使用触发器,则需要在每个分区和主表上创建它们,或者小心触发器的顺序。触发器以BEFORE触发器运行,然后按触发器名称的字母顺序触发AFTER触发器。

鉴于DDL:

create table demo (id integer);

CREATE OR REPLACE FUNCTION cancel_tg() RETURNS trigger AS $$
BEGIN
  RETURN NULL;
END;
$$ LANGUAGE plpgsql VOLATILE;

CREATE OR REPLACE FUNCTION notice_tg() RETURNS trigger AS $$
BEGIN
  RAISE NOTICE 'Trigger fired';
  -- This just makes it a no-op:
  IF tg_op = 'INSERT' OR tg_op = 'DELETE' THEN
    RETURN NEW;
  ELSE
    RETURN OLD;
  END IF;
END;
$$ LANGUAGE plpgsql VOLATILE;

通知触发器之前的取消触发器不产生任何消息:

regress=> CREATE TRIGGER aa_cancel 
          BEFORE INSERT OR UPDATE OR DELETE ON demo
          FOR EACH ROW EXECUTE PROCEDURE cancel_tg();
CREATE TRIGGER
regress=> CREATE TRIGGER bb_notice
          BEFORE INSERT OR UPDATE OR DELETE ON demo 
          FOR EACH ROW EXECUTE PROCEDURE notice_tg();
CREATE TRIGGER
regress=> insert into demo values (1);
INSERT 0 0

另一种方法是触发通知,因为aa_noticebb_cancel之前触发:

regress=> drop trigger bb_notice on demo;
DROP TRIGGER
regress=> drop trigger aa_cancel on demo;
DROP TRIGGER
regress=> CREATE TRIGGER aa_notice
          BEFORE INSERT OR UPDATE OR DELETE ON demo
          FOR EACH ROW EXECUTE PROCEDURE notice_tg();
CREATE TRIGGER
regress=> CREATE TRIGGER bb_cancel
          BEFORE INSERT OR UPDATE OR DELETE ON demo 
          FOR EACH ROW EXECUTE PROCEDURE cancel_tg();
CREATE TRIGGER
regress=> insert into demo values (1);
NOTICE:  Trigger fired
INSERT 0 0

这意味着AFTER触发器永远不会触发使用触发器分区的表,并且BEFORE触发器必须在分区触发器之前按字母顺序排列。另一种在PostgreSQL中进行分区的方法是一个丑陋的黑客......