You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PL/pgSQL长时循环任务进度监控优化方案咨询

PL/pgSQL实现类似tqdm/R progress的进度监控

我有运行时长可达数小时的PL/pgSQL函数和存储过程,需要实现类似Python tqdm或R progress的进度监控功能,确认任务在正常推进。

现有实现的问题

我已经有一个可用的基础版本,功能是读取example.task_list表中的整数,通过Upsert将其两倍值存入第二列。但这个实现有两个明显不足:

  • 重复执行相同SQL查询:一次统计待完成任务数,一次用于循环遍历
  • 业务逻辑与计时、进度通知代码混杂,可读性差

现有粗糙实现代码

CREATE TABLE example.task_list (
  intt int NOT NULL PRIMARY KEY,
  double_intt int
);

INSERT INTO example.task_list VALUES
  (1, NULL), (2, NULL), (3, NULL), (4, NULL)
RETURNING *;

CREATE OR REPLACE function example.doloop()
  returns void
  LANGUAGE plpgsql AS
$func$
DECLARE
   begin_time timestamptz = clock_timestamp();
   num_tasks_done int := 0;
   average_time_per_task interval := null;
   tasks_remaining int;
   f record;
   time_to_do interval;
BEGIN
   SELECT count(*) INTO tasks_remaining FROM (SELECT DISTINCT intt from example.task_list where double_intt is null);
   raise notice '%: Starting. We have  [%] tasks to do.', clock_timestamp(), tasks_remaining;
   FOR f IN (SELECT DISTINCT intt from example.task_list where double_intt is null)
      LOOP
      begin_time = clock_timestamp();
      raise notice '%: Putting in [%]', begin_time, f.intt;
      raise notice '%:   We have done [%] jobs and have [%] jobs remaining. It will take %', clock_timestamp(), num_tasks_done, tasks_remaining, average_time_per_task * tasks_remaining;
      INSERT INTO example.task_list (intt, double_intt) values (f.intt, 2 * f.intt)
      ON CONFLICT("intt") DO UPDATE
      SET double_intt = EXCLUDED.double_intt;
      time_to_do = clock_timestamp() - begin_time;
      raise notice '%:        Finished. It took : %', clock_timestamp(), time_to_do;
      /* Do calculations for time remaining */
      average_time_per_task = ((coalesce(average_time_per_task,'0 minutes') * num_tasks_done) + (time_to_do)) / (num_tasks_done+1);
      num_tasks_done = num_tasks_done + 1;
      tasks_remaining = tasks_remaining - 1;
      END LOOP;
  END;
$func$;
select example.doloop();

优化方案:复用任务列表 + 封装进度逻辑

1. 复用任务列表:避免重复查询

先将待处理的任务结果存入游标或临时表,只执行一次查询,既可以获取总任务数,又可以遍历处理。

2. 封装进度监控逻辑:分离业务与监控

创建自定义类型存储进度状态,编写通用的进度跟踪函数负责计时、计算剩余时间和输出通知,让业务逻辑专注于核心操作。

最终实现代码

第一步:创建进度跟踪的自定义类型和通用函数

-- 定义进度跟踪的状态类型
CREATE TYPE example.progress_state AS (
    total_tasks int,
    completed_tasks int,
    avg_time_per_task interval,
    start_time timestamptz
);

-- 初始化进度状态的函数
CREATE OR REPLACE FUNCTION example.init_progress(total_tasks int)
RETURNS example.progress_state AS $$
BEGIN
    RETURN (total_tasks, 0, '0 seconds'::interval, clock_timestamp())::example.progress_state;
END;
$$ LANGUAGE plpgsql;

-- 更新进度并输出通知的函数
CREATE OR REPLACE FUNCTION example.update_progress(state INOUT example.progress_state, task_id text DEFAULT '')
RETURNS example.progress_state AS $$
DECLARE
    elapsed_time interval;
    remaining_tasks int;
    estimated_remaining interval;
BEGIN
    -- 计算当前任务耗时
    elapsed_time := clock_timestamp() - state.start_time;
    
    -- 更新平均耗时
    state.avg_time_per_task := (state.avg_time_per_task * state.completed_tasks + elapsed_time) / (state.completed_tasks + 1);
    
    -- 更新完成数与剩余数
    state.completed_tasks := state.completed_tasks + 1;
    remaining_tasks := state.total_tasks - state.completed_tasks;
    
    -- 计算预计剩余时间
    estimated_remaining := state.avg_time_per_task * remaining_tasks;
    
    -- 输出进度通知
    RAISE NOTICE '[%] 进度: 已完成 %/% 任务 | 当前任务耗时: % | 预计剩余时间: %',
        clock_timestamp(),
        state.completed_tasks,
        state.total_tasks,
        elapsed_time,
        estimated_remaining;
    
    -- 重置下一个任务的开始时间
    state.start_time := clock_timestamp();
END;
$$ LANGUAGE plpgsql;

第二步:优化后的业务函数

CREATE OR REPLACE FUNCTION example.do_loop_with_progress()
RETURNS void AS $$
DECLARE
    -- 定义游标存储待处理任务(仅执行一次查询)
    temp_tasks CURSOR FOR SELECT DISTINCT intt FROM example.task_list WHERE double_intt IS NULL;
    task_record record;
    progress example.progress_state;
    total_tasks int;
BEGIN
    -- 获取总任务数
    SELECT count(*) INTO total_tasks FROM (SELECT DISTINCT intt FROM example.task_list WHERE double_intt IS NULL) t;
    
    -- 初始化进度状态
    progress := example.init_progress(total_tasks);
    RAISE NOTICE '%: 任务启动,共 % 个待处理任务', clock_timestamp(), total_tasks;
    
    -- 遍历任务并执行业务逻辑
    FOR task_record IN temp_tasks LOOP
        -- 核心业务操作:Upsert两倍值
        INSERT INTO example.task_list (intt, double_intt)
        VALUES (task_record.intt, 2 * task_record.intt)
        ON CONFLICT(intt) DO UPDATE
        SET double_intt = EXCLUDED.double_intt;
        
        -- 调用进度更新函数
        PERFORM example.update_progress(progress, task_record.intt::text);
    END LOOP;
    
    RAISE NOTICE '%: 所有任务完成,总耗时: %', clock_timestamp(), clock_timestamp() - (progress).start_time;
END;
$$ LANGUAGE plpgsql;

调用优化后的函数

SELECT example.do_loop_with_progress();

方案优势

  • 无重复查询:仅执行一次任务列表查询,同时获取总数和遍历数据
  • 逻辑解耦:业务代码专注核心操作,进度监控逻辑封装为通用组件,可读性与可维护性提升
  • 可复用性:进度跟踪的类型和函数可直接用于其他需要监控的PL/pgSQL函数

内容的提问来源于stack exchange,提问作者Stuart

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 06:54:56