PostgreSQL中触发器递归的处理 | 翻译

在这里插入图片描述
许多初学者在某个时候都会陷入触发器递归的陷阱。通常,解决方案是完全避免递归。但对于某些用例,您可能必须处理触发器递归。本文将告诉您有关该主题需要了解的内容。如果您曾经被错误消息“超出堆栈深度限制”所困扰,那么这里就是解决方案。

01 初学者的错误导致触发递归

触发器是自动更改数据的唯一好方法。约束是确保规则不被违反的“警察”,而触发器是让数据保持一致的工人。理解这一点的初学者可能(非常正确)希望使用触发器来设置updated_at下表中的列:

CREATE TABLE data (id bigintGENERATED ALWAYS AS IDENTITYPRIMARY KEY,value text NOT NULL,updated_at timestamp with time zoneDEFAULT current_timestampNOT NULL
);

插入行时将设置列默认值updated_at,但更新行时不会更改该值。为此,我们的初学者编写了一个触发器:

CREATE FUNCTION set_updated_at() RETURNS triggerLANGUAGE plpgsql AS
$$BEGINUPDATE dataSET updated_at = current_timestampWHERE data.id = NEW.id;RETURN NEW;
END;$$;CREATE TRIGGER set_updated_atAFTER UPDATE ON data FOR EACH ROWEXECUTE FUNCTION set_updated_at();

但这不会按预期发挥作用:

INSERT INTO data (value) VALUES ('initial') RETURNING id;id 
════1
(1 row)UPDATE data SET value = 'changed' WHERE id = 1;
ERROR:  stack depth limit exceeded
HINT:  Increase the configuration parameter "max_stack_depth" (currently 2048kB), after ensuring the platform's stack depth limit is adequate.
CONTEXT:  SQL statement "UPDATE dataSET updated_at = current_timestampWHERE data.id = NEW.id"
PL/pgSQL function set_updated_at() line 2 at SQL statement
SQL statement "UPDATE dataSET updated_at = current_timestampWHERE data.id = NEW.id"
PL/pgSQL function set_updated_at() line 2 at SQL statement
...

错误上下文的最后四行不断重复,并表明存在递归问题。

02BEFORE使用触发器避免触发器递归

触发器的问题在于,它更新了最初调用触发器的更新的同一张表。这会再次触发相同的触发器,依此类推,直到堆栈上的递归函数调用过多而超出限制。与大多数其他情况不同,PostgreSQL 的提示在这里毫无用处。由于递归是无限的,因此增加堆栈深度限制只会增加错误消息的时间和错误上下文的长度。

即使不会导致无限递归,上述触发器也不是理想的。由于 PostgreSQL 的多版本实现,每次更新都会产生一个“死元组”,VACUUM稍后必须清理。如果触发器对您刚刚更新的表行执行第二次更新,则会生成第二个死元组。这是低效的,您可能需要调整自动清理以应对额外的工作负载。

PostgreSQL 中避免第二次更新和无限递归的正确解决方案是在BEFORE将新行添加到表之前对其进行修改的触发器:

CREATE FUNCTION set_updated_at() RETURNS triggerLANGUAGE plpgsql AS
$$BEGINNEW.updated_at := current_timestamp;RETURN NEW;
END;$$;CREATE TRIGGER set_updated_atbefore UPDATE ON data FOR EACH ROWEXECUTE FUNCTION set_updated_at();

03一个更严重的触发器递归示例

上述初学者的错误很容易修复,而且在大多数情况下,只要稍加思考就可以轻松避免这种递归。但有时,有些触发器用例很难避免递归。想象一下工作场所和工人的公共卫生数据库:

CREATE TABLE address (id bigint PRIMARY KEY,street text,zip text NOT NULL,city text NOT NULL
);CREATE TABLE worker (id bigint PRIMARY KEY,name text NOT NULL,quarantined boolean NOT NULL,address_id bigint REFERENCES address
);INSERT INTO address VALUES(101, 'Römerstraße 19', '2752', 'Wöllersdorf'),(102, 'Heldenplatz', '1010', 'Wien');INSERT INTO worker VALUES(1, 'Laurenz Albe', FALSE, 101),(2, 'Hans-Jürgen Schönig', FALSE, 101),(3, 'Alexander Van der Bellen', FALSE, 102);

每个工人都有一个状态“ quarantined”(不,这篇文章不是在疫情期间写的)。

04使用容易出现无限递归的触发器来执行数据规则

想象一下,法律规定,如果一名工人被隔离,则在同一地址工作的所有人也将被隔离。最好使用触发器来实现这样的数据完整性规则。否则,在应用程序之外执行的数据修改可能会破坏数据的完整性。这样的触发器可能如下所示:

CREATE FUNCTION quarantine_coworkers() RETURNS triggerLANGUAGE plpgsql AS
$$BEGINIF NEW.quarantined IS TRUE THENUPDATE workerSET quarantined = TRUEWHERE worker.address_id = NEW.address_idAND worker.id <> NEW.id;END IF;RETURN NEW;
END;$$;CREATE TRIGGER quarantine_coworkersAFTER UPDATE ON worker FOR EACH ROWEXECUTE FUNCTION quarantine_coworkers();

这看起来基本上是正确的,但是只要一个地址上有更多工作线程,就会出现触发器递归。第一次触发器调用将更新同一地址的其他工作线程,这将再次调用触发器,第二次更新原始工作线程,依此类推,直到无穷:

UPDATE worker SET quarantined = TRUE WHERE id = 1;
ERROR:  stack depth limit exceeded
HINT:  Increase the configuration parameter "max_stack_depth" (currently 2048kB), after ensuring the platform's stack depth limit is adequate.
CONTEXT:  SQL statement "SELECT 1 FROM ONLY "laurenz"."address" x WHERE "id" OPERATOR(pg_catalog.=) $1 FOR KEY SHARE OF x"
SQL statement "UPDATE workerSET quarantined = TRUEWHERE worker.address_id = NEW.address_idAND worker.id <> NEW.id"
PL/pgSQL function quarantine_coworkers() line 3 at SQL statement
SQL statement "UPDATE workerSET quarantined = TRUEWHERE worker.address_id = NEW.address_idAND worker.id <> NEW.id"
PL/pgSQL function quarantine_coworkers() line 3 at SQL statement
...

05WHERE使用条件避免无限触发器递归

对于上述情况,您可以通过添加另一个WHERE避免第二次更新行的条件来修复无限递归:

CREATE OR REPLACE FUNCTION quarantine_coworkers() RETURNS triggerLANGUAGE plpgsql AS
$$BEGINIF NEW.quarantined IS TRUE THENUPDATE workerSET quarantined = TRUEWHERE worker.address_id = NEW.address_idAND worker.id <> NEW.idAND NOT worker.quarantined;END IF;RETURN NEW;
END;$$;

现在,如果我用 更新工作器id = 1,触发器将用 更新工作器id = 2。这将第二次调用触发器,但该地址的所有工作器都已被隔离,因此触发器不会更新任何行,并且递归停止。

06使用函数避免无限触发递归pg_trigger_depth()

在我们的示例中,使用条件来避免无限递归并不困难WHERE。但事情并不总是那么容易。还有另一种方法可以停止递归:函数pg_trigger_depth()。此函数用于触发函数并返回递归级别。我们可以使用它作为保护措施来在第一级之后停止递归:

CREATE OR REPLACE FUNCTION quarantine_coworkers() RETURNS triggerLANGUAGE plpgsql AS
$$BEGINIF NEW.quarantined IS TRUE AND pg_trigger_depth() < 2 THENUPDATE workerSET quarantined = TRUEWHERE worker.address_id = NEW.address_idAND worker.id <> NEW.idAND NOT worker.quarantined;END IF;RETURN NEW;
END;$$;

07使用触发WHEN子句来获得更好的性能

WHEN使用上述代码,触发器仍将被调用两次。第二次,触发器函数将返回而不执行任何操作,但我们仍需付出第二次函数调用的代价。中鲜为人知的子句CREATE TRIGGER可以使触发器调用有条件并避免这种开销:

DROP TRIGGER quarantine_coworkers ON worker;CREATE OR REPLACE FUNCTION quarantine_coworkers() RETURNS triggerLANGUAGE plpgsql AS
$$BEGINUPDATE workerSET quarantined = TRUEWHERE worker.address_id = NEW.address_id;AND worker.id <> NEW.idAND NOT worker.quarantined;RETURN NEW;
END;$$;CREATE TRIGGER quarantine_coworkers AFTER UPDATE ON worker FOR EACH ROWWHEN (NEW.quarantined AND pg_trigger_depth() < 2)EXECUTE FUNCTION quarantine_coworkers();

通过此定义,在触发函数被第二次调用之前,递归就会停止,这将显著提高性能。

结论

我们已经了解了如何通过完全避免递归来避免初学者容易犯的错误,即导致无限触发器递归。在无法避免触发器递归的情况下,我们已经了解了如何使用pg_trigger_depth()或精心设计的附加条件在适当的时刻停止递归。我们还了解了可以简化代码并提高性能WHEN的子句。CREATE TRIGGER
#PG证书#PG考试#postgresql培训#postgresql考试#postgresql认证

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/882885.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Pytest参数详解 — 基于命令行模式!

1、--collect-only 查看在给定的配置下哪些测试用例会被执行 2、-k 使用表达式来指定希望运行的测试用例。如果测试名是唯一的或者多个测试名的前缀或者后缀相同&#xff0c;可以使用表达式来快速定位&#xff0c;例如&#xff1a; 命令行-k参数.png 3、-m 标记&#xff08;…

msql事务隔离级别 线上问题

1. 对应代码 解决方式&#xff1a; 在事务隔离级别为可重复读&#xff08;RR&#xff09;时&#xff0c;数据库确实通常会记录当前数据的快照。 在可重复读隔离级别下&#xff0c;事务在执行期间看到的数据是事务开始时的数据快照&#xff0c;即使其他事务对数据进行了修改&am…

Lucas带你手撕机器学习——线性回归

什么是线性回归 线性回归是机器学习中的基础算法之一&#xff0c;用于预测一个连续的输出值。它假设输入特征与输出值之间的关系是线性关系&#xff0c;即目标变量是输入变量的线性组合。我们可以从代码实现的角度来学习线性回归&#xff0c;包括如何使用 Python 进行简单的线…

2024 最新版1200道互联网大厂Java面试题附答案详解

很多 Java 工程师的技术不错&#xff0c;但是一面试就头疼&#xff0c;10 次面试 9 次都是被刷&#xff0c;过的那次还是去了家不知名的小公司。 问题就在于&#xff1a;面试有技巧&#xff0c;而你不会把自己的能力表达给面试官。 应届生&#xff1a;你该如何准备简历&#…

4、CSS3笔记

文章目录 四、CSS3CSS3简介css3概述CSS3私有前缀什么是私有前缀为什么要有私有前缀常见浏览器私有前缀 CSS3基本语法CSS3新增长度单位CSS3新增颜色设置方式CSS3新增选择器CSS3新增盒模型相关属性box-sizing 怪异盒模型resize 调整盒子大小box-shadow 盒子阴影opacity 不透明度 …

【ChatGPT插件漏洞三连发之一】未授权恶意插件安装

漏洞 要了解第一个漏洞&#xff0c;我们必须首先向您展示 OAuth 身份验证的工作原理&#xff1a; 假设您是 Dan&#xff0c;并且您想使用您的 Facebook 帐户连接到 Example.com。当您点击“使用Facebook登录”时会发生什么&#xff1f; 在步骤 2-3 中&#xff1a; 在 Dan 单…

QT枚举类型转字符串和使用QDebug<<重载输出私有枚举类型

一 将QT自带的枚举类型转换为QString 需要的头文件&#xff1a; #include <QMetaObject> #include <QMetaEnum> 测试代码 const QMetaObject *metaObject &QImage::staticMetaObject;QMetaEnum metaEnum metaObject->enumerator(metaObject->indexOf…

【ubuntu18.04】ubuntu18.04升级cmake-3.29.8及还原系统自带cmake操作说明

参考链接 cmake升级、更新&#xff08;ubuntu18.04&#xff09;-CSDN博客 升级cmake操作说明 下载链接 Download CMake 下载版本 下载软件包 cmake-3.30.3-linux-x86_64.tar.gz 拷贝软件包到虚拟机 cp /var/run/vmblock-fuse/blockdir/jrY8KS/cmake-3.29.8-linux-x86_64…

详解mac系统通过brew安装mongodb与使用

本文目录 一、通过brew安装MongoDB二、mongodb使用示例1、启动数据库2、创建/删除数据库3、创建/删除集合 三、MongoDB基本概念1&#xff09;数据库 (database)2&#xff09;集合 &#xff08;collection&#xff09;3) 文档&#xff08;document&#xff09;4&#xff09;mong…

什么是感知与计算融合?

感知与计算融合&#xff08;Perception-Computing Fusion&#xff09;是指将感知技术&#xff08;如传感器、摄像头等&#xff09;与计算技术&#xff08;如数据处理、人工智能等&#xff09;有机结合&#xff0c;以实现对环境的更深层次理解和智能反应的过程。该技术广泛应用于…

基于ISO13400实现的并行刷写策略

一 背景及挑战 随着车辆智能化的逐渐普及&#xff0c;整车控制器数量的急剧增加&#xff0c;加之软件版本的迭代愈发频繁&#xff0c;使整车控制器刷写的数据量变得越来越大。面对如此多的控制器刷写&#xff0c;通过传统的控制器顺序刷写则易出现刷写时间过长的情况&#xff…

将本地文件上传到GIT上

上传文件时&#xff0c;先新建一个空文件&#xff0c;进行本地库初始化&#xff0c;再进行远程库克隆&#xff0c;将要上传的文件放到克隆下来的文件夹里边&#xff0c;再进行后续操作 1.在本地创建文件夹&#xff0c;将要上传的文件放在该文件下 2.在该文件页面中打开Git Bas…

免登录H5快手商城系统/抖音小店商城全开源运营版本

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 最近因为直播需要然后在互站花500买了一套仿抖音的商城系统&#xff0c;感觉确实还可以&#xff0c;反正都买了所以就分享给有需要的人 以下是互站那边的网站介绍可以了看一下&#…

【路径规划】基于蚁群算法的飞行冲突解脱

摘要 飞行冲突解脱是空中交通管理中的重要问题&#xff0c;确保飞机之间安全的距离避免冲突尤为重要。本文提出了一种基于蚁群算法的飞行冲突解脱方法&#xff0c;通过优化飞行器的路径&#xff0c;实现冲突的有效解脱。蚁群算法是一种模拟蚂蚁觅食行为的启发式算法&#xff0…

大厂为什么要禁止使用数据库自增主键

大表为何不能用自增主键&#xff1f; 数据库自增主键&#xff0c;以mysql为例&#xff0c;设置表的ID列为自动递增&#xff0c;便可以在插入数据时&#xff0c;ID字段值自动从1开始自动增长&#xff0c;不需要人为干预。 在小公司&#xff0c;或者自己做项目时&#xff0c;设置…

爬虫基础--requests模块

1、requests模块的认识 requests模块的认识请跳转到 requests请求库使用_使用requests库-CSDN博客 2、爬取数据 这里我们以b站动漫追番人数为例。 首先进去b站官网 鼠标右键点击检查或者键盘的F12&#xff0c;进入开发者模式。&#xff08;这里我使用的是谷歌浏览器为例&#…

二分查找_ x 的平方根搜索插入位置山脉数组的峰顶索引

x 的平方根 在0~X中肯定有数的平方大于X&#xff0c;这是肯定的。我们需要从中找出一个数的平方最接近X且不大于X。0~X递增&#xff0c;它们的平方也是递增的&#xff0c;这样我们就可以用二分查找。 我们找出的数的平方是<或者恰好X&#xff0c;所以把0~X的平方分为<X …

Elasticsearch是做什么的?

初识elasticsearch 官方网站&#xff1a;Elasticsearch&#xff1a;官方分布式搜索和分析引擎 | Elastic Elasticsearch是做什么的&#xff1f; Elasticsearch 是一个分布式搜索和分析引擎&#xff0c;专门用于处理大规模数据的实时搜索、分析和存储。它基于 Apache Lucene …

文言文编程,没错,尤雨溪都点赞了

文言文编程&#xff0c;没错&#xff0c;尤雨溪都点赞了 在现代编程语言百花齐放的今天&#xff0c;居然有人选择用古典汉语来写代码&#xff1f;这就是文言编程语言 Wenyan-lang&#xff0c;一种让你在写代码时&#xff0c;仿佛重回古代&#xff0c;挥毫泼墨般潇洒。本文将带你…

Ubuntu22.04安装RTX3080

Ubuntu22.04安装RTX3080 1 安装基础环境 更新依赖包 sudo apt-get update sudo apt-get upgrade2 安装驱动 &#xff08;1&#xff09;查看适合的显卡驱动 # 查看可用的驱动 sudo ubuntu-drivers devices# 返回值&#xff0c;推荐版本&#xff1a;nvidia-driver-550 ERROR…