私は現在、SQLAlchemyを少し遊んでいます。
テストのために、SHA1ハッシュ(重複を削除するために:-)でインデックス付けされた、画像アーカイブを含む巨大なテーブルを作成しました。驚くほど高速でした...
楽しみのために、結果のSQLiteデータベースに対してselect *
と同等のことを行いました。
session = Session()
for p in session.query(Picture):
print(p)
ハッシュがスクロールするのを期待していたが、代わりにディスクをスキャンし続けた。同時に、メモリ使用量は急増し、数秒後に1GBに達しました。これは、SQLAlchemyのアイデンティティマップ機能に由来するようです。これは、弱い参照のみを保持していると思っていました。
誰かがこれを私に説明できますか?ハッシュが書き出された後、各画像pが収集されると思いました!?
さて、私はこれを自分で行う方法を見つけました。コードを
session = Session()
for p in session.query(Picture).yield_per(5):
print(p)
一度に5つの画像のみをロードします。クエリはデフォルトで一度にすべての行をロードするようです。ただし、その方法に関する免責事項はまだわかりません。 SQLAlchemy docs からの引用
警告:この方法は注意して使用してください。同じインスタンスが複数の行のバッチに存在する場合、属性に対するエンドユーザーの変更は上書きされます。特に、熱心に読み込まれたコレクション(つまり、lazy = False)でこの設定を使用することは通常不可能です。これらのコレクションは、後続の結果バッチで発生したときに、新しい読み込みのためにクリアされるためです。
したがって、yield_per
は実際には 正しい方法(TM) ORMを使用しているときに大量のSQLデータをスキャンするには、いつそれを使用しても安全ですか?
これが私がこの状況で通常行うことです:
def page_query(q):
offset = 0
while True:
r = False
for elem in q.limit(1000).offset(offset):
r = True
yield elem
offset += 1000
if not r:
break
for item in page_query(Session.query(Picture)):
print item
これにより、DBAPIが行うさまざまなバッファリング(psycopg2やMySQLdbなど)も回避されます。クエリに明示的なJOINがある場合でも、適切に使用する必要があります。ただし、積極的に読み込まれたコレクションは、実際のLIMIT/OFFSETが指定されたサブクエリに適用されるため、完全に読み込まれることが保証されます。
Postgresqlは大きな結果セットの最後の100行を返すのに、結果全体(実際の行フェッチのオーバーヘッドを差し引いたもの)を返すのとほぼ同じくらい時間がかかることに気づきました。
画像を延期して、アクセス時にのみ取得することができます。クエリごとに行うことができます。お気に入り
session = Session()
for p in session.query(Picture).options(sqlalchemy.orm.defer("picture")):
print(p)
またはマッパーでそれを行うことができます
mapper(Picture, pictures, properties={
'picture': deferred(pictures.c.picture)
})
方法はドキュメントに記載されています here
どちらの方法でも、属性にアクセスしたときにのみ画像が読み込まれるようになります。