3

这是我的实体:

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"

然后当我迭代结果时,小马正在发出另一个查询(查询):

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 只发出一个查询来加载所有需要的数据。那么我该如何实现呢?

4

2 回答 2

5

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

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

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

  1. 与没有重复信息传输的情况相比,从数据库传输的数据量变得更大。在您的示例中,如果您有十篇文章,每篇文章由三位作者撰写,则单个查询将返回三十行,其中包含article.contents多次重复的大字段。单独的查询将传输尽可能少的数据,大小的差异可能很容易达到一个数量级,具体取决于特定的多对多关系。

  2. 数据库服务器通常用 C 等编译语言编写,运行速度非常快。网络层也是如此。但是 Python 代码是被解释的,Python 代码消耗的时间(与某些观点相反)通常比在数据库中花费的时间多得多。您可以看到SQLAlchemy 作者 Mike Bayer 执行的分析测试,之后他得出结论:

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

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

    作为对我的话的支持,我可以指出 Django ORM。这个 ORM 有两种方法可用于查询优化。第一个称为select_related在单个查询中加载所有相关对象,而最近添加的称为prefetch_related的方法以 Pony 默认执行的方式加载对象。根据 Django 用户的说法,第二种方法工作得更快

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

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

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

    当数据库执行连接时,它需要花费额外的时间来执行它。但是对于 Pony 来说,数据库是否加入是无关紧要的,因为在任何情况下,一个对象都会在 ORM 身份映射中相互链接。所以数据库在执行连接时所做的工作只是浪费数据库时间。另一方面,使用身份映射模式 Pony 可以同样快速地链接对象,无论它们是否在同一数据库行中提供。

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

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

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

于 2015-11-26T13:01:40.457 回答
0

这应该工作

python from pony.orm import select select((article, author) for article in Article if Article.authors == Authors.id)

于 2015-04-24T19:43:01.140 回答