加载关系时如何指示SQLAlchemy ORM并行执行多个查询?
Posted
技术标签:
【中文标题】加载关系时如何指示SQLAlchemy ORM并行执行多个查询?【英文标题】:How to instruct SQLAlchemy ORM to execute multiple queries in parallel when loading relationships? 【发布时间】:2017-06-09 05:05:39 【问题描述】:我正在使用 SQLAlchemy 的 ORM。我有一个具有多个多对多关系的模型:
User
User <--MxN--> Organization
User <--MxN--> School
User <--MxN--> Credentials
我正在使用association tables 实现这些,因此还有我不直接使用的 User_to_Organization、User_to_School 和 User_to_Credentials 表。
现在,当我尝试使用联合预加载加载单个用户(使用其 PK 标识符)及其关系(和相关模型)时,我得到了可怕的性能(15 秒以上)。我认为这是由于this issue:
当结合或子查询加载使用多级深度时,加载集合内集合将使以笛卡尔方式获取的总行数相乘。两种形式的预加载总是从原始父类加入。
如果我在层次结构中引入另一个或两个级别:
Organization <--1xN--> Project
School <--1xN--> Course
Project <--MxN--> Credentials
Course <--MxN--> Credentials
查询需要 50 多秒才能完成,即使每个表中的记录总量相当少。
使用延迟加载,我需要手动加载每个关系,并且到服务器有多次往返。
例如 操作,作为查询串行执行:
获取用户 获取用户的组织 获取用户的学校 获取用户凭据 为每个组织获取其项目 为每所学校获取其课程 对于每个项目,获取其凭据 为每门课程获取其证书不过,这一切都在不到 200 毫秒内完成。
我想知道是否确实使用延迟加载,但并行执行关系加载查询。例如,使用concurrent
模块、asyncio
或使用gevent
。
例如 第 1 步(并行):
获取用户 获取用户的组织 获取用户的学校 获取用户凭据第 2 步(并行):
为每个组织获取其项目 为每所学校获取其课程第 3 步(并行):
对于每个项目,获取其凭据 为每门课程获取其证书其实此时,做一个子查询类型加载也是可以的,即在两个单独的查询中分别返回Organization和OrganizationID/Project/Credentials:
例如 第 1 步(并行):
获取用户 获取用户的组织 获取用户的学校 获取用户凭据第 2 步(并行):
获取组织 获取学校 获取组织的项目,加入凭证 获取学校课程,加入证书【问题讨论】:
让我们看看你的 15 秒查询的 SQL;也许我们可以从那里开始工作。 您可以查看范围会话以了解并发方法:sqlalchemy: scoped session 范围会话不会给你并发性 - 除非我遗漏了什么? 【参考方案1】:mysql 在单个连接中没有并行性。对于 ORM 来说,这样做需要与 MySQL 的多个连接。一般来说,尝试这样做的开销是“不值得的”。
要获得user
、他的Organizations
、Schools
等,都可以通过单个查询完成(在mysql中):
SELECT user, organization, ...
FROM Users
JOIN Organizations ON ...
etc.
这比
效率高得多SELECT user FROM ...;
SELECT organization ... WHERE user = ...;
etc.
(这不是“并行”。)
或者也许你的“步骤”不太“正确”?...
SELECT user, organization, project
FROM Users
JOIN Organizations ...
JOIN Projects ...
只需一步即可获得所有用户,以及他们的所有组织和项目。
但是“用户”是否与“项目”相关联?如果不是,那么这是错误的方法。
如果 ORM 没有提供生成类似查询的机制,那么它就是“碍事”。
【讨论】:
没错,单个连接中没有并行性。我说的是多个连接——它们只是躺在那里,不活跃。为什么不使用它们?保存过程中最大的区间、网络边界、返程行程区间,为什么“不值得”? 正如我在上面所说的,您所说的连接问题是我们遇到了笛卡尔乘法,并且数据量激增 - 它包括大量重复。用户数据可以每行重复一次。当然,在基数较低的情况下,这不是问题。它确实很快就会出现问题,尤其是如果我们使用带有状态的关联表。是的,用户与一个项目相关联 - 他参与了一个项目。然后他获得了相关的认证/“证书”。您的最后一部分确实有意义 - 我想我可能会在某些代码部分使用非 ORM。耻辱.. Justin Swanhart 有一个工具包,可以将您的 SQL 转换为并行 SQL。【参考方案2】:您要做的第一件事是检查数据库上实际执行的查询。除非您非常熟悉它,否则我不会假设 SQLAlchemy 正在做您期望的事情。您可以在引擎配置中使用echo=True
或查看一些数据库日志(不知道如何使用 mysql 执行此操作)。
您提到您正在使用不同的加载策略,所以我想您已经阅读了相关文档( http://docs.sqlalchemy.org/en/latest/orm/loading_relationships.html)。对于您正在做的事情,我可能会推荐子查询加载,但这完全取决于您正在处理的行数/列数。不过,根据我的经验,这是一个很好的总体起点。
有一点需要注意,你可能需要这样的东西:
db.query(Thing).options(subqueryload('A').subqueryload('B')).filter(Thing.id==x).first()
使用filter.first
而不是get
,因为如果主对象已经在身份映射中,后一种情况不会根据您的加载策略重新执行查询。
最后,我不知道您的数据 - 但如果没有庞大的数据集,这些数字听起来非常糟糕。检查您是否在所有表上指定了正确的索引。
您可能已经经历了所有这些,但根据您提供的信息,听起来您需要做更多的工作来缩小问题的范围。是 db 架构,还是 SQLA 正在执行的查询?
无论哪种方式,我都会说“不”在不同的连接上运行多个查询。任何这样做的尝试都可能导致返回到您的应用程序的数据不一致,如果您认为现在遇到问题...... :-)
【讨论】:
我肯定会很快深入,确实我会在这里发布更新(并创建一个新的赏金)。 不过,我不清楚您评论的最后一点 - 为什么与进行多个顺序查询相比,它“更加”不一致?在那段时间里,还有更多的事情可以改变。诚然,如果您进行一次查询并“一次性”获得所有内容,那么查询就不存在一致性问题,但话又说回来 - 您会进入笛卡尔乘法结果大小爆炸谷:) 因为数据库为您管理事务,以便数据在您的事务中保持一致。如果您使用多个连接(以获得并发),您将有多个事务,并且每个事务都将在不同的时间查看数据。忘掉那个想法吧——这不是一件已经完成的事情。 无论您的问题是什么(我的猜测是 sqla 正在执行急切加载并且正在运行大量微小查询),多个事务将简直是人间地狱(并且可能不会让任何事情变得更快) *不是急切加载以上是关于加载关系时如何指示SQLAlchemy ORM并行执行多个查询?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 SQLAlchemy ORM 上实现对同一属性的自引用多对多关系?
如何将 Sqlalchemy ORM 查询结果转换为包含关系的单个联接表?