加载关系时如何指示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、他的OrganizationsSchools等,都可以通过单个查询完成(在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 关系的结果?

如何在 SQLAlchemy ORM 上实现对同一属性的自引用多对多关系?

如何将 Sqlalchemy ORM 查询结果转换为包含关系的单个联接表?

西游之路——python全栈——ORM之SQLAlchemy外键与relationship的关系

ORM SQLAlchemy 表于表的关系