タグ: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() # 入金を記録
といった具合にユーザーからユーザーへ振り込み機能の処理を実装してみます
何も問題がなければ上記のロジックで入金処理は完了しますが、何らかの理由によって処理3で受取人となるrecieverの取得に失敗した時に、Django全体の処理は止まりますが、内部では処理1はコミット済なのでユーザーAの出金は記録されています。
再起動した際に処理2は完了できなかったのでユーザーBにお金が入ってこず、事実上の振り込みは完了していません。
これがリリースしたサービスで起こったら大変です。
トランザクションが機能しないという事がどういう常態か理解したところでDjangoにトランザクション機能を利用したい場合は以下の方法があります
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
'ATOMIC_REQUESTS': True, # この行を追加
}
}
これでひとつのリクエストの開始から終了までが1つのトランザクションになり、View関数が呼ばれる前にトランザクションが開始され、最後まで問題が発生しなければコミットされて、途中で例外が発生すればトランザクションはロールバック(前のコミット自体を無かった事に)されます。
ただこの設定はプロジェクト内のすべてのデータベースの登録時にATOMIC_REQUESTSを適応する事になり、一例の処理中はテーブルにロックがかかりトラフィックが増加するときには内部処理のパフォーマンスに負荷がかかり非効率となります。
またユーザー側に「この工程まではコミットが完了した」という記録の証明が必要なケースには対応できなくなるので容易には設定しない方が良さそうです
from django.db import transaction
# transactionデコレータを追記
@transaction.atomic
def viewfunc(request):
hogehoge()
この手法ではひとつのviewが呼ばれるタイミングでそのviewブロックにのみにtransactionデコレータが機能してトランザクションを有効にすることができます。
from django.db import transaction
def viewfunc(request):
hogehoge()
try:
# トランザクションを有効にしたい範囲をネスト
with transaction.atomic():
generate_hoge()
except IntegrityError:
handle_exception()
この手法ではview関数内のさらに一部の範囲にのみトランザクションを有効にさせる事ができます。try/exceptでラップすることで処理に失敗してロールバックが発生したら例外としてキャッチして処理を区分けすることも可能になります。
