如何使用pony orm以多对多关系加载数据?

时间:2015-03-20 18:59:02

标签: python sql ponyorm

以下是我的实体:

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只发出一个可以加载所需数据的查询,那么我想要的行为就是这样。那么我该如何实现呢?

2 个答案:

答案 0 :(得分:4)

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

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

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

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

  2. 数据库服务器通常用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%。

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

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

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

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

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

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

答案 1 :(得分:0)

这应该有效

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