SQLAlchemy-for循环中query和query.all之间的区别


74

我想问一下两者之间有什么区别

for row in session.Query(Model1):
    pass

for row in session.Query(Model1).all():
    pass

是第一个以某种方式用单个查询轰炸数据库的迭代器,而后一个“急切”迭代器以列表的形式查询整个对象(例如range(x)vs xrange(x))?

Answers:


99

不,数据库流量没有差异。区别在于,for row in session.Query(Model1)在将ORM分配给您时,它会在每行上工作,而for row in session.Query(Model1).all()在将ORM分配给您之前,ORM在所有行上都工作。

请注意,这q.all()只是用于的糖list(q),即将生成器产生的所有内容收集到一个列表中。下面是源代码的话,在Query类(找到def all在链接源):

def all(self):
    """Return the results represented by this ``Query`` as a list.

    This results in an execution of the underlying query.

    """
    return list(self)

...其中self,查询对象是可迭代的,即具有__iter__方法。

因此,从逻辑上讲,这两种方式在数据库流量方面完全相同;两者最终都会调用query.__iter__()以获取行迭代器,并逐步next()通过它。

实际的区别在于,前者可以在数据到达后立即开始为您提供行,从而将DB结果集“流化”给您,从而减少了内存使用和延迟。我不能肯定地说所有当前的引擎实现都能做到(我希望他们能做到!)。无论如何,后一个版本都没有充分理由阻止这种效率。


13
这篇文章阐明了实现细节。简短答案:__iter__ 确实预取了所有结果(有充分的理由),但是如果您知道自己在做什么,则可以更改此行为。
2013年

因此,使用没有优势q.all()
dshgna '16

2
好吧,list(q)我猜比它更具可读性。但是行为和性能是相同的。与迭代相比,迭代q.all()至少可能比启动慢,启动起来也更耗费内存q
Gunnlaugur Briem

数据库命中数可能相同,但是请记住,all它将把整个结果集加载到内存中。这可能是千兆字节的数据。
卢卡斯·巴托

12

实际上,接受的响应是不正确的(或者至少不再是正确的),特别是以下语句是错误的:

(1)区别仅在于session.Query(Model1)中的行要在将其提供给您时,ORM在每一行上工作,而session.Query(Model1).all()中的行则进行在开始将它们提供给您之前,ORM会在所有行上工作。

无论您选择使用2个选项中的哪一个,SQLAlchemy都会始终对所有行进行ORM映射。在这些行的源代码中可以看到这一点;该loading.instances方法的确会返回一个生成器,但返回一个已经映射的ORM实例之一。您可以在实际的生成器循环代码中确认这一点:

for row in rows: #  ``rows`` here are already ORM mapped rows
    yield row

因此,在生成器的第一次运行完成并生成实例之前,所有实例都已进行ORM映射。(以下示例(1))

(2)实际的区别在于,前者可以在数据到达后立即开始为您提供行,从而将DB结果集“流化”给您,从而减少了内存使用和延迟。

正如上面所解释的,这也是错误的,因为从数据库检索到的所有数据都将在完成任何屈服之前进行处理/映射。

这些评论也具有误导性。特别:

数据库命中可能是相同的,但请记住,所有操作都会将整个结果集加载到内存中。这可能是千兆字节的数据

无论是否使用,都会加载整个结果集.all()。在这条线上清晰可见。这也非常容易测试/验证。

我从陈述的2个选项中可以看到的唯一区别是,使用时,.all您将在结果中循环两次(如果您要处理所有实例/行),因为首先要调用生成器来迭代/耗尽生成器,list(self)以将其转换为一个列表。

由于SQLAlchemy代码不容易理解,因此我写了一个简短的代码段来举例说明所有这一切:

class Query:
    def all(self):
        return list(self)

    def instances(self, rows_to_fetch=5):
        """ORM instance generator"""
        mapped_rows = []
        for i in range(rows_to_fetch):
            # ORM mapping work here as in lines 81-88 from loading.instances
            mapped_rows.append(i)
        print("orm work finished for all rows")
        for row in mapped_rows:  # same as ``yield from mapped_rows``
            print("yield row")
            yield row

    def __iter__(self):
        return self.instances()


query = Query()
print("(1) Generator scenario:")
print("First item of generator: ", next(iter(query)))

print("\n(2) List scenario:")
print("First item of list: ", query.all()[0])

"""
RESULTS:
--------
(1) Generator scenario:
orm work finished for all rows
yield row
First item of generator:  0

(2) List scenario:
orm work finished for all rows
yield row
yield row
yield row
yield row
yield row
First item of list:  0
"""

为了在处理大型结果集时拥有更好的控制,例如,必须使用诸如yield_per之类的方法,但这仍然不是一个一例的场景,而是要按批次执行实例的ORM映射。

值得一提的是,仅有的一点确实是针对的,并且很容易被忽略(因此写了这个答案),指向迈克尔·拜耳的解释

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.