2020

6

16

Djangoのトランザクションについてまとめてやんよ!!!

タグ:

Pocket
LINEで送る
Facebook にシェア
reddit にシェア
LinkedIn にシェア

Djangoのトランザクションについて

Djangoにはトランザクションがデフォルトで機能していません
トランザクション(互いに関連・依存する複数の処理をまとめ、一体不可分の処理単位として扱う)が機能しないとはどういう事か?

トランザクションを使わずに振り込み機能を作る

# 送金処理をする関数
def send_money(request, sender_id, reciever_id, amount):
    # 処理1・・・ユーザーA(送金元)の残高を減算する
    sender = User.objects.get(id=sender_id)
    sender.balance -= amount
    sender.save()  # 出金を記録

    # ----- 処理1と処理2は直接連携していない ------

    # 処理2・・・ユーザーB(送金先)の残高を加算する
    reciever = User.objects.get(id=reciever_id)
    reciever.balance += amount
    reciever.save()  # 入金を記録

といった具合にユーザーからユーザーへ振り込み機能の処理を実装してみます

  1. 送金元となるユーザーAの残高情報を取得する
  2. ユーザーAの残高データから送金額分を差し引き記録する
  3. 受け取り人となるユーザーBの口座情報を取得する
  4. ユーザーBの口座残高に送金額分を加算する
  5. ユーザーBの更新された残高を記録する

何も問題がなければ上記のロジックで入金処理は完了しますが、何らかの理由によって処理3で受取人となるrecieverの取得に失敗した時に、Django全体の処理は止まりますが、内部では処理1はコミット済なのでユーザーAの出金は記録されています。
再起動した際に処理2は完了できなかったのでユーザーBにお金が入ってこず、事実上の振り込みは完了していません
これがリリースしたサービスで起こったら大変です。

Djangoでトランザクションを有効にする

トランザクションが機能しないという事がどういう常態か理解したところでDjangoにトランザクション機能を利用したい場合は以下の方法があります

ケース① 共通設定でATOMIC_REQUESTSを追加

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
        'ATOMIC_REQUESTS': True,  # この行を追加
    }
}

これでひとつのリクエストの開始から終了までが1つのトランザクションになり、View関数が呼ばれる前にトランザクションが開始され、最後まで問題が発生しなければコミットされて、途中で例外が発生すればトランザクションはロールバック(前のコミット自体を無かった事に)されます。

ATOMIC_REQUESTSの注意点

ただこの設定はプロジェクト内のすべてのデータベースの登録時にATOMIC_REQUESTSを適応する事になり、一例の処理中はテーブルにロックがかかりトラフィックが増加するときには内部処理のパフォーマンスに負荷がかかり非効率となります。
またユーザー側に「この工程まではコミットが完了した」という記録の証明が必要なケースには対応できなくなるので容易には設定しない方が良さそうです

ケース② view関数単位でtransactionデコレータを使う

from django.db import transaction

# transactionデコレータを追記
@transaction.atomic
def viewfunc(request):
    hogehoge()

この手法ではひとつのviewが呼ばれるタイミングでそのviewブロックにのみにtransactionデコレータが機能してトランザクションを有効にすることができます。

ケース③ viewの内部で一部にtransactionを使う

from django.db import transaction

def viewfunc(request):
    hogehoge()

    try:
        # トランザクションを有効にしたい範囲をネスト
        with transaction.atomic():
            generate_hoge()
    except IntegrityError:
        handle_exception()

この手法ではview関数内のさらに一部の範囲にのみトランザクションを有効にさせる事ができます。
try/exceptでラップすることで処理に失敗してロールバックが発生したら例外としてキャッチして処理を区分けすることも可能になります。

Pocket
LINEで送る
Facebook にシェア
reddit にシェア
LinkedIn にシェア

トップへ