sql - Postgresql 触发器函数偶尔无法正确更新其目标——可能的竞争条件?
问题描述
我有一个系统设置为一系列jobs
和tasks
. 每个作业都由几个任务组成,还有一个task_progress
表只记录每个任务的当前状态。tasks
(出于与此问题无关的业务原因,此信息与主表分开保存。)
该jobs
表有一个整体列,当所有作业的任务都达到最终状态(或)时status
,需要更新该列。这由触发函数处理:completed
ok
error
CREATE OR REPLACE FUNCTION update_job_status_when_progress_changes()
RETURNS trigger AS $$
DECLARE
current_job_id jobs.id%TYPE;
pending integer;
BEGIN
SELECT tasks.job_id INTO current_job_id
FROM tasks WHERE tasks.id = NEW.task_id;
SELECT COUNT(*) INTO pending
FROM task_progress
JOIN tasks ON task_progress.task_id = tasks.id
WHERE tasks.job_id = current_job_id
AND task_progress.status NOT IN ('ok', 'error');
IF pending = 0 THEN
UPDATE jobs
SET status = 'completed', updated_at = NOW() AT TIME ZONE 'utc'
WHERE jobs.id = current_job_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql
CREATE TRIGGER task_progress_update_job_status
AFTER UPDATE OR DELETE
ON task_progress
FOR EACH ROW
EXECUTE PROCEDURE update_job_status_when_progress_changes()
它几乎完全没问题。但有时——比如,可能每几百个工作一次——一个工作将无法转换为completed
状态。进度行全部正确;根据task_progress
表内容显示完成百分比的业务逻辑达到 100%,但作业的状态保持在processing
. 我们无法可靠地复制它;这只是不时发生的事情。但这很令人沮丧,我想把它确定下来。
不涉及任何交易;每个任务进度都由完成任务的进程自动更新。
是否有可能遇到这样的情况,例如,作业中的最后两个任务几乎同时完成,导致任务 A 的触发器看到任务 B 仍处于挂起状态,反之亦然?我认为FOR EACH ROW
应该防止这样的比赛条件,但我无法解释我所看到的其他情况。
我在这里最好的选择是什么?
解决方案
是的,有一个竞争条件。如果最后两个任务几乎同时完成,则触发器函数可以同时运行。由于触发器作为事务的一部分运行,并且事务都尚未提交,因此没有一个触发器函数可以看到另一个事务所做的数据修改。因此,每个人都认为仍有一项任务悬而未决。
您可以使用咨询锁来确保不会发生这种情况:就在 之前SELECT count(*) ...
,添加
SELECT pg_advisory_xact_lock(42);
这确保没有会话将执行查询,而另一个已经执行查询的会话仍未提交,因为锁定一直保持到事务结束。
推荐阅读
- excel - 通过 FTP 发布 Excel 加载项
- android - 未调用 AndroidManifest 中设置的活动
- java - 如何在环境变量中赋予多个系统变量相同的优先级?
- spring - 将对象从 thymeleaf 模板发送到 Rest Controller 返回“不支持的媒体类型”
- css - 引导网站,嵌入 iframe (youtube) 溢出并阻止其他内容
- go - Go 应用程序在 NGINX 代理后面不起作用:HTTP 502
- python-3.x - 检查对象是否具有来自 python3 中给定命名空间的类型
- printing - Cypher - 尝试将所有节点打印到文本 o/p 超出 Java 堆空间 - Neo4j V 3.5
- intellij-idea - 如何在maven中用EAR、EJB和WEB构建JEE项目?
- regex - 使用 ant 语句 replaceregexp 时出现错误“无法重命名临时文件”