PostgresQL 中超出堆栈深度限制(删除触发器后)

Posted

技术标签:

【中文标题】PostgresQL 中超出堆栈深度限制(删除触发器后)【英文标题】:Stack Depth Limit exceeded in PostgresQL (After Delete Trigger) 【发布时间】:2014-08-23 18:33:06 【问题描述】:
CREATE TABLE parent (
   parent_id VARCHAR(255) PRIMARY KEY
);

CREATE TABLE child (
   parent_id VARCHAR(255) REFERENCES parent ON DELETE CASCADE,
   child_id VARCHAR(255) PRIMARY KEY
);

CREATE OR REPLACE FUNCTION delete_parent()
RETURNS TRIGGER AS $$
BEGIN
    DELETE FROM parent WHERE parent_id = OLD.parent_id;
    RETURN NULL;
END; $$ LANGUAGE 'plpgsql';

CREATE TRIGGER delete_parent AFTER DELETE
ON child 
FOR EACH ROW
EXECUTE PROCEDURE delete_parent();

错误:

stack depth limit exceeded 提示:'在确保平台的堆栈深度限制足够之后,增加配置参数“max_stack_depth”(当前为 6144kB)。'

背景:

一个parent可以有多个children 该架构设计为,如果parent 被删除,它的所有子记录也会被删除。 如果删除了child,触发器将删除parent,然后级联删除与该parent相关的所有其他children

这已经工作了几个月,今天突然我们开始收到这个错误。

我找不到可能存在无限递归的情况,我正在考虑将堆栈深度限制加倍以查看会发生什么。

注意:实际的架构比这更复杂,并且有更多的相关表具有 CASCADE 删除约束。但这是唯一的触发因素。

更新:所以我将 max_stack_depth 限制加倍,现在可以了。我认为这不是一个好的解决方案,我仍然不确定如何防止这种情况在未来发生。

【问题讨论】:

通过 child->parent FK 的级联删除将导致如果删除父级,则所有子级都将被删除。你的触发器正好相反。所以一旦一个孩子被删除,它的父母和它的兄弟姐妹就会被删除。这是你的意图吗? 没错。如果孩子被删除,请删除父母及其所有 silbings 顺便说一句:你为​​什么使用 varchar(255) 字段作为 PK 和 FK ? 它实际上不是 VARCHAR(255)。服务器现在着火了,所以我只是快速编写了一个人为的示例,而不是复制真实的模式。它是一个 UUID。 【参考方案1】:

到目前为止,你就是这样:

    删除 child1。 触发删除父项。 删除 DELETE CASCADEn child1 的兄弟姐妹。 调用相同的触发器 n 次。 没有兄弟姐妹了。

没有无限循环,但仍然 n 次调用触发器。这可以解释为什么超出了堆栈深度限制,但您可以通过增加限制来修复它。更大的n 可能会再次发生同样的情况。

或者,将您的触发器替换为:

CREATE OR REPLACE FUNCTION delete_family()
  RETURNS TRIGGER AS
$func$
BEGIN
    DELETE FROM child  WHERE parent_id = OLD.parent_id;
    DELETE FROM parent WHERE parent_id = OLD.parent_id;  -- done after 1st call
    RETURN NULL;
END
$func$ LANGUAGE plpgsql;  -- don't quote the language name!

CREATE TRIGGER delete_family
AFTER DELETE ON child 
FOR EACH ROW EXECUTE PROCEDURE delete_family();

并将 FK 约束替换为 没有 ON DELETE CASCADE 的版本。代码示例:

ALTER TABLE to add ON DELETE CASCADE statement

现在,对于DELETE 全家人,你不能像以前那样删除父级(现在被 FK 禁止)。取而代之的是 DELETE 任何孩子。

也应该更快。

【讨论】:

嗨@erwin 感谢您的解决方案。似乎唯一改变的是添加了DELETE FROM child WHERE parent_id = OLD.parent_id; 为什么会减少调用次数? 此外,当父级被删除时,它会级联到子级,并且子级删除触发尝试再次删除父级(此时不再存在) @samol:抱歉,我忘了添加基本细节:从 FK 约束中删除 ON DELETE CASCADE 但我需要 on delete 级联触发器,因为有时我删除父级,我需要删除级联 @samol:你可以放弃任何孩子来代替同样的效果。另外,我不完全确定您的问题不是由您提到的其他触发器/约束引起的。

以上是关于PostgresQL 中超出堆栈深度限制(删除触发器后)的主要内容,如果未能解决你的问题,请参考以下文章

触发触发器时超出堆栈深度限制

进度警告:在过程中超出 -s 堆栈。那是啥意思?

RecursionError:比较中超出了最大递归深度'

Python - RecursionError:比较错误中超出了最大递归深度[重复]

在地图功能中超出最大更新深度[重复]

非递归过程中超出递归限制