🍣

トランザクションの考え方

に公開

トランザクションは寿司で考えるとわかりやすい🍣

「トランザクション」って、聞いただけでちょっと難しそうな印象がありますよね。
私も最初はよくわからなくて、「なんとなく難しそう」と感じていました。でも実は、身近な例で考えるとすごくシンプルなんです。

たとえば、あなたが寿司屋で「マグロの握り」を注文したとします。でも届いたのはシャリだけ…。ネタが乗っていない!
こんなことが起きたら嫌ですよね?これは「注文したものがまとまっていない」状態です。

実はこういうことが、プログラムの世界でも起きることがあります。それを防ぐための仕組みが「トランザクション」なんです。

トランザクションって何?

トランザクションとは、「ひとまとまりの処理が全部成功したら確定、1つでも失敗したら全部取り消す」という考え方です。

実際のアプリ開発では、たとえば次のようなケースがあります:

  • 商品を購入したとき:在庫を減らす、購入履歴に追加する、クレジットカードを決済する

この3つの処理のうち、どれか1つでも失敗したら全部をやり直したいですよね?そうしないと「カードは引き落とされたのに、在庫が減ったまま戻らない」といった矛盾が生まれてしまいます。
処理の一部だけ成功してしまうと不自然なことが起きるため、だから、すべての処理がうまくいったときだけ、まとめて“確定”させるという仕組みが必要なんです。

使い方

実際のコードでどうやってトランザクションを使うのか Python と SQLAlchemy を使ったシンプルな例で見てみます。

from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.exc import SQLAlchemyError

async def purchase_item(session: AsyncSession, user_id: int, item_id: int):
    try:
        # トランザクション開始
        async with session.begin():
            # 在庫を減らす
            await decrease_stock(session, item_id)

            # 購入履歴に追加
            await add_purchase_history(session, user_id, item_id)

            # 決済処理(ここではダミー関数)
            await process_payment(user_id)

        return {"message": "購入完了!"}

    except SQLAlchemyError:
        return {"error": "購入に失敗しました"}

async with session.begin() の中の処理は、すべて「ひとまとまり」として扱われます。

どれか1つでも失敗した場合、全体が自動的に取り消される(ロールバックされる)ため、
データベースに「中途半端な状態」が残りません。

たとえば、決済が失敗したときに「在庫だけ減っていた」なんてことも防げるわけです。
トランザクションを使うことで、アプリがより安全で信頼できるものになります。

手動で制御する方法

トランザクションは、async with session.begin() を使わずに、手動で commit()rollback() を書くこともできます。

以下は、明示的にトランザクションを制御するコードの例です。

async def purchase_item(session: AsyncSession, user_id: int, item_id: int):
    try:
        await decrease_stock(session, item_id)
        await add_purchase_history(session, user_id, item_id)
        await process_payment(user_id)

        # 全部成功したら手動でコミット
        await session.commit()

        return {"message": "購入完了!"}

    except Exception:
        # 失敗したらロールバック
        await session.rollback()
        return {"error": "購入に失敗しました"}

このように、処理をすべて try ブロックの中に書いて、
最後に commit() を実行することでトランザクションを確定させることができます。

もしエラーが発生した場合は except ブロックで rollback() を呼び出し、
データベースを元の状態に戻します。

session.begin() との違いは?

session.begin() を使うと、トランザクションの開始・終了・ロールバックが自動で管理されるので、
個人的には「書き忘れがなくて安心」という印象があります。
一方、commit() / rollback() を使う場合は、自分でしっかり書く必要があるため、書き忘れなどのミスには注意が必要です。

まとめ

「トランザクション」という言葉は、一見するととっつきにくく感じるかもしれませんが、
「処理をひとまとまりとして扱う」という考え方は、実は私たちの日常にもたくさんあります。

実際のコードでは、async with session.begin() を使うことで、トランザクションを自動的に管理できます。
私はこの方法がミスも少なく、個人的には書きやすいと感じています。

一方で、処理を細かく制御したいときには、手動で commit()rollback() を書くスタイルもあります。
どちらを使うかは場面や自分のスタイルに合わせて選べるのが良いところだと思います。

トランザクションは、データの整合性を守るための大切な考え方です。
アプリの信頼性を高めるためにも、「処理のひとまとまり」を意識する習慣を少しずつ身につけていきたいですね。

今回のように、プログラムを書く中で概念から考えることに慣れてくると、少しずつプログラミングの本質が見えてくるのかな、と感じています。

「トランザクションって、意外と身近なものだったんだな」と感じてもらえたら嬉しいです!

Discussion