首页 > 解决方案 > Postgresql 触发器函数偶尔无法正确更新其目标——可能的竞争条件?

问题描述

我有一个系统设置为一系列jobstasks. 每个作业都由几个任务组成,还有一个task_progress表只记录每个任务的当前状态。tasks(出于与此问题无关的业务原因,此信息与主表分开保存。)

jobs表有一个整体列,当所有作业的任务都达到最终状态(或)时status,需要更新该列。这由触发函数处理:completedokerror

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应该防止这样的比赛条件,但我无法解释我所看到的其他情况。

我在这里最好的选择是什么?

标签: sqlpostgresqltriggers

解决方案


是的,有一个竞争条件。如果最后两个任务几乎同时完成,则触发器函数可以同时运行。由于触发器作为事务的一部分运行,并且事务都尚未提交,因此没有一个触发器函数可以看到另一个事务所做的数据修改。因此,每个人都认为仍有一项任务悬而未决。

您可以使用咨询锁来确保不会发生这种情况:就在 之前SELECT count(*) ...,添加

SELECT pg_advisory_xact_lock(42);

这确保没有会话将执行查询,而另一个已经执行查询的会话仍未提交,因为锁定一直保持到事务结束。


推荐阅读