一个用于sqlalchemy的大容量延迟加载程序,它解决了n+1加载问题
sqlalchemy-bulk-lazy-loader的Python项目详细描述
一个用于sqlalchemy关系的自定义惰性加载程序,它确保关系始终有效加载。此加载程序自动解决n+1 query problem问题,而无需手动向所有查询添加joinedload或subqueryload语句
问题
每当在循环中延迟加载关系时,就会出现n+1查询问题例如:
students=session.query(Student).limit(100).all()forstudentinstudents:print('{} studies at {}'.format(student.name,student.school.name))
在上面的代码中,将生成101个sql查询-一个用于加载学生列表,然后为每个学生生成100个单独查询以加载该学生的学校。声明如下:
SELECT*FROMstudentsLIMIT100;SELECT*FROMschoolsWHEREschools.student_id=1LIMIT1;SELECT*FROMschoolsWHEREschools.student_id=2LIMIT1;SELECT*FROMschoolsWHEREschools.student_id=3LIMIT1;SELECT*FROMschoolsWHEREschools.student_id=4LIMIT1;...
这很糟糕
用sqlalchemy解决这个问题的传统方法是在初始查询中添加一个joinedload或subqueryload,以包括学校和学生,如下所示:
students=(session.query(Student).options(subqueryload(Student.school)).limit(100).all())forstudentinstudents:print('{} studies at {}'.format(student.name,student.school.name))
虽然这样做有效,但它需要添加到执行的每个查询中,并且当添加了许多相关模型时,您可以很容易地得到大量的subqueryload和joinedload列表。如果您在任何地方都忘记了一个,那么您将静默地返回到n+1查询问题。另外,如果以后不再需要关系,则需要记住从原始查询中删除它,否则现在加载的数据太多。此外,在任何有数据库查询的地方,都必须在代码中维护这些相关模型的列表,这是一个巨大的痛苦。
如果您不必担心添加subqueryload和joinedload,而且还可以保证所有关系都能有效加载,这不是很好吗
批量延迟加载程序的工作原理
99%的情况下,如果内存中加载了一个模型列表,并且其中一个模型上的关系是延迟加载的,那么您就处于循环中,并且将在每个其他模型上请求相同的关系。sqlalchemy bulk lazy loader假设是这种情况,并且每当模型上的关系被延迟加载时,它将在当前会话中查找需要加载相同关系的任何其他类似模型,并将发出一个单独的批量sql语句来一次加载它们。
这意味着您可以在循环中加载所需的所有关系,同时确保所有关系都以性能方式加载,并且只加载所使用的关系。例如,上面的代码相同:
students=session.query(Student).limit(100).all()forstudentinstudents:print('{} studies at {}'.format(student.name,student.school.name))
大容量延迟加载程序将只发出2条sql语句,就像您在初始查询中指定了subqueryload一样,只是现在您的代码更干净了,而且您可以保证只加载所需的关系。耶!
安装
SQLAlchemy Bulk Lazy Loader可以通过pip安装
pip install SQLAlchemy-bulk-lazy-loader
用法
在声明SQLAlchemy映射之前,需要运行以下命令:
fromsqlalchemy_bulk_lazy_loaderimportBulkLazyLoaderBulkLazyLoader.register_loader()
这将向sqlalchemy注册加载程序,并通过在关系映射中指定lazy='bulk'使其在关系上可用。例如:
classStudent(db.model):id=db.Column(db.Integer,primary_key=True)school_id=db.Column(db.Integer,db.ForeignKey('school.id'))classSchool(db.model):id=db.Column(db.Integer,primary_key=True)students=db.relationship('Student',lazy='bulk',backref=db.backref('school',lazy='bulk'))
就这样!批量延迟加载程序将用于student.school和school.students关系。
限制
目前只支持单个主键或简单辅助联接上的关系。
students=relationship('Student',lazy='bulk')# OK!students=relationship('Student',lazy='bulk',order_by=Student.id)# OK!student=relationship('Student',lazy='bulk',uselist=False)# OK!students=relationship('Student',lazy='bulk',secondary=school_to_students)# OK!students=relationship('Student',lazy='bulk',secondary=school_to_students,primaryjoin='and_(...)')# NOT SUPPORTED
不支持Python2。
但我有一个案例,我想用不同的方式加载关系!
如果要在查询中加载仍使用subqueryload或joinedload的关系,您仍然可以这样做-只有在尚未加载的模型上请求关系时,批量延迟加载程序才会启动。如果在特定情况下确实需要对关系加载进行细粒度控制,还可以使用attributes.set_committed_value(model, <relation_name>, <related_model/s>)显式设置相关模型。实际上,BulkLazyLoader就是这样在幕后工作的。
贡献
欢迎投稿!创建一个请求并确保添加测试覆盖率测试使用sqlalchemy测试框架,可以使用py.test运行。
装载愉快!