Python | DB・SQL:セッション

Python
スポンサーリンク

セッションって何?まずはざっくりイメージ

「セッション」は、データベースとやり取りするときの「ひとまとまりの会話」のようなものです。
Python から見ると、

あるタイミングで DB との“窓口”を開く
その窓口を通して SQL(または ORM)で操作をする
終わったら窓口を閉じる

この「開いているあいだ」が 1 セッション、というイメージです。

特に ORM(SQLAlchemy など)では、Session というクラスが出てきて、
「トランザクションの単位」「オブジェクトと DB の橋渡し役」として、とても重要な役割を持ちます。

ここでは、まず素の DB 接続のセッション感覚から入り、
そのあと SQLAlchemy の Session をしっかり深掘りしていきます。


素の DB 接続での「セッション」の感覚

sqlite3 での接続とカーソルの流れ

Python 標準の sqlite3 を例にすると、基本の流れはこうです。

import sqlite3

conn = sqlite3.connect("app.db")   # ここで「接続」を開く
cur = conn.cursor()                # SQL を投げるためのカーソルを作る

cur.execute(
    """
    CREATE TABLE IF NOT EXISTS users (
        id     INTEGER PRIMARY KEY AUTOINCREMENT,
        name   TEXT NOT NULL,
        email  TEXT UNIQUE NOT NULL
    )
    """
)

conn.commit()                      # 変更を確定
conn.close()                       # 接続を閉じる
Python

このとき、connect してから close するまでの間が、
ざっくり「DB とのセッション」と考えられます。

この一連の流れの中で、
複数の SQL を実行したり、トランザクションをまとめたりします。

トランザクションとの関係

例えば、複数の操作をまとめて 1 回のトランザクションにしたい場合は、
同じ接続(同じセッション)の中で行います。

conn = sqlite3.connect("app.db")
cur = conn.cursor()

try:
    cur.execute("BEGIN")

    cur.execute(
        "INSERT INTO users (name, email) VALUES (?, ?)",
        ("Taro", "taro@example.com"),
    )
    cur.execute(
        "INSERT INTO users (name, email) VALUES (?, ?)",
        ("Hanako", "hanako@example.com"),
    )

    conn.commit()
except Exception:
    conn.rollback()
    raise
finally:
    conn.close()
Python

ここでは、connect から close までが「接続としてのセッション」であり、
その中で BEGIN から COMMIT / ROLLBACK までが「トランザクション」です。

セッションは「DB との会話の枠」、
トランザクションは「その中の一連の処理の枠」、
というイメージを持つと整理しやすくなります。


SQLAlchemy の Session をしっかり理解する

Engine と Session の役割の違い

SQLAlchemy では、まず Engine を作ります。

from sqlalchemy import create_engine

engine = create_engine("sqlite:///example.db", echo=True)
Python

Engine は、「どの DB にどう接続するか」という設定と、
実際の接続プール(コネクションプール)を持っている存在です。

一方 Session は、「その Engine を使って、実際に ORM モデルを通して DB を操作する窓口」です。

from sqlalchemy.orm import sessionmaker

SessionLocal = sessionmaker(bind=engine)
session = SessionLocal()
Python

Engine は「物理的な接続の管理者」、
Session は「論理的な会話(トランザクション+オブジェクト管理)の単位」、
と分けて考えるとスッキリします。

Session がやっていること(重要)

SQLAlchemy の Session は、主に次のようなことをしています。

どのオブジェクトが新規追加されたか(INSERT 対象か)を覚えている
どのオブジェクトが変更されたか(UPDATE 対象か)を覚えている
どのオブジェクトが削除されたか(DELETE 対象か)を覚えている
クエリを投げて、結果を ORM モデルのインスタンスとして返す
トランザクションを開始し、commit / rollback を管理する

つまり Session は、「ORM の世界」と「DB の世界」の橋渡し役であり、
かつ「トランザクションの単位」でもあります。


Session を使った基本的な CRUD の流れ

セッションを開く・閉じる

まずは Session を作って、終わったら閉じる、という基本形です。

session = SessionLocal()

# ここで DB 操作をする

session.close()
Python

実務では、例外処理も含めて with を使うことも多いですが、
最初はこの「開く→使う→閉じる」の流れを意識するだけで十分です。

INSERT:オブジェクトを追加して commit

User モデルを例にします。

from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String, nullable=False)
    email = Column(String, unique=True, nullable=False)
Python

このモデルを使って、Session 経由で INSERT します。

session = SessionLocal()

new_user = User(name="Taro", email="taro@example.com")
session.add(new_user)   # セッションに「新規オブジェクト」として登録
session.commit()        # ここで INSERT が実行される

session.close()
Python

ここで重要なのは、add した瞬間に INSERT が走るわけではない、という点です。
Session は「このオブジェクトは新規追加だな」と覚えておき、
commit() したタイミングでまとめて DB に反映します。

SELECT:クエリでオブジェクトを取得

Session を使って、User を検索します。

session = SessionLocal()

user = session.query(User).filter_by(email="taro@example.com").first()
print(user.id, user.name, user.email)

session.close()
Python

session.query(User) が「User テーブルに対する SELECT」のスタートで、
filter_by で WHERE 条件を付け、first() で 1 件だけ取っています。

戻り値は dict ではなく User インスタンスです。
Session は、「DB から取ってきた行を User オブジェクトに変換して返す」役割も担っています。

UPDATE:属性を書き換えて commit

更新も Session を通して行います。

session = SessionLocal()

user = session.query(User).filter_by(email="taro@example.com").first()
user.name = "Taro Yamada"   # 属性を書き換える
session.commit()            # ここで UPDATE が発行される

session.close()
Python

Session は、「この user オブジェクトは前と違う値になった」と検知し、
commit() のときに適切な UPDATE 文を発行します。

DELETE:オブジェクトを delete して commit

削除も同様です。

session = SessionLocal()

user = session.query(User).filter_by(email="taro@example.com").first()
session.delete(user)   # 削除対象としてマーク
session.commit()       # ここで DELETE が発行される

session.close()
Python

Session は、「このオブジェクトは削除対象だ」と覚えておき、
commit() で DELETE を実行します。


Session とトランザクションの関係をもう少し深掘りする

Session は「トランザクションの単位」でもある

SQLAlchemy の Session は、基本的に「1 セッション=1 トランザクション」と考えると分かりやすいです。

Session を作る
複数の add / delete / 属性変更 / クエリを行う
commit する(または rollback する)
Session を閉じる

この一連の流れが、1 つのトランザクションになります。

例えば、ユーザーを作って、そのユーザーに紐づく何かを作る、という処理をまとめたいとき。

session = SessionLocal()

try:
    user = User(name="Taro", email="taro@example.com")
    session.add(user)

    # ここで user を使って別のテーブルにも INSERT したりできる
    # 例: Profile(user=user, ...)

    session.commit()   # ここまで全部成功したら確定
except Exception:
    session.rollback() # 途中でエラーがあれば全部取り消し
    raise
finally:
    session.close()
Python

このように、Session を単位として「全部成功か全部失敗か」を制御できます。
これはトランザクション(ACID)の Atomicity と直結しています。

なぜ「Session を短く保つ」ことが大事なのか

Session を長時間開きっぱなしにして、
その中でだらだらと操作を続けると、いくつか問題が出てきます。

トランザクションが長くなり、ロックが長時間保持される
他の処理が待たされる可能性が高くなる
接続プールのコネクションを占有し続けてしまう

そのため、実務では、

必要な処理だけを 1 セッション(1 トランザクション)にまとめる
処理が終わったら早めに commit / rollback して close する

という設計がとても重要になります。


Web アプリでの Session の扱い方イメージ

「1 リクエスト=1 セッション」が基本パターン

FastAPI や Flask などの Web フレームワークと SQLAlchemy を組み合わせるとき、
よく使われるパターンは「1 HTTP リクエストにつき 1 Session」です。

リクエストが来る
Session を作る
そのリクエストの処理の中で DB 操作をする
成功したら commit、エラーなら rollback
最後に Session を閉じる

という流れです。

FastAPI 風に書くと、イメージはこんな感じです。

from fastapi import Depends

def get_db():
    db = SessionLocal()
    try:
        yield db
        db.commit()
    except Exception:
        db.rollback()
        raise
    finally:
        db.close()

@app.get("/users/{user_id}")
def read_user(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).get(user_id)
    return {"id": user.id, "name": user.name}
Python

ここで db がまさに Session です。
1 リクエストの間だけ生きていて、終わったら必ず閉じられます。

このパターンを覚えておくと、
「セッションをどの粒度で持つべきか」という感覚が身につきます。


まとめ(セッションは「DB との会話とトランザクションの枠」)

Python × DB × SQL の文脈で「セッション」を整理すると、こうなります。

素の sqlite3 などでは、connect してから close するまでの間が、ざっくり「DB とのセッション」で、その中でトランザクション(BEGIN / COMMIT / ROLLBACK)を行う。
SQLAlchemy の Session は、Engine の上に乗る「論理的な会話の単位」であり、オブジェクトの追加・変更・削除を追跡し、commit でまとめて INSERT / UPDATE / DELETE を流す。
Session はトランザクションの単位でもあり、「全部成功したら commit、途中でエラーなら rollback」という形で、一連の処理を安全にまとめられる。
Session を長く持ちすぎるとロックや接続占有の問題が出るので、「必要な処理だけを短くまとめる(例:1 リクエスト=1 セッション)」という設計が重要になる。

ここまで読んで、
「自分の頭の中にあるアプリの処理を、どんな単位でセッションにまとめるべきか」
少しイメージが湧いてきたと思います。

タイトルとURLをコピーしました