【问题标题】:Should I temporarily disable foreign key constraints? How?我应该暂时禁用外键约束吗?如何?
【发布时间】:2013-02-07 00:53:40
【问题描述】:

我有两张桌子:

person:
    id serial primary key,
    name varchar(64) not null

task:
    tenant_id   integer not null references person (id) on delete cascade,
    customer_id integer not null references person (id) on delete restrict

(他们有比这更多的列,但其余的与问题无关。)

问题是,当它的租户person 被删除时,我想级联删除一个task。但是当租户和客户是同一个人时,customer_id外键约束会限制删除。

我的问题分为两部分:

  1. 暂时禁用第二个外键是我唯一的选择吗?
  2. 如果是这样,那么我该如何在 PostgreSQL 中做到这一点?

【问题讨论】:

  • 表人是自引用的?还是我误会了?
  • @Frederic 是的,我忘了...我已经相应地更新了问题
  • @clapas,当 id =tenant_id 时出现问题?或与应在级联上删除的任何其他寄存器?抱歉,我真的不确定 Postgres,但是 sql-server 女士遇到了类似的问题,当自引用时,sql-server 不允许设置删除级联......我做了一个 cte 来获取所有的 regs在级联上删除并在同一删除语句上全部删除..这样FK没有限制原因确实注册将被一起删除......
  • 请说明person.tenant_id的作用。似乎与问题无关?另外,您谈到了问题的第一部分和第二部分,我无法确定。我看到一个问题。并声明正在使用的 Postgres 的版本。
  • 如果要级联删除,为什么customer_id外键约束上有on delete restrict?啊,我明白了,没关系。

标签: sql database postgresql foreign-keys


【解决方案1】:

实际上你创建了一个具有矛盾规则的竞态条件

我的第一个冲动是检查DEFERRED 约束是否有帮助。但它没有任何区别是有道理的。

我发现在CREATE TABLE 脚本中首先出现的 FK 约束是这场竞赛的获胜者。如果ON DELETE CASCADE先出现,则级联删除,如果ON DELETE RESTRICT先出现,则中止操作。

考虑SQL Fiddle上的演示。

这似乎与目录表pg_constraint 中较小的oid 相关:

SELECT oid, * FROM pg_constraint WHERE conrelid = 'task'::regclass

但是您的反馈表明,这不是原因。也许pg_attribute.attnum 决定了比赛。无论哪种方式,只要它没有记录在案的行为,您就不能依靠它在下一个主要版本中保持这种状态。可能值得在 pgsql-general@postgresql.org 上发布问题。

与所有这些无关,您需要考虑其他行:即使 CASCADE 将通过 task 中的一行同时具有 tenant_idcustomer_id 指向 person,它仍然会如果任何行只有 customer_id 引用 person,则受到限制。
另一个SQL Fiddle 演示了这个案例。

如何禁用约束?

最好的办法是放弃并重新创建它。在事务中执行所有操作,以确保不会破坏参照完整性。

BEGIN;

ALTER TABLE task DROP CONSTRAINT task_customer_id_fkey;

DELETE FROM person WHERE id = 3;

ALTER TABLE task ADD CONSTRAINT task_customer_id_fkey
FOREIGN KEY (customer_id) REFERENCES person (id) ON DELETE RESTRICT;

COMMIT;

这会独占锁定表,不适合在多用户环境中日常使用。

我怎么知道约束的名称?如上所示,我从pg_constraint 获取它。使用显式约束名称开头可能更容易:

CREATE TEMP TABLE task (
    customer_id integer NOT NULL
   ,tenant_id integer NOT NULL REFERENCES person (id) ON DELETE CASCADE
   ,CONSTRAINT task_customer_id_fkey FOREIGN KEY (customer_id)
    REFERENCES person (id) ON DELETE RESTRICT
);

还有

ALTER TABLE task DISABLE trigger ALL;

More in the manual here。但这会禁用 all 触发器。我试图只禁用系统创建的触发器来实现单个 FK 约束,但运气不好。

其他替代方法是使用triggersrules 实施您的制度。这样可以正常工作,但执行起来不像外键那样严格。

【讨论】:

  • 我已经尝试过类似的方法,但由于某种原因,它不起作用。我已经删除了tenant_id 外键并在之后重新创建了它,所以它的 oid 更大(我可以通过您指出的pg_constraint 中的选择来判断)。我从你的小提琴中复制了代码并且它在本地工作,所以这可以成为实现我的目标的一种方式,但我认为它工作的原因可能不是 oid 更大。我必须尝试DEFERRED 的东西。我不明白你答案的最后一部分:tenant_idcustomer_id 都有 not null 约束。
  • 啊!现在我明白你的意思了!第二个sqlfiddle中的场景在我的系统中没有发生,因为不同的租户不能共享一个共同的客户,即租户定义了一个范围。我不好不提。正如您所说,我想最好不要依赖未记录的行为,所以我将暂时禁用约束或使用触发器或规则实施解决方案,我还没有决定。无论如何,非常感谢您的帮助。
  • 顺便说一句,我在 Postgres 手册的 create table 部分中读到“不能延迟除 NO ACTION 检查之外的引用操作,即使约束被声明为可延迟。”
  • @clapas:我知道这一点并使用NO ACTION 进行了测试,这本来可以很好地替代RESTRICT,但正如我的回答中所记录的那样无济于事。
  • 你为什么要删除你的cmets?现在它是一个毫无意义的独白。
猜你喜欢
  • 2012-07-23
  • 1970-01-01
  • 2017-01-14
  • 2018-05-29
  • 1970-01-01
  • 2013-03-08
相关资源
最近更新 更多