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

Python 如何使用pony orm以多对多关系加载数据?,python,sql,ponyorm,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 = S

以下是我的实体:

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发出另一个查询(查询):

我希望的行为是,orm只发出一个查询,加载所有需要的数据。那么我该如何做到这一点呢?

这应该是可行的

python
从pony.orm导入选择
如果article.authors==authors.id,则为文章中的文章选择((文章,作者)

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

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

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

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

  • 数据库服务器通常用C之类的编译语言编写,运行速度非常快。网络层也是如此。但是Python代码是经过解释的,Python代码所花费的时间(与某些观点相反)通常要比在数据库中花费的时间多得多。你可以看到,这是由SQLAlchemy的作者Mike Bayer完成的,之后他得出了结论:

    我经常遇到的一个很大的误解是,与数据库的通信占用了以数据库为中心的Python应用程序的大部分时间。这可能是C语言甚至Java等编译语言中的一种常见智慧,但在Python中通常不是。Python的速度非常慢,与这样的系统相比(…)无论是用纯Python还是用C编写数据库驱动程序(DBAPI),都会产生大量额外的Python级开销。仅对于DBAPI而言,这可能要慢一个数量级

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

    为了支持我的话,我可以指出Django ORM。此ORM有两种方法可用于查询优化。第一个方法称为加载单个查询中的所有相关对象,而最近添加的方法称为加载对象,默认情况下,加载方式与Pony相同。据Django用户称,第二种方法有效:

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

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

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

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

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

    总之,我需要说,ORM性能对我们这些小型ORM开发人员来说是非常重要的。正因为如此,我们不希望在一个查询中实现加载多对多关系,因为它肯定会比我们当前的解决方案慢


    所以,为了回答您的问题,您不能在一个查询中同时加载多对多关系的两侧。我认为这是件好事。

    试过了。你检查过生成的sql了吗?这是一个很好的解释,但是我仍然有
    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)