SQLAlchemy:セッションの作成と再利用


98

ただ、簡単な質問:SQLAlchemyのは語る呼び出しsessionmaker()た後が、結果として呼び出すSession()クラスにあなたがあなたのDBに話をする必要があるたびに。私にとっては、2番目に最初session.add(x)または類似したことを実行することを意味します。

from project import Session
session = Session()

これまで私が行ったことはsession = Session()、モデルで一度呼び出しを行ってから、常に同じセッションをアプリケーションの任意の場所にインポートすることでした。これはWebアプリケーションであるため、通常は同じ意味になります(1つのビューが実行されるため)。

しかし、違いはどこですか?機能が完了するまでデータベースセッションで1つのセッションを使用し、次にDBと通信するときに新しいセッションを作成することに対して、常に1つのセッションを使用することの欠点は何ですか?

複数のスレッドを使用する場合、各スレッドが独自のセッションを取得する必要があることがわかります。しかしscoped_session()、を使用して、問題が存在しないことをすでに確認していますか?

私の仮定のいずれかが間違っているかどうかを明確にしてください。

回答:


223

sessionmaker()ファクトリSessionです。新しいオブジェクトを作成するための構成オプションを1か所に配置することを奨励するために存在します。これはオプションです。冗長で冗長な場合を除いてSession(bind=engine, expire_on_commit=False)、新しいが必要なときにいつでも簡単に呼び出すことができSession、それぞれが新しい冗長性の問題に対処する小規模な「ヘルパー」の急増を阻止したかったので、そしてより混乱する方法。

したがってsessionmaker()Session必要なときにオブジェクトを作成するためのツールにすぎません。

次の部分。問題は、Session()さまざまな時点で新しく作成することと、1つだけを使用することの違いは何だと思います。答えは、それほどではありません。 Session入れたすべてのオブジェクトのコンテナであり、開いているトランザクションも追跡します。rollback()またはを呼び出した時点でcommit()トランザクションは終了し、SessionSQLを再度発行するように要求されるまで、データベースへの接続はありません。マップされたオブジェクトへのリンクは、オブジェクトが保留中の変更をSession除去している限り、弱い参照です。そのため、アプリケーションがマップされたオブジェクトへのすべての参照を失うと、それ自体が空になり、新しい状態に戻ります。デフォルトのままにしておくと"expire_on_commit"設定すると、コミット後にすべてのオブジェクトが期限切れになります。それがSession5〜20分間滞留し、次にそれを使用するときにすべての種類のものがデータベースで変更された場合、それらがメモリに座っていたとしても、次にそれらのオブジェクトにアクセスするときにすべての新しい状態がロードされます。 20分間。

Webアプリケーションでは、通常Session、同じリクエストを何度も使用するのではなく、リクエストごとにまったく新しいものを作成してみませんか。これにより、新しいリクエストが確実に「クリーン」に開始されます。以前のリクエストの一部のオブジェクトがまだガベージコレクションされておらず、オフ"expire_on_commit"になっている場合、以前のリクエストの一部の状態がまだ残っており、その状態はかなり古い可能性があります。expire_on_commit電源を入れたままにしておき、間違いなく電話commit()またはrollback()リクエストの終わりに注意を払うのであればSession問題ありませんが、まったく新しいから始めるのであれば、問題なく開始できるという質問すらありません。つまり、各リクエストを新しいものから始めるというアイデアSessionexpire_on_commitこのフラグcommit()は、一連の操作の途中で呼び出す操作に対して追加のSQLを大量に発生させる可能性があるため、新しく開始することを確認し、ほとんどの使用をオプションにするための本当に最も簡単な方法です。これがあなたの質問に答えるかどうかわかりません。

次のラウンドはあなたがスレッドについて言及するものです。アプリがマルチスレッド化されている場合Session、使用中のものをローカルのものにすることをお勧めします。 scoped_session()デフォルトでは、現在のスレッドに対してローカルになります。Webアプリでは、リクエストに対してローカルなほうが実際にはさらに優れています。Flask-SQLAlchemyは実際にカスタム「スコープ関数」を送信してscoped_session()、リクエストスコープのセッションを取得します。平均的なPyramidアプリケーションは、セッションを「リクエスト」レジストリに貼り付けます。このようなスキームを使用する場合、「リクエストに応じて新しいセッションを作成する」という考え方は、物事をまっすぐに保つ最も簡単な方法のように見えます。


17
わあ、これでSQLAlchemyの部分に関する私のすべての質問に答え、FlaskとPyramidに関する情報も追加されます!ボーナスを追加:開発者が回答;)複数回投票できればいいのに。どうもありがとうございました!
javex

可能であれば、1つの明確化:expire_on_commitは「大量の余分なSQLが発生する可能性がある」と言います...詳細を教えてください。expire_on_commitは、RAMで発生することだけを考慮し、データベースで発生することは考慮しないと考えました。
Veky

3
expire_on_commitを使用すると、同じセッションを再度再利用した場合にSQLが増加し、一部のオブジェクトがそのセッション内で依然としてハングしている場合、それらにアクセスすると、それぞれが個別に更新されるため、オブジェクトごとに単一行のSELECTが取得されます。新しいトランザクションに関するそれらの状態。
zzzeek 2015

1
こんにちは、@ zzzeek。すばらしい答えをありがとう。私はPythonが非常に新しいので、明確にしたいことがいくつかあります。1)Session()メソッドを呼び出して新しい「セッション」を作成すると、SQLトランザクションが作成され、セッションがコミット/ロールバックされるまでトランザクションが開かれるので、正しく理解できますか。 ?2)session()はある種の接続プールを使用しますか、それとも毎回sqlへの新しい接続を作成しますか?
Alex Gurskiy、2018

27

優れたzzzeekの答えに加えて、使い捨ての自己完結型セッションをすばやく作成するための簡単なレシピを次に示します。

from contextlib import contextmanager

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker

@contextmanager
def db_session(db_url):
    """ Creates a context with an open SQLAlchemy session.
    """
    engine = create_engine(db_url, convert_unicode=True)
    connection = engine.connect()
    db_session = scoped_session(sessionmaker(autocommit=False, autoflush=True, bind=engine))
    yield db_session
    db_session.close()
    connection.close()

使用法:

from mymodels import Foo

with db_session("sqlite://") as db:
    foos = db.query(Foo).all()

3
新しいセッションだけでなく、新しい接続も作成する理由はありますか?
danqing 2017

実際にはそうではありません。これはメカニズムを示す簡単な例ですが、私がこのアプローチを最も頻繁に使用するテストですべてを新しく作成することは理にかなっています。オプションの引数として接続を使用すると、この関数を簡単に拡張できます。
Berislav Lopac 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.