の違いは何ですか?
for row in session.Query(Model1):
pass
と
for row in session.Query(Model1).all():
pass
最初のイテレータは単一のクエリで DB を攻撃し、後者の「熱心な」クエリはすべてをリストとしてクエリします (range(x) と xrange(x) のように) ?
の違いは何ですか?
for row in session.Query(Model1):
pass
と
for row in session.Query(Model1).all():
pass
最初のイテレータは単一のクエリで DB を攻撃し、後者の「熱心な」クエリはすべてをリストとしてクエリします (range(x) と xrange(x) のように) ?
いいえ、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)
...ここself
で、クエリオブジェクトは反復可能です。つまり、__iter__
メソッドがあります。
したがって、論理的には、DB トラフィックに関しては 2 つの方法はまったく同じです。query.__iter__()
両方とも、行反復子を取得するために呼び出し、それをnext()
処理することになります。
実際の違いは、前者はデータが到着するとすぐに行を提供し始め、DB の結果セットを「ストリーミング」して、メモリの使用と待ち時間を少なくできることです。現在のすべてのエンジン実装がそうしていると断言することはできません (そうなることを願っています!)。いずれにせよ、後者のバージョンでは、正当な理由もなく、その効率が妨げられます。
実際、受け入れられた応答は真実ではありません(または、少なくとも真実であるとしても、もはやそうではありません)。具体的には、次のステートメントは誤りです。
(1) 違いは、session.Query(Model1) の行の場合、それがあなたに与えようとしているときに各行で ORM を実行するのに対して、session.Query(Model1).all() の行の場合は、 ORM は、すべての行を処理してから、それらを提供し始めます。
SQLAlchemy は、2 つのオプションのどちらを使用するかに関係なく、常にすべての行を ORM マップします。これは、これらの行内のソース コードで確認できます。このloading.instances
メソッドは確かにジェネレーターを返しますが、既にマップされている ORM インスタンスの 1 つです。これは、実際のジェネレーターのループ コードで確認できます。
for row in rows: # ``rows`` here are already ORM mapped rows
yield row
そのため、ジェネレーターの最初の実行が終了し、インスタンスが生成されるまでに、すべてのインスタンスが ORM マップされています。(以下(1)に例示)
(2) 実際の違いは、前者はデータが到着するとすぐに行の提供を開始し、DB 結果セットを「ストリーミング」して、メモリの使用と待ち時間を少なくできることです。
上記で説明したように、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などを使用する必要がありますが、これは 1 つずつのシナリオではなく、インスタンスのバッチごとに ORM マッピングを実行します。
マイケル・ベイヤーからの説明を指している、実際に適切であり、簡単に見落とされる可能性のある唯一のコメントに注意する価値があります(したがって、この回答を書いています)。