SQLAlchemy 1.1.18中遍历CollectionAdapter为何速度极慢?
这个问题的核心是N+1查询陷阱,我来给你拆解原因和解决方案:
为什么速度慢?
你定义的qws关系默认是**懒加载(lazy='select')**模式——这意味着当你第一次访问self.qws时,SQLAlchemy才会单独发送一条SQL查询去拉取当前Project对应的ProjectQWS数据。
当你遍历1000+个Project对象时,每个对象都会触发一次额外的查询:
SELECT projects_qws.project_id, projects_qws.qws_id FROM projects_qws WHERE projects_qws.project_id = ?
哪怕99%的Project只有1条qws数据,1000个Project就会多1000次数据库请求,每次几百毫秒的耗时累积起来自然就很慢了。而当qws为空时,SQLAlchemy能通过外键约束或主查询的上下文判断关联为空,不会触发额外查询,所以速度快。
解决方案
1. 预加载关联数据(推荐)
在查询Project的时候,用joinedload或subqueryload一次性把所有关联的qws数据加载完成,彻底避免N+1查询:
- 使用
joinedload(适合关联数据少的场景):
from sqlalchemy.orm import joinedload # 查询Project时预加载qws projects = session.query(Project).options(joinedload(Project.qws)).all()
它会生成一条包含JOIN的SQL,一次性把projects和对应的projects_qws数据都拉取回来,后续访问project.qws直接用内存里的数据,不会再发SQL。
- 使用
subqueryload(适合关联数据多、避免重复Project对象的场景):
如果一个Project有多个qws,joinedload会导致Project对象重复出现,这时候可以用subqueryload:
from sqlalchemy.orm import subqueryload projects = session.query(Project).options(subqueryload(Project.qws)).all()
它会先查所有Project,再用一个子查询一次性查所有关联的qws,然后在内存中把数据关联起来,既避免N+1,又不会重复Project对象。
2. 直接修改关系的懒加载模式
如果这个qws关联几乎每次查询Project都需要用到,可以直接把relationship的lazy参数改成'joined',这样每次查询Project都会自动预加载qws:
qws = relationship( 'ProjectQWS', foreign_keys='ProjectQWS.project_id', cascade='all, delete-orphan', order_by=ProjectQWS.is_main, lazy='joined' # 新增这一行 )
注意:如果有部分场景不需要加载qws,这个设置会带来不必要的JOIN开销,需要根据业务场景权衡。
3. 简化get_qws_dicts方法(可选)
虽然不是性能瓶颈,但可以用列表推导式让代码更简洁:
def get_qws_dicts(self): return [qws.to_dict() for qws in self.qws] if self.qws else []
验证方法
可以打开SQLAlchemy的SQL日志,看看优化前后的查询数量变化:
import logging logging.basicConfig() logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
优化前会看到大量针对projects_qws的单独查询,优化后只会看到1-2条查询语句。
内容的提问来源于stack exchange,提问作者Yonoss

