とりあえずの独り言

テーマは特に限定せず、独り言のように

【SQLAlchemy】sessionの作成はどの方法がいいのか?考えてみた

概要

SQLAlchemyでは、sessionの作成方法が複数あって結局どれがいいの?っていうのをいろいろ考えてみた。

ちなみに、Flaskなどのフレームワークを利用している場合はSQLAlchemyをラップしたFlask-SQLAlchemyなどがあるのでここら辺を利用するとsession周りは良しなにやってくれます。

フレームワークを使わないような、ツールやバッチ系などではSQLAlchemy単体で使うことも多いと思うのでそういう人向けです。

参考:
SQLAlchemyのSession生成方法 - Qiita
【PythonのORM】SQLAlchemyで基本的なSQLクエリまとめ - Qiita
Using the Session — SQLAlchemy 1.3 Documentation

一つ目のリンクと結構重なるところが多かったorz
ただ、結論が若干違うのでパk・・・引用しつつ書き残したい。

sessionクラスを使う

from sqlalchemy import create_engine
from sqlalchemy.orm import Session


DATABASE = "sqlite:///localdb.sqlite3"
engine = create_engine(DATABASE)

session1 = Session(
    autocommit=False,
    autoflush=False,
    bind=engine)

session2 = Session(
    autocommit=False,
    autoflush=False,
    bind=engine)

print(session1)
print(session2)
print(session1==session2)

## 結果 ##
# <sqlalchemy.orm.session.Session object at 0x00000214C12E98C8>
# <sqlalchemy.orm.session.Session object at 0x00000214C12E9A48>
# False

一番簡単なやり方。
Sessionを呼ぶたびに、新しいインスタンスが作られる。

ちなみに、Sessionクラスはスレッドセーフではないため、setting.pyに記載して使いまわさないほうがいい。(絶対単一スレッドでしか起動しないなら関係ないが)

session_markerクラスを使う

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker


DATABASE = "sqlite:///localdb.sqlite3"
engine = create_engine(DATABASE)

session = sessionmaker(
    autocommit=False,
    autoflush=False,
    bind=engine
)

session1 = session()

session2 = session()

print(session1)
print(session2)
print(session1==session2)

## 結果 ##
# <sqlalchemy.orm.session.Session object at 0x000001379B0C9788>
# <sqlalchemy.orm.session.Session object at 0x000001379B0C9988>
# False

Sessionクラスを使うときと同様にsession()を呼ぶたびに新しいインスタンスが作成される。 違いは、毎回設定を渡さなくてもいいということぐらい。

上でも書いたように、SessionクラスはスレッドセーフではないのでSessionインスタンスは使いまわさないほうがいい。 だから、sessionmarkerで設定のみsetting.pyに記載して、それぞれの個所でsession()を呼ぶという使い方になるんだろうと思う。

scoped_sessionクラスを使う(オススメ)

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


DATABASE = "sqlite:///localdb.sqlite3"
engine = create_engine(DATABASE)

session = scoped_session(
    sessionmaker(
        autocommit=False,
        autoflush=False,
        bind=engine
    )
)
session1 = session()
session2 = session()
session.remove()
session3 = session()

print(session1)
print(session2)
print(session1==session2)
print(session3)
print(session1==session3)

## 結果 ##
# <sqlalchemy.orm.session.Session object at 0x000002A8E2699848>
# <sqlalchemy.orm.session.Session object at 0x000002A8E2699848>
# True
# <sqlalchemy.orm.session.Session object at 0x000002A8E2699988>
# False

今までの2つと異なる点は、sessionを呼んでも同一のインスタンスを返してくれること。もちろんsession.remove()すれば新しいインスタンスを返してくれるようになる。
また、このsessionインスタンスはスレッドローカルを利用しているためマルチスレッドで実行させたいといったときは、これを利用する。

実際に実装するときは、

  • setting.pyの中にscoped_sessionでsessionクラスを定義する
  • dbパッケージを作る
  • dbパッケージの__init__.pysetting.pyimportする

みたいな感じになるんじゃないだろうか?

session のクローズについて

session.close()メソッドでセッションを閉じれるが、scoped_sessionクラスで作成したインスタンスを使いまわす形になるのでcloseする必要はなさそう。Session()で都度作成するパターンの場合は、使い終わったらcloseは必要だろうけど、その点でもscoped_sessionクラスはよさそう。

補足(auto_flush, auto_commit設定について)

ちなみにauto_flush, auto_commitFalseでいいんじゃないかなー?

Transactions and Connection Management — SQLAlchemy 1.3 Documentation

auto_commitはlegacy modeとか書いてあるし。

ちなみに、flushとcommitでflushしなきゃいけないパターンについては、

【SQLAlchemy】flushとcommitの違い - とりあえずの独り言

で少し書いた。

補足(トランザクションについて)

トランザクションの開始とかは特に、明記する必要もない。auto_commit設定がTrueになっている場合は、明示的にトランザクションを設定する場合もあるらしい。

auto_commitFalseの場合は、コミットした段階で新しいトランザクションが開始される。scoped_sessionで作成したインスタンスは、スレッドローカルを使うのでマルチスレッドで動作しても違うトランザクションになるので並列実行しても問題なさそう。(スレッドローカルとか、マルチスレッドとか苦手でいまいち理解しきれていない・・・)