如何使用小马orm以多对多关系加载数据? - python

这是我的实体:

class Article(db.Entity):
    id = PrimaryKey(int, auto=True)
    creation_time = Required(datetime)
    last_modification_time = Optional(datetime, default=datetime.now)
    title = Required(str)
    contents = Required(str)
    authors = Set('Author')


class Author(db.Entity):
    id = PrimaryKey(int, auto=True)
    first_name = Required(str)
    last_name = Required(str)
    articles = Set(Article)

这是我用来获取一些数据的代码:

return left_join((article, author) for article in entities.Article
                 for author in article.authors).prefetch(entities.Author)[:]

无论我是否使用预取方法,生成的sql总是看起来相同:

SELECT DISTINCT "article"."id", "t-1"."author"
FROM "article" "article"
  LEFT JOIN "article_author" "t-1"
    ON "article"."id" = "t-1"."article"

然后,当我遍历结果时,pony发出了另一个查询(查询):

SELECT "id", "creation_time", "last_modification_time", "title", "contents"
FROM "article"
WHERE "id" = %(p1)s

SELECT "id", "first_name", "last_name"
FROM "author"
WHERE "id" IN (%(p1)s, %(p2)s)

对我来说,理想的行为是,如果orm仅发出一个查询来加载所有需要的数据。那么我该如何实现呢?

python大神给出的解决方案

PonyORM的作者在这里。我们不希望仅使用一个查询来加载所有这些对象,因为这样效率低下。

使用单个查询加载多对多关系的唯一好处是减少了往返数据库的次数。但是,如果我们将三个查询替换为一个,这并不是一个重大改进。当数据库服务器位于应用程序服务器附近时,与使用Python处理结果数据相比,这些往返实际上非常快。

另一方面,当使用同一查询加载多对多关系的两端时,不可避免的是,同一对象的数据将在多行中一遍又一遍地重复。这有很多缺点:

与没有重复信息传输的情况相比,从数据库传输的数据大小变得更大。在您的示例中,如果您有十篇文章,并且每篇文章都是由三位作者撰写的,则单个查询将返回三十行,其中大字段(如article.contents)重复多次。单独的查询将传输可能的最小数据量,大小差异可能很容易达到一个数量级,具体取决于特定的多对多关系。
数据库服务器通常使用C之类的编译语言编写,并且运行速度非常快。网络层也是如此。但是Python代码是经过解释的,Python代码所消耗的时间(与某些观点相反)通常比在数据库中花费的时间长得多。您可以看到由SQLAlchemy作者Mike Bayer执行的profiling tests之后得出结论:

我似乎经常遇到的一个很大的误解是,与数据库的通信占用了以数据库为中心的Python应用程序所花费的大部分时间。这在C甚至Java之类的编译语言中可能是一种常识,但在Python中通常不是。与此类系统相比,Python非常慢(...)数据库驱动程序(DBAPI)是用纯Python还是用C编写的,都将导致大量额外的Python级开销。仅对于DBAPI,这可能会慢一个数量级。

当使用相同的查询加载多对多关系的所有数据并且在多行中重复相同的数据时,有必要在Python中解析所有这些重复的数据,以丢弃其中的大部分。由于Python是过程中最慢的部分,因此这种“优化”可能会导致性能下降。

为了支持我的话,我可以指向Django ORM。该ORM有两种可用于查询优化的方法。第一个名为select_related的对象在单个查询中加载所有相关对象,而最近添加的名为prefetch_related的方法以默认情况下Pony的方式加载对象。根据Django用户的说法,第二种方法much faster起作用:

在某些情况下,我们发现速度提高了30%。

需要数据库执行连接,这会消耗数据库服务器的宝贵资源。

尽管Python代码是处理单个请求时最慢的部分,但数据库服务器CPU时间是所有并行请求都使用的共享资源。您可以通过在不同的服务器上启动多个Python进程来轻松扩展Python代码,但是扩展数据库要困难得多。因此,在高负载的应用程序中,最好将有用的工作从数据库服务器卸载到应用程序服务器,因此可以由多个应用程序服务器并行完成此工作。

当数据库执行连接时,它需要花费更多的时间来进行连接。但是对于Pony而言,是否进行数据库联接无关紧要,因为在任何情况下,对象都将在ORM身份映射内相互链接。因此,数据库在执行联接时所做的工作只是浪费了数据库时间。另一方面,使用身份映射模式Pony可以同等快速地链接对象,而不管对象是否在同一数据库行中提供。

回到往返次数,Pony具有专门的机制来消除“ N + 1查询”问题。当ORM发送数百个非常相似的查询时,每个查询都会从数据库中加载独立的对象,因此会出现“ N + 1查询”反模式。许多ORM都遭受这个问题的困扰。但是Pony可以检测到它,并用单个查询替换重复的N个查询,该查询一次加载所有必需的对象。此机制非常有效,可以大大减少往返次数。但是,当我们谈论加载多对多关系时,这里没有N个查询,只有三个查询在单独执行时效率更高,因此尝试执行单个查询没有任何好处。

总之,我要说的是,对于我们(小马ORM开发人员)来说,ORM的性能非常重要。因此,我们不想在单个查询中实现加载多对多关系,因为它肯定比我们当前的解决方案要慢。

因此,要回答您的问题,您不能在单个查询中加载多对多关系的双方。我认为这是一件好事。