Sqlalchemy - for循环中查询和查询之间的区别

Tom*_*Tom 68 sqlalchemy

我想问一下两者之间的区别

for row in session.Query(Model1):
    pass
Run Code Online (Sandbox Code Playgroud)

for row in session.Query(Model1).all():
    pass
Run Code Online (Sandbox Code Playgroud)

是第一个以某种方式用单个查询轰炸你的数据库的迭代器,而后者"急切地"将整个事件作为一个列表(如range(x)vs xrange(x))查询?

Gun*_*iem 94

不,DB流量没有区别.不同之处在于for row in session.Query(Model1)ORM在它将要发送给你的每一行时都会工作,而for row in session.Query(Model1).all()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)
Run Code Online (Sandbox Code Playgroud)

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

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

实际的区别在于,前者可以在数据到达后立即开始为您提供行,将数据库结果集"流式传输"给您,减少内存使用和延迟.我无法确定所有当前的引擎实现都是这样做的(我希望他们这样做!).在任何情况下,后一版本都没有充分的理由阻止了这种效率.

  • [这篇文章](http://www.mail-archive.com/sqlalchemy@googlegroups.com/msg12443.html)介绍了实施细节.简短回答:`__iter__`*确实*预取所有结果(有充分理由),但如果您知道自己在做什么,可以更改此行为. (11认同)
  • 嗯,它比`list(q)`稍微更具可读性.但行为和表现是一样的.并且迭代`q.all()`至少_potentially_慢开始,并且更多的内存饥饿,而不是迭代`q`. (2认同)

Ger*_*obi 14

实际上,接受的响应不是真的(或者至少不再是真的),特别是以下陈述是错误的:

(1) 不同之处仅在于,对于 session.Query(Model1) 中的 row,当它即将提供给您时,会在每一行上进行 ORM 工作,而对于 session.Query(Model1).all() 中的 row,它会执行 ORM ORM 在所有行上工作,然后才开始将它们提供给您。

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

for row in rows: #  ``rows`` here are already ORM mapped rows
    yield row
Run Code Online (Sandbox Code Playgroud)

因此,当生成器的第一次运行完成并产生一个实例时,所有实例都已被 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
"""
Run Code Online (Sandbox Code Playgroud)

为了在处理大型结果集时进行更精细的控制,例如必须使用诸如yield_per 之类的东西,但这仍然不是一个一个的案例场景,而是通过批量实例执行 ORM 映射。

值得一提的是,唯一确实切中要害且很容易被忽略的评论(因此撰写了此答案),指出了迈克尔·拜耳的解释

  • 感谢您的撰写 - 您有机会用永久链接替换链接的代码行吗?L79 现在是一条评论,可能不是您最初指向的内容 (2认同)