使用Python billiard多进程时同一年份SQL查询重复执行的问题排查
核心原因排查
你遇到的每个年份被重复执行的现象,大概率由以下几个问题导致:
函数定义位置错误
在Python多进程(包括billiard)中,子进程需要能正确导入任务函数。你的代码中run_sql函数定义在Pool创建之后,在Windows系统或使用spawn启动模式时,子进程无法找到该函数,可能引发异常的执行行为(比如重复触发任务)。返回值误导判断
run_sql函数返回的是全局变量years(整个年份列表),导致pool.map的结果r是[[2019,2020,2021], [2019,2020,2021], [2019,2020,2021]]。你可能看到日志里的这个结果,误以为每个年份被重复执行,但实际每个年份仅执行了一次。SQL执行模块的潜在逻辑错误
如果<module to run sql>部分的代码存在隐藏循环(比如不小心遍历了整个years列表),会导致单个任务内部重复执行所有年份的查询,出现每个年份被多次执行的情况。对进程数与任务数的误解
你设置了processes=4,但只有3个任务,Pool会启动4个进程(其中一个空闲)。你可能看到4个进程存在,误以为每个进程都在执行所有年份的查询,但实际上每个任务仅由一个进程处理一次。
修复方案
调整函数定义位置
将run_sql函数移到Pool创建代码之前,确保子进程能正确加载该函数:def run_sql(params): year, name, sql, parameters = params try: sql_filtered = sql + f''' where date_part('year', created_at) = {year}''' # <module to run sql> return year # 返回单个年份,而非全局列表 except Exception as e: raise e # 后续Pool创建代码 years = [2019, 2020, 2021] params = zip(years, repeat(<table_name>), repeat(<sql>), repeat(<parameters>)) pool = Pool(processes=4) # ... 剩余代码修改返回值便于验证
将return years改为return year,这样pool.map的结果会是[2019,2020,2021],能直观确认每个年份仅执行一次。添加日志验证执行情况
在run_sql中添加进程ID和年份的打印,明确每个任务的执行主体:import os def run_sql(params): year, name, sql, parameters = params try: print(f"进程ID {os.getpid()} 正在处理年份 {year}") sql_filtered = sql + f''' where date_part('year', created_at) = {year}''' # <module to run sql> return year except Exception as e: raise e运行后可以清晰看到每个年份由哪个进程处理,是否存在重复执行。
检查SQL执行模块
确认<module to run sql>部分没有遍历years列表的逻辑,仅基于传入的year参数执行单次查询。
内容的提问来源于stack exchange,提问作者Saraki

