Mar*_* M. 3 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)
Run Code Online (Sandbox Code Playgroud)
这是我用来获取一些数据的代码:
return left_join((article, author) for article in entities.Article
for author in article.authors).prefetch(entities.Author)[:]
Run Code Online (Sandbox Code Playgroud)
无论我是否使用 prefetch 方法,生成的 sql 看起来总是一样的:
SELECT DISTINCT "article"."id", "t-1"."author"
FROM "article" "article"
LEFT JOIN "article_author" "t-1"
ON "article"."id" = "t-1"."article"
Run Code Online (Sandbox Code Playgroud)
然后当我迭代结果时,小马正在发出另一个查询(查询):
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)
Run Code Online (Sandbox Code Playgroud)
我想要的行为是如果 orm 只发出一个查询来加载所有需要的数据。那么我该如何实现呢?
PonyORM 的作者在这里。我们不想只使用一个查询来加载所有这些对象,因为这是低效的。
使用单个查询加载多对多关系的唯一好处是减少到数据库的往返次数。但是如果我们将三个查询替换为一个,这并不是一个重大改进。当您的数据库服务器位于应用程序服务器附近时,与在 Python 中处理结果数据相比,这些往返实际上非常快。
另一方面,当多对多关系的双方都使用相同的查询加载时,不可避免地会在多行中一遍又一遍地重复相同对象的数据。这有很多缺点:
与不传输重复信息的情况相比,从数据库传输的数据量变得更大。在您的示例中,如果您有 10 篇文章,每篇文章由三位作者撰写,则单个查询将返回 30 行,其中包含article.contents多次重复的大字段。单独的查询将传输尽可能少的数据,大小差异很容易达到一个数量级,具体取决于特定的多对多关系。
数据库服务器通常用 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%。
数据库需要执行连接,这会消耗数据库服务器的宝贵资源。
虽然 Python 代码是处理单个请求时最慢的部分,但数据库服务器 CPU 时间是所有并行请求使用的共享资源。您可以通过在不同服务器上启动多个 Python 进程来轻松扩展 Python 代码,但扩展数据库要困难得多。正因为如此,在高负载的应用程序中,最好将有用的工作从数据库服务器卸载到应用程序服务器,这样这项工作可以由多个应用程序服务器并行完成。
当数据库执行连接时,它需要花费额外的时间来完成。但是对于 Pony 而言,数据库是否进行连接无关紧要,因为在任何情况下,对象都将在 ORM 身份映射中互连。所以数据库在执行join时所做的工作只是浪费数据库时间。另一方面,使用身份映射模式 Pony 可以同样快速地链接对象,无论它们是否在同一数据库行中提供。
回到往返次数,Pony 有专门的机制来消除“N+1 查询”问题。“N+1 查询”反模式在 ORM 发送数百个非常相似的查询时出现,每个查询从数据库加载单独的对象。许多 ORM 都存在这个问题。但是 Pony 可以检测到它并用一次加载所有必需对象的单个查询替换重复的 N 个查询。这种机制非常高效,可以大大减少往返次数。但是当我们谈到加载多对多关系时,这里没有 N 个查询,只有三个查询在单独执行时效率更高,因此尝试执行单个查询没有任何好处。
总而言之,我需要说 ORM 性能对我们 Pony ORM 开发人员来说非常重要。正因为如此,我们不想在单个查询中实现加载多对多关系,因为它肯定会比我们当前的解决方案慢。
因此,要回答您的问题,您不能在单个查询中加载多对多关系的双方。我认为这是一件好事。