データベーストランザクションの管理

revision-up-to:17812 (1.4)

Django はトランザクションをサポートしているデータベース向けに、トランザクショ ン管理を制御する方法をいくつか提供しています。

デフォルトのトランザクション処理

Django のデフォルトの挙動では、組み込みのデータ変更に関わるモデル関数を呼び 出したときにはいつでも自動的に commit を行います。例えば、 model.save()model.delete() を呼び出すと、変更は即座にコミットされます。

これはほとんどのデータベースにおける自動コミット設定とほとんど同じ挙動です。 すなわち、ユーザがデータベースへの書き込みを必要とするような操作を行うと、 Django はすぐに INSERT/UPDATE/DELETE 文を実行し、次いで COMMIT を実行します。暗黙のロールバックは行いません。

HTTP リクエストとトランザクションを結び付ける

TransactionMiddleware を介してリクエストとレスポンスのフェイズにトラン ザクションを結び付けるというものです。

このトランザクション処理は次のように行われます: まず、リクエスト処理の開始 時にトランザクションを開始します。レスポンスを問題なく生成できたら、全ての トランザクションをコミットします。ビュー関数が例外を送出したら、ロールバッ クを起こします。

この機能を有効にするには、 TransactionMiddleware ミドルウェアを MIDDLEWARE_CLASSES 設定に追加します:

MIDDLEWARE_CLASSES = (
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.transaction.TransactionMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
)

ミドルウェアの配置順はとても重要です。トランザクションミドルウェアは呼び出 されるビュー関数だけでなく、後続のミドルウェアモジュール全てに影響します。 従って、セッションミドルウェアをトランザクションミドルウェアの後ろに配置す ると、セッションの生成はトランザクションの一部に入ってしまいます。

いくつかのキャッシュミドルウェアの例外は: CacheMiddleware, UpdateCacheMiddleware, FetchFromCacheMiddleware は影響を受けませ ん。データベースキャッシュを使用しているときでも、 Django のキャッシュミドル ウェアは独自のデータベースカーソルを使います。(内部的に独自のデータベース コネクションとマッピングされています。)

ビュー内でトランザクションを管理する

トランザクションを管理するコンテキストマネージャーは Django 1.3 で 新しくなりました。

ほとんどのユーザにとって、非明示的なリクエストベースのトランザクションは素 晴らしい働きをすることでしょう。しかしながら、トランザクションの管理方法を より詳細に制御したい場合、 django.db.transaction の中の関数群を使って 関数ごと、またはコードブロックベースごとにトランザクションを管理してください。

これらの関数は、下記で詳細に説明しています。二つの方法で使えます。:

  • 特定の関数の デコレーター としての一例は:

    from django.db import transaction
    
    @transaction.commit_on_success
    def viewfunc(request):
        # ...
        # this code executes inside a transaction
        # ...
    
  • コンテキストマネージャー として特定のブロックに使うには

    from django.db import transaction
    
    def viewfunc(request):
        # ...
        # this code executes using default transaction management
        # ...
    
        with transaction.commit_on_success():
            # ...
            # this code executes inside a transaction
            # ...
    

両方のテクニックは、 Python の全てのサポートバージョンで使えます。 しかしながら、 Python 2.5 では、 with 文を使う際には from __future__ import with_statement をモジュールの先頭で 加えなければ行けません。

Note

下記ではビュー関数を例に取ってはいますが、以下に述べるデコレータと コンテキストマネージャーはトランザクションを扱う必要がある全ての コードで使用することが出来ます。

autocommit()

ビュー関数のトランザクションの挙動を、グローバルな設定に関係なく Django の デフォルトの挙動にスイッチするには、 autocommit デコレータを使います。

例えば:

from django.db import transaction

@transaction.autocommit
def viewfunc(request):
    ....

@transaction.autocommit(using="my_other_database")
def viewfunc2(request):
    ....

viewfunc() の中では、 model.save()model.delete() 、その他デー タベースに書き込みを行う全ての関数でトランザクションを commit します。 viewfunc2() は同じ挙動になるでしょう、ですが "別のデータベース" コネクションにです。

commit_on_success()

commit_on_success デコレータを使うのは、関数内の全ての処理にわたるトラン ザクションを使えます:

from django.db import transaction

@transaction.commit_on_success
def viewfunc(request):
    ....

@transaction.commit_on_success(using="my_other_database")
def viewfunc2(request):
    ....

関数の実行に成功すると、 Django はそれまでの全ての作業を commit します。関 数が例外を送出すると、 Django はトランザクションを rollback します。

commit_manually()

トランザクションを完全に管理したい場合には、 commit_manually デコレータ を使います。このデコレータは Django にユーザが自分でトランザクションを管理 しようとしていることを知らせます。

commit()rollback() を行わずにデータを変更した場合は TransactionManagementError 例外を送出します。

手動のトランザクション管理は以下のようになります:

from django.db import transaction

@transaction.commit_manually
def viewfunc(request):
    ...
    # commit/rollback を好きなタイミングで行えます
    transaction.commit()
    ...

    # ただし、自分でちゃんとやっておくのを忘れないように!
    try:
        ...
    except:
        transaction.rollback()
    else:
        transaction.commit()

@transaction.commit_manually(using="my_other_database")
def viewfunc2(request):
    ...

トランザクションを操作する必要性

リリースノートを参照してください

Django はリクエストの完了より前にオープンなトランザクションを閉じなくてはいけ ません。もし、 autocommit() (通常のコミットモード)を使用しているか、 commit_on_success() ならば、自動的にその処理が行われます。 しかし、手動でトランザクションを管理していると( commit_manually() デコレータを使用していれば)、トランザクションがコミットされるか、 ロールバックするかをリクエストが終了する前に確かめておかなければいけません。

これは全てのデータベースオペレーションに適用されます、書き込み処理だけでは ありません。データベースから読み取るだけだったとしても、トランザクションは リクエストが完了する前にコミットかロールバックされてなければいけません。

トランザクション管理をグローバルに無効化する方法

制御マニアの人は、 Django 設定ファイルで DISABLE_TRANSACTION_MANAGEMENTTrue に設定すれば、全ての自動トランザクション管理を無効にし、自分で トランザクションを管理できます。

この場合、 Django はいかなるトランザクション管理も行わなくなります。ミドル ウェアが非明示的にトランザクションを commit することはなくなり、自分でロー ル管理を行わねばなりません。さらに、何らかのミドルウェアで変更の commit を 自分で行わねばならなくなります。

従って、トランザクションの無効化は、自作のトランザクション制御ミドルウェア を実行したい場合や、本当に変わったことをやりたい場合向けです。ほとんどの状況では、 デフォルトの挙動かトランザクションミドルウェアで十分で、必要に応じ て特定の関数だけを変更すればよいでしょう。

セーブポイント

セーブポイントはトランザクションの中で、全てのトランザクションではなく、 トランザクションの一部をロールバックできるようにします。セーブポイントは PostgreSQL 8 や Oracle 、MySQL (バージョン 5.0.3 とそれより新しいもので InnnoDB をストレージエンジンに使っているもの)バックエンドで使えます。 他のバックエンドはセーブポイントを関数で提供します、しかしこれは空の オペレーションです – これは実際何もしません。

MySQL バックエンドでのセーブポイントは Django 1.4 からサポートされ始め ました。

セーブポイントは、通常の autocommit を Django で使っている限りこれは 役に立ちません。もし、 commit_on_successcommit_manually を 使っていると、それぞれの開かれたトランザクションは一連のデータベース処理として 構築され、コミットやロールバックを待ちます。もし生じた問題をロールバックしたい のならば、完全にトランザクションをロールバックします。セーブポイントは、 transaction.rollback() で動作する完全なロールバックを細かく 動作させられるようにします、

それぞれの関数は using 引数を取ります。これはこの処理が適用される データベースの名前にしてください。もし using 引数がなかったら、 "default" データベースが使われます。

セーブポイントは、トランザクションオブジェクトの三つのメソッドによって コントロールされています。:

transaction.savepoint(using=None)

新しいセーブポイントを作ります。これは、 “よい”状態にある トランザクションをマークするポイントです。

セーブポイント ID (sid)を返します。

transaction.savepoint_commit(sid, using=None)

最後のコミットか、セーブポイントが作られたときからの動作を含むように セーブポイントをアップデートします。

transaction.savepoint_rollback(sid)

トランザクションをセーブポイントかコミットされたところまで戻します。

セーブポイントを使った例は:

from django.db import transaction

@transaction.commit_manually
def viewfunc(request):

  a.save()
  # open transaction now contains a.save()
  sid = transaction.savepoint()

  b.save()
  # open transaction now contains a.save() and b.save()

  if want_to_keep_b:
      transaction.savepoint_commit(sid)
      # open transaction still contains a.save() and b.save()
  else:
      transaction.savepoint_rollback(sid)
      # open transaction now contains only a.save()

  transaction.commit()

MySQL でのトランザクション

MySQL を使っている場合、MySQL のバージョンと使っているテーブルの形式に応じ て、テーブルがトランザクションをサポートする場合としない場合があります。 (「テーブルの形式」とは、”InnoDB” や “MyISAM” などを指します。) このドキュ メントでは MySQL のトランザクションにまつわる説明はしませんが、 MySQL のサ イトには トランザクションに関する情報 が掲載されています。

MySQL 構成がトランザクションをサポート していない 場合、 Django は自動コ ミットモードで動作します。すなわち、 SQL 文は呼び出されたその場で実行され、 コミットされます。 MySQL 構成がトランザクションをサポートしている場合、 Django はこのドキュメントの説明通りにトランザクションを処理します。

PostgreSQL のトランザクションで例外処理を扱う

PostgreSQL のカーソルが例外を呼び出した時(典型的には IntegrityError )、 同じトランザクションの中の以後全ての SQL がエラーにより失敗します “トランザ クションブロックの終わりまでクエリは無視され、最新のトランザクションは 破棄されます”。一方で save() は PostgreSQL の例外処理とは似ていません、 そこではもっと進歩して慣用的なパターンがあり、ユニークフィールドでオブジェクト をセーブするようなものであったり、 force_insert/force_update フラグを使って セーブしたり、またはカスタム SQL の

この種のエラーからの回復方法はいくつかあります。

トランザクションロールバック

トランザクションを全て巻き戻すための最初のオプション例です:

a.save() # Succeeds, but may be undone by transaction rollback
try:
    b.save() # Could throw exception
except IntegrityError:
    transaction.rollback()
c.save() # Succeeds, but a.save() may have been undone

transaction.rollback() を呼ぶと、トランザクションをまるまる元に戻します。 コミットされてないデータベースへの手続きは全て失われます。この例では、 a.save() による変更は失われます、ですがこの手続きはエラーを出しません。

セーブポイント ロールバック

もし、 PostgreSQL 8 か、それより新しいものを使っていれば、 セーブポイント を使って データベースが動作する前、失敗するまえに戻せます、セーブポイントを アップグレードしたり設定することが出来ます。この方法は、オペレーションが 失敗した時に、一つ前の手続きにもどることができ、完全にトランザクションを 巻き戻さなくて済みます。:

a.save() # Succeeds, and never undone by savepoint rollback
try:
    sid = transaction.savepoint()
    b.save() # Could throw exception
    transaction.savepoint_commit(sid)
except IntegrityError:
    transaction.savepoint_rollback(sid)
c.save() # Succeeds, and a.save() is never undone

この例では、 a.save() は実行されておらず、 b.save() が 例外を発生させています。

データベースレベル オートコミット

PostgreSQL 8.2 かそれより新しいと、進歩したオプションが PostgreSQLを実行 するためにあります。 データベースレベル オートコミット です。 もしこのオプションを使うと、恒常的にトランザクションが開かなくなり、 例外を捕まえた後でも処理を継続できるようになります。:

a.save() # succeeds
try:
    b.save() # Could throw exception
except IntegrityError:
    pass
c.save() # succeeds

Note

これは、 オートコミットデコレータ と同じではなく、データベースレベルでのオートコミットを使う時に、それは データベースのトランザクションは全くありません。 オートコミット でコレーッタはトランザクションを使い、データベースが変化する処理が生じた 時に、自動的にそれぞれのトランザクションをコミットします。