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.rhkb.cn/news/454371.html

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

相关文章

电脑视频剪辑大比拼,谁更胜一筹?

随着短视频的火爆&#xff0c;越来越多的人开始尝试自己动手制作视频&#xff0c;无论是记录生活点滴还是创作个性短片&#xff0c;一款好用的视频剪辑软件是必不可少的。今天&#xff0c;我们就从短视频运营的角度&#xff0c;来聊聊几款热门的电脑视频剪辑软件&#xff0c;看…

FineReport 数据筛选过滤

从大量的数据当中&#xff0c;获取到符合条件的数据&#xff0c;经常会使用到数据筛选过滤功能&#xff0c;在FineReort产品中实现筛选过滤的方法有三种 1&#xff09;直接通过 SQL 语句取出满足条件的的数据&#xff0c;如修改数据集 SQL 语句为&#xff1a;SELECT * FROM 订单…

YOLOv8改进,YOLOv8采用WTConv卷积(感受野的小波卷积),二次创新C2f结构,ECCV 2024

摘要 WTConv(基于小波变换的卷积层),用于在卷积神经网络(CNN)中实现大感受野。作者通过利用小波变换,设计了一个卷积层,可以在保持少量可训练参数的情况下大幅扩大感受野。WTConv 被设计为可以无缝替换现有 CNN 架构中的深度卷积层,适用于图像分类、语义分割、物体检测…

Vue-插槽slot

当我们封装一个组件时&#xff0c;不希望里面的内容写死&#xff0c;希望使用的时候能够自定义里面的内容&#xff0c;这时我们就需要使用到插槽 插槽是什么呢 插槽是子组件提供给父组件的一个占位符&#xff0c;用slot标签表示&#xff0c;父组件可以在这个标签填写任何模板代…

3DMAX碎片生成器插件FragmentGenerator使用方法

3DMAX碎片生成器FragmentGenerator插件&#xff0c;主要应用于科研绘图方面&#xff0c;一键从选择对象体积上生成若干不规则大小凌乱排列的三角形面。 【适用版本】 3dMax2015 – 2025&#xff08;不仅限于此范围&#xff09; 【安装方法】 FragmentGenerator插件无需安装&a…

基于vue框架的的二手数码产品回收管理系统bodx1(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,产品分类,产品信息,预约回收,回收员,产品回收 开题报告内容 基于Vue框架的二手数码产品回收管理系统开题报告 一、课题背景及意义 随着科技的快速发展和消费者生活水平的提高&#xff0c;数码产品的更新换代速度日益加快&#x…

网络安全中的日志审计:为何至关重要?

在数字化时代&#xff0c;网络安全已成为企业和组织不可忽视的重要议题。随着网络攻击手段的不断进化&#xff0c;保护信息系统和数据安全变得日益复杂和具有挑战性。在这种背景下&#xff0c;日志审计作为一种关键的信息安全和网络管理工具&#xff0c;发挥着至关重要的作用。…

软考(网工)——局域网和城域网

文章目录 &#x1f550;局域网基础1️⃣局域网和城域网体系架构 IEEE&#xff08;负责链路层&#xff09;2️⃣局域网拓扑结构 &#x1f551;CSMA/CD1️⃣CSMA/CD2️⃣CSMA/CD三种监听算法3️⃣冲突检测原理 &#x1f552;二进制指数退避算法1️⃣ 二进制指数退避算法 &#x1…

envoyFilter导致的webSockets协议无法正常工作

一、背景 生产项目有一个socket请求经过网关一直无法响应&#xff0c;其它接口服务都能正常处理。 二、 处理过程 让租户提供对应的模拟请求接口&#xff0c;然后进行模拟请求测试&#xff0c;并查看envoy网关日志&#xff0c;发现在发起请求时&#xff0c;envoy网关日志是无法…

解密 Redis:如何通过 IO 多路复用征服高并发挑战!

文章目录 一、什么是 IO 多路复用&#xff1f;二、为什么 Redis 要使用 IO 多路复用&#xff1f;三、Redis 如何实现 IO 多路复用&#xff1f;四、IO 多路复用的核心机制&#xff1a;epoll五、IO 多路复用在 Redis 中的工作流程六、IO 多路复用的优点七、IO 多路复用使用中的注…

CTF(四)

导言&#xff1a; 本文主要讲述在CTF竞赛中&#xff0c;web类题目file_include。 靶场链接&#xff1a;攻防世界 (xctf.org.cn) 一&#xff0c;观察页面。 可以看到一段php代码。从则段代码中我们可以知道&#xff1a; 1&#xff0c;使用include引入check.php文件&#xff…

排序算法 —— 快速排序(理论+代码)

目录 1.快速排序的思想 2.快速排序的实现 hoare版 挖坑法 前后指针法 快排代码汇总 3.快速排序的优化 三数取中 小区间优化 三路划分 4.快速排序的非递归版本 5.快速排序总结 1.快速排序的思想 快速排序是一种类似于二叉树结构的排序方法。其基本思想为从待排序序…

【前端】如何制作一个自己的网页(15)

有关后代选择器的具体解释&#xff1a; 后代选择器 后代选择器使用时&#xff0c;需要以空格将多个选择器间隔开。 比如&#xff0c;这里p span&#xff0c;表示只设置p元素内&#xff0c;span元素的样式。 <style> /* 使用后代选择器设置样式 */ p span { …

给EXE添加网络验证激活码(卡密)

介绍 网络验证可以理解为给EXE文件添加一个激活码, 用户在打开EXE文件时, 需要输入激活码, 输入后, 通过网络验证激活码, 如果激活码有效用户便可以继续使用软件. 网络验证可以生成静态激活码(也就是卡密), 再需要使用的时候直接发给用户即可, 无需像离线一机一码加密那样需要…

漏洞挖掘 | 基于mssql数据库的sql注入

前记 今天挖edu随意点开个站&#xff0c;发现存在mssql数据库的sql注入&#xff0c;在此分享下整个挖掘过程 目录 0x1 判断网站数据库类型 0x2 了解mssql数据库的主要三大系统表 0x3 了解mssql的主要函数 0x4 判断注入点及其注入类型 0x5 联合查询之判断列数 0x6 联合查询之…

spring源码拓展点3之addBeanPostProcesser

概述 在refresh方法中的prepareBeanFactory方法中&#xff0c;有一个拓展点&#xff1a;addBeanPostProcessor。即通过注入Aware对象从而将容器中的某些值设置到某个bean中。 beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));aware接口调用 …

华为配置 之 Console线路配置

目录 简介&#xff1a; 知识点&#xff1a; 配置Console线路密码 1.密码认证模式 2.AAA认证模式 知识点&#xff1a; 总结&#xff1a; 简介&#xff1a; 使用PC模拟器与路由器相连&#xff08;与交换机相连原理一样&#xff09;&#xff0c;在关机状态下&#xff0c;使用…

手机玩黑色沙漠?GameViewer远程玩黑色沙漠教程

黑色沙漠的国服即将在10月24日迎来公测&#xff01;这是一款玩法多元的大型多人在线角色扮演游戏&#xff0c;你可以享受激烈的战斗&#xff0c;也可以感受惬意的生活&#xff0c;在这个游戏里你能体验到一个不一样的冒险故事。不管你是老玩家还是新玩家&#xff0c;只要你想玩…

鸿蒙开发:实现一个超简单的网格拖拽

前言 网格拖拽&#xff0c;此功能很是常见&#xff0c;一般用于频道的编辑或者条目顺序的排列&#xff0c;在鸿蒙的开发中&#xff0c;针对网格的编辑&#xff0c;系统也给出了相关的Api&#xff0c;通过onItemDragStart和在onItemDrop即可轻松实现&#xff0c;onItemDragStart…

Linux LVS详解

LVS&#xff08;Linux Virtual Server&#xff09;即Linux虚拟服务器&#xff0c;是一个基于Linux操作系统的高性能、可扩展的负载均衡器。以下是对LVS的详细介绍&#xff1a; 一、简介 LVS项目由章文嵩博士在1998年5月发起&#xff0c;是中国国内最早出现的自由软件项目之一…