web-dev-qa-db-ja.com

SQLAlchemy:ORMを使用して巨大なテーブルをスキャンしますか?

私は現在、SQLAlchemyを少し遊んでいます。

テストのために、SHA1ハッシュ(重複を削除するために:-)でインデックス付けされた、画像アーカイブを含む巨大なテーブルを作成しました。驚くほど高速でした...

楽しみのために、結果のSQLiteデータベースに対してselect *と同等のことを行いました。

session = Session()
for p in session.query(Picture):
    print(p)

ハッシュがスクロールするのを期待していたが、代わりにディスクをスキャンし続けた。同時に、メモリ使用量は急増し、数秒後に1GBに達しました。これは、SQLAlchemyのアイデンティティマップ機能に由来するようです。これは、弱い参照のみを保持していると思っていました。

誰かがこれを私に説明できますか?ハッシュが書き出された後、各画像pが収集されると思いました!?

41
Bluehorn

さて、私はこれを自分で行う方法を見つけました。コードを

session = Session()
for p in session.query(Picture).yield_per(5):
    print(p)

一度に5つの画像のみをロードします。クエリはデフォルトで一度にすべての行をロードするようです。ただし、その方法に関する免責事項はまだわかりません。 SQLAlchemy docs からの引用

警告:この方法は注意して使用してください。同じインスタンスが複数の行のバッチに存在する場合、属性に対するエンドユーザーの変更は上書きされます。特に、熱心に読み込まれたコレクション(つまり、lazy = False)でこの設定を使用することは通常不可能です。これらのコレクションは、後続の結果バッチで発生したときに、新しい読み込みのためにクリアされるためです。

したがって、yield_perは実際には 正しい方法(TM) ORMを使用しているときに大量のSQLデータをスキャンするには、いつそれを使用しても安全ですか?

51
Bluehorn

これが私がこの状況で通常行うことです:

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行を返すのに、結果全体(実際の行フェッチのオーバーヘッドを差し引いたもの)を返すのとほぼ同じくらい時間がかかることに気づきました。

32
zzzeek

画像を延期して、アクセス時にのみ取得することができます。クエリごとに行うことができます。お気に入り

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

どちらの方法でも、属性にアクセスしたときにのみ画像が読み込まれるようになります。

7
David Raznick