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

Flask-SQLAlchemy内存泄漏排查:无限循环生成题库致内存耗尽

内存泄漏原因分析与修复方案

首先明确:这不是无限循环本身的问题,无限循环只是让内存泄漏的症状被放大——因为程序长期运行,内存缓慢累积直到触发上限崩溃。从你给出的内存追踪数据来看,每次循环内存净增长约5000字节,根源是ORM会话的对象缓存和一些代码细节导致的内存无法被回收。

核心问题原因

  1. SQLAlchemy会话的对象累积
    你使用的ORM(看起来是SQLAlchemy)的db.session会默认缓存所有通过query加载的对象(存在identity map中),哪怕你执行了db.session.commit(),这些对象依然会被会话持有引用,无法被Python的垃圾回收(GC)机制回收。每次循环都会加载新的Problem、Skill对象,日积月累就会导致内存持续上涨。

  2. 冗余查询与不必要的对象加载

    • 你多次重复查询同一个固定对象(比如Skill.query.filter_by(id=1).first()、Skill.query.filter_by(skillName="generated").first()),每次查询都会把对象加入会话缓存,完全没必要重复查询。
    • 检查问题是否存在时,你用了Problem.query.filter_by(question=prob).first() == None,这会把整个Problem对象加载到内存里,而实际上只需要判断存在性,不需要加载完整对象。
  3. tracemalloc的追踪数据累积
    你全程开启了tracemalloc.start(),它会记录所有内存分配的快照数据,长期运行这些数据本身也会占用大量内存,如果只是调试用,调试完成后应该关闭或者定期清理。

  4. 异常处理的小bug
    代码里myfile.write(sys.exc_value)是错误的,sys.exc_value在Python3中已经被废弃,应该用str(e),而且这个错误可能会导致异常信息无法正确写入,甚至引发新的异常。


具体修复步骤

1. 定期清理ORM会话

在每次generate_problem函数执行完成后,调用db.session.remove()来清理会话的identity map,释放对象引用,让GC可以回收内存:

def generate_problem(gen_id):
    # ... 原有代码 ...
    db.session.commit()
    print("2: " + str(tracemalloc.get_traced_memory()[0]))
    # 新增:清理会话
    db.session.remove()
    return prob, ans, generator_name
else:
    # 即使问题已存在,也要清理会话
    db.session.remove()
    return "Problem already exists", "N/A", "N/A"

2. 优化查询逻辑,减少不必要的对象加载

  • 提前缓存固定的Skill对象,避免重复查询:
# 在auto_generate函数外或者程序启动时提前查询一次
skill_id1 = Skill.query.filter_by(id=1).first()
skill_generated = Skill.query.filter_by(skillName="generated").first()

def generate_problem(gen_id):
    poster = User.query.filter_by().first()
    gen_list = mathgen.getGenList()
    prob, ans = mathgen.genById(gen_id)
    print("1: " + str(tracemalloc.get_traced_memory()[0]))
    generator_name = gen_list[gen_id][1]
    
    # 用exists()替代first() == None,只判断存在性,不加载完整对象
    if not Problem.query.filter_by(question=prob).exists():
        Problem.create(
            question=prob,
            answer=ans,
            poster_id=poster.id,
            correctnessRating=1,
            sortRating=1,
            difficultyLevel=None,
            expectedTime=None,
            hasSolution=True,
        )
        thisProb = Problem.query.filter_by(question=prob).first()
        # 使用提前缓存的对象
        thisProb.skills.append(skill_id1)
        
        # 检查generator_name对应的Skill是否存在
        generator_skill = Skill.query.filter_by(skillName=generator_name).first()
        if not generator_skill:
            generator_skill = Skill.create(skillName=generator_name)
        thisProb.skills.append(generator_skill)
        
        thisProb.skills.append(skill_generated)
        db.session.commit()
        print("2: " + str(tracemalloc.get_traced_memory()[0]))
        db.session.remove()
        return prob, ans, generator_name
    else:
        db.session.remove()
        return "Problem already exists", "N/A", "N/A"

3. 调整tracemalloc的使用

如果只是调试内存,调试完成后记得关闭追踪:

def auto_generate():
    tracemalloc.start()
    try:
        while True:  # 用while True替代while 0==0,更易读
            # ... 原有循环代码 ...
    finally:
        tracemalloc.stop()  # 调试完成后关闭追踪

如果需要长期追踪,可以定期清理追踪数据:

# 每N次循环清理一次,比如每100次
loop_count = 0
while True:
    loop_count +=1
    # ... 原有代码 ...
    if loop_count % 100 == 0:
        tracemalloc.clear_traces()

4. 修复异常处理的bug

import datetime  # 需要导入datetime模块

# ... 原有代码 ...
except Exception as e:
    with open("gen_errors.txt", "a") as myfile:
        # 写入时间戳和异常信息,方便排查
        myfile.write(f"{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}: {str(e)}\n")

验证修复效果

修复后,你可以重新用tracemalloc追踪内存变化,应该会看到内存不再持续上涨,而是在一个稳定的区间内波动(因为GC会回收不再使用的对象)。如果还有小幅度增长,可以进一步检查是否有其他未释放的资源,比如文件句柄、第三方库的缓存等,但以上修复应该能解决大部分问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:38:30