データベーストランザクション
Djangoは、データベーストランザクションの管理方法を制御するいくつかの方法を提供します。
データベーストランザクションの管理
Djangoのデフォルトのトランザクション動作
Djangoのデフォルトの動作は、自動コミットモードで実行することです。 トランザクションがアクティブでない限り、各クエリはすぐにデータベースにコミットされます。 詳細は以下をご覧ください。
Djangoは、トランザクションまたはセーブポイントを自動的に使用して、複数のクエリ、特に delete()および update()クエリを必要とするORM操作の整合性を保証します。
Djangoの TestCase クラスも、パフォーマンス上の理由から、トランザクション内の各テストをラップします。
トランザクションをHTTPリクエストに結び付ける
Webでトランザクションを処理する一般的な方法は、各リクエストをトランザクションでラップすることです。 設定 :setting: `ATOMIC_REQUESTS ` にTrue
この動作を有効にする各データベースの構成で。
それはこのように動作します。 ビュー関数を呼び出す前に、Djangoはトランザクションを開始します。 応答が問題なく生成された場合、Djangoはトランザクションをコミットします。 ビューで例外が生成された場合、Djangoはトランザクションをロールバックします。
通常は atomic()コンテキストマネージャーを使用して、ビューコードのセーブポイントを使用してサブトランザクションを実行できます。 ただし、ビューの最後に、すべての変更がコミットされるか、まったくコミットされません。
警告
このトランザクションモデルの単純さは魅力的ですが、トラフィックが増加すると非効率的になります。 すべてのビューに対してトランザクションを開くには、いくらかのオーバーヘッドがあります。 パフォーマンスへの影響は、アプリケーションのクエリパターンと、データベースがロックをどの程度適切に処理するかによって異なります。
リクエストごとのトランザクションとストリーミング応答
ビューが StreamingHttpResponse を返す場合、応答のコンテンツを読み取ると、コンテンツを生成するコードが実行されることがよくあります。 ビューはすでに返されているため、このようなコードはトランザクションの外部で実行されます。
一般的に、ストリーミング応答の生成中にデータベースに書き込むことはお勧めできません。応答の送信を開始した後にエラーを処理する賢明な方法がないためです。
実際には、この機能は、以下で説明する atomic()デコレータのすべてのビュー関数をラップします。
ビューの実行のみがトランザクションに含まれていることに注意してください。 ミドルウェアはトランザクションの外部で実行され、テンプレート応答のレンダリングも実行されます。
いつ :setting: `ATOMIC_REQUESTS ` が有効になっている場合でも、トランザクションでビューが実行されないようにすることは可能です。
- non_atomic_requests(using=None)
このデコレータは、 :setting: `ATOMIC_REQUESTS ` 特定のビューの場合:
from django.db import transaction @transaction.non_atomic_requests def my_view(request): do_stuff() @transaction.non_atomic_requests(using='other') def my_other_view(request): do_stuff_on_the_other_database()
ビュー自体に適用されている場合にのみ機能します。
トランザクションを明示的に制御する
Djangoは、データベーストランザクションを制御するための単一のAPIを提供します。
- atomic(using=None, savepoint=True, durable=False)
原子性は、データベーストランザクションの定義プロパティです。
atomic
を使用すると、データベースのアトミック性が保証されるコードのブロックを作成できます。 コードのブロックが正常に完了すると、変更はデータベースにコミットされます。 例外がある場合、変更はロールバックされます。atomic
ブロックはネストできます。 この場合、内側のブロックが正常に完了しても、後で外側のブロックで例外が発生した場合でも、その効果をロールバックできます。atomic
ブロックが常に最も外側のatomic
ブロックであることを確認し、ブロックがエラーなしで終了したときにデータベースの変更がコミットされるようにすると便利な場合があります。 これは耐久性と呼ばれ、durable=True
を設定することで実現できます。atomic
ブロックが別のブロック内にネストされている場合、RuntimeError
が発生します。atomic
は、デコレータとしても使用できます。from django.db import transaction @transaction.atomic def viewfunc(request): # This code executes inside a transaction. do_stuff()
およびコンテキストマネージャーとして:
from django.db import transaction def viewfunc(request): # This code executes in autocommit mode (Django's default). do_stuff() with transaction.atomic(): # This code executes inside a transaction. do_more_stuff()
atomic
をtry / exceptionブロックでラップすると、整合性エラーを自然に処理できます。from django.db import IntegrityError, transaction @transaction.atomic def viewfunc(request): create_parent() try: with transaction.atomic(): generate_relationships() except IntegrityError: handle_exception() add_children()
この例では、
generate_relationships()
が整合性制約を破ってデータベースエラーを引き起こした場合でも、add_children()
でクエリを実行でき、create_parent()
からの変更は引き続き存在し、同じトランザクション。generate_relationships()
で試行された操作は、handle_exception()
が呼び出されたときにすでに安全にロールバックされているため、必要に応じて例外ハンドラーもデータベースを操作できます。atomic
内で例外をキャッチしないでください!atomic
ブロックを終了するとき、Djangoはそれが正常に終了したか、例外を除いて終了したかを調べて、コミットするかロールバックするかを決定します。atomic
ブロック内で例外をキャッチして処理すると、問題が発生したという事実をDjangoから隠すことができます。 これにより、予期しない動作が発生する可能性があります。これは主に、 DatabaseError および IntegrityError などのそのサブクラスに関する懸念事項です。 このようなエラーの後、トランザクションは中断され、Djangoは
atomic
ブロックの最後でロールバックを実行します。 ロールバックが発生する前にデータベースクエリを実行しようとすると、Djangoは TransactionManagementError を発生させます。 ORM関連のシグナルハンドラが例外を発生させたときにも、この動作が発生する可能性があります。データベースエラーをキャッチする正しい方法は、上記のように
atomic
ブロックの周りです。 必要に応じて、この目的のためにatomic
ブロックを追加します。 このパターンには別の利点があります。例外が発生した場合にロールバックされる操作を明示的に区切ることです。生のSQLクエリによって発生した例外をキャッチした場合、Djangoの動作は指定されておらず、データベースに依存します。
トランザクションをロールバックするときに、モデルの状態を手動で戻す必要がある場合があります。
トランザクションのロールバックが発生しても、モデルのフィールドの値は元に戻されません。 これにより、元のフィールド値を手動で復元しない限り、モデルの状態に一貫性がなくなる可能性があります。
たとえば、
MyModel
にactive
フィールドがある場合、このスニペットは、active
をTrue
はトランザクションで失敗します:from django.db import DatabaseError, transaction obj = MyModel(active=False) obj.active = True try: with transaction.atomic(): obj.save() except DatabaseError: obj.active = False if obj.active: ...
アトミック性を保証するために、
atomic
は一部のAPIを無効にします。atomic
ブロック内でデータベース接続の自動コミット状態をコミット、ロールバック、または変更しようとすると、例外が発生します。atomic
はusing
引数を取ります。これは、データベースの名前である必要があります。 この引数が指定されていない場合、Djangoは"default"
データベースを使用します。内部的には、Djangoのトランザクション管理コードは次のとおりです。
最も外側の
atomic
ブロックに入るときにトランザクションを開きます。内側の
atomic
ブロックに入るときにセーブポイントを作成します。内部ブロックを終了するときに、セーブポイントを解放またはロールバックします。
最も外側のブロックを終了するときに、トランザクションをコミットまたはロールバックします。
savepoint
引数をFalse
に設定すると、内部ブロックのセーブポイントの作成を無効にできます。 例外が発生した場合、Djangoは、セーブポイントがある場合は最初の親ブロックを終了するときにロールバックを実行し、それ以外の場合は最も外側のブロックを実行します。 原子性は、外部トランザクションによって引き続き保証されます。 このオプションは、セーブポイントのオーバーヘッドが目立つ場合にのみ使用してください。 上記のエラー処理を壊すという欠点があります。自動コミットがオフの場合は、
atomic
を使用できます。 最も外側のブロックであっても、セーブポイントのみを使用します。
パフォーマンスに関する考慮事項
オープントランザクションには、データベースサーバーのパフォーマンスコストがかかります。 このオーバーヘッドを最小限に抑えるには、トランザクションをできるだけ短くします。 これは、Djangoの要求/応答サイクル以外の長時間実行プロセスで atomic()を使用している場合に特に重要です。
警告
django.test.TestCase は耐久性チェックを無効にして、パフォーマンス上の理由からトランザクションで耐久性のあるアトミックブロックをテストできるようにします。 耐久性をテストするには、 django.test.TransactionTestCase を使用します。
バージョン3.2で変更: durable
引数が追加されました。
自動コミット
Djangoが自動コミットを使用する理由
SQL標準では、各SQLクエリは、すでにアクティブになっていない限り、トランザクションを開始します。 このようなトランザクションは、明示的にコミットまたはロールバックする必要があります。
これは、アプリケーション開発者にとって常に便利であるとは限りません。 この問題を軽減するために、ほとんどのデータベースには自動コミットモードが用意されています。 自動コミットがオンになっていて、アクティブなトランザクションがない場合、各SQLクエリは独自のトランザクションにラップされます。 つまり、このような各クエリはトランザクションを開始するだけでなく、クエリが成功したかどうかに応じて、トランザクションが自動的にコミットまたはロールバックされます。
PEP 249 、PythonデータベースAPI仕様v2.0では、最初に自動コミットをオフにする必要があります。 Djangoはこのデフォルトをオーバーライドし、自動コミットをオンにします。
これを回避するには、トランザクション管理を非アクティブ化できますが、お勧めしません。
トランザクション管理の非アクティブ化
設定することで、特定のデータベースに対するDjangoのトランザクション管理を完全に無効にすることができます :setting: `AUTOCOMMIT ` にFalse
その構成で。 これを行うと、Djangoは自動コミットを有効にせず、コミットも実行しません。 基盤となるデータベースライブラリの通常の動作を取得します。
これには、Djangoまたはサードパーティのライブラリによって開始されたトランザクションであっても、すべてのトランザクションを明示的にコミットする必要があります。 したがって、これは、独自のトランザクション制御ミドルウェアを実行したい場合や、本当に奇妙なことをしたい場合に最適です。
コミット後にアクションを実行する
現在のデータベーストランザクションに関連するアクションを実行する必要がある場合がありますが、トランザクションが正常にコミットされた場合に限ります。 例としては、 Celery タスク、電子メール通知、またはキャッシュの無効化などがあります。
Djangoは、トランザクションが正常にコミットされた後に実行する必要があるコールバック関数を登録するための on_commit()関数を提供します。
- on_commit(func, using=None)
関数(引数をとらない)を on_commit()に渡します。
from django.db import transaction
def do_something():
pass # send a mail, invalidate a cache, fire off a Celery task, etc.
transaction.on_commit(do_something)
関数をラムダでラップすることもできます。
transaction.on_commit(lambda: some_celery_task.delay('arg1'))
渡した関数は、on_commit()
が呼び出された場所で行われた架空のデータベース書き込みが正常にコミットされた直後に呼び出されます。
アクティブなトランザクションがないときにon_commit()
を呼び出すと、コールバックはすぐに実行されます。
その架空のデータベース書き込みが代わりにロールバックされた場合(通常、 atomic()ブロックで未処理の例外が発生した場合)、関数は破棄され、呼び出されることはありません。
セーブポイント
セーブポイント(つまり ネストされた atomic()ブロック)は正しく処理されます。 つまり、セーブポイントの後に登録された on_commit()呼び出し可能オブジェクト(ネストされた atomic()ブロック内)は、外部トランザクションがコミットされた後に呼び出されますが、そのロールバックの場合は呼び出されません。トランザクション中に発生したセーブポイントまたは以前のセーブポイント:
with transaction.atomic(): # Outer atomic, start a new transaction
transaction.on_commit(foo)
with transaction.atomic(): # Inner atomic block, create a savepoint
transaction.on_commit(bar)
# foo() and then bar() will be called when leaving the outermost block
一方、セーブポイントがロールバックされると(例外が発生したため)、内部の呼び出し可能オブジェクトは呼び出されません。
with transaction.atomic(): # Outer atomic, start a new transaction
transaction.on_commit(foo)
try:
with transaction.atomic(): # Inner atomic block, create a savepoint
transaction.on_commit(bar)
raise SomeError() # Raising an exception - abort the savepoint
except SomeError:
pass
# foo() will be called, but not bar()
実行順序
特定のトランザクションのオンコミット関数は、登録された順序で実行されます。
例外処理
特定のトランザクション内の1つのオンコミット関数でキャッチされない例外が発生した場合、その同じトランザクションで後で登録された関数は実行されません。 これは、 on_commit()を使用せずに自分で関数を順番に実行した場合と同じ動作です。
実行のタイミング
コールバックは、コミットが成功した後に実行されるため、コールバックに失敗してもトランザクションはロールバックされません。 これらはトランザクションの成功時に条件付きで実行されますが、トランザクションの一部ではありません。 使用目的(メール通知、Celeryタスクなど)の場合、これで問題ありません。 そうでない場合(フォローアップアクションが非常に重要で、その失敗がトランザクション自体の失敗を意味するはずである場合)、 on_commit()フックを使用する必要はありません。 代わりに、 psycopgの2フェーズコミットプロトコルサポートや オプションの2フェーズコミット拡張機能などの 2フェーズコミットが必要になる場合があります。 PythonDB-API仕様。
コールバックは、コミット後に接続で自動コミットが復元されるまで実行されません(そうしないと、コールバックで実行されたクエリによって暗黙のトランザクションが開かれ、接続が自動コミットモードに戻ることができなくなります)。
自動コミットモードで atomic()ブロックの外側にある場合、関数はコミット時ではなく、すぐに実行されます。
オンコミット関数は、 自動コミットモードそしてそのアトミック() (また :setting: `ATOMIC_REQUESTS ` )トランザクションAPI。 自動コミットが無効で、アトミックブロック内にいないときに on_commit()を呼び出すと、エラーが発生します。
テストで使用
Djangoの TestCase クラスは、テストの分離を提供するために、トランザクション内の各テストをラップし、各テストの後にそのトランザクションをロールバックします。 これは、トランザクションが実際にコミットされないことを意味します。したがって、 on_commit()コールバックは実行されません。
TestCase.captureOnCommitCallbacks()を使用すると、この制限を克服できます。 これにより、 on_commit()コールバックがリストにキャプチャされ、コールバックに対してアサーションを作成したり、コールバックを呼び出してトランザクションのコミットをエミュレートしたりできます。
制限を克服する別の方法は、 TestCase の代わりに TransactionTestCase を使用することです。 これは、トランザクションがコミットされ、コールバックが実行されることを意味します。 ただし、 TransactionTestCase はテスト間でデータベースをフラッシュします。これは、 TestCase 'の分離よりも大幅に低速です。
なぜロールバックフックがないのですか?
ロールバックフックは、さまざまなことが暗黙的なロールバックを引き起こす可能性があるため、コミットフックよりも堅牢に実装するのが困難です。
たとえば、プロセスが正常にシャットダウンする機会なしに強制終了されたためにデータベース接続が切断された場合、ロールバックフックは実行されません。
ただし、解決策があります。アトミックブロック(トランザクション)中に何かを実行し、トランザクションが失敗した場合にそれを元に戻す代わりに、 on_commit()を使用して、トランザクションが成功するまで最初に実行を遅らせます。 そもそもやったことのないことを元に戻すのはずっと簡単です!
低レベルAPI
警告
可能であれば、常に atomic()を優先してください。 各データベースの特異性を説明し、無効な操作を防ぎます。
低レベルのAPIは、独自のトランザクション管理を実装している場合にのみ役立ちます。
自動コミット
Djangoは、 django.db.transaction モジュールでAPIを提供して、各データベース接続の自動コミット状態を管理します。
- get_autocommit(using=None)
- set_autocommit(autocommit, using=None)
これらの関数は、データベースの名前であるusing
引数を取ります。 提供されていない場合、Djangoは"default"
データベースを使用します。
自動コミットは最初はオンになっています。 オフにした場合、それを復元するのはあなたの責任です。
自動コミットをオフにすると、データベースアダプターのデフォルトの動作が得られ、Djangoは役に立ちません。 その動作は PEP 249 で指定されていますが、アダプターの実装は常に相互に一貫しているとは限りません。 使用しているアダプタのドキュメントを注意深く確認してください。
自動コミットをオンに戻す前に、通常は commit()または rollback()を発行して、トランザクションがアクティブになっていないことを確認する必要があります。
atomic()ブロックがアクティブな場合、Djangoは自動コミットをオフにすることを拒否します。これは、アトミック性が損なわれるためです。
トランザクション
トランザクションは、データベースクエリのアトミックセットです。 プログラムがクラッシュした場合でも、データベースはすべての変更が適用されるか、まったく適用されないことを保証します。
Djangoは、トランザクションを開始するためのAPIを提供していません。 トランザクションを開始するための予想される方法は、 set_autocommit()を使用して自動コミットを無効にすることです。
トランザクションが開始されたら、 commit()を使用してこの時点までに実行した変更を適用するか、 rollback()を使用して変更をキャンセルするかを選択できます。 これらの関数は django.db.transaction で定義されています。
- commit(using=None)
- rollback(using=None)
これらの関数は、データベースの名前であるusing
引数を取ります。 提供されていない場合、Djangoは"default"
データベースを使用します。
atomic()ブロックがアクティブな場合、Djangoはコミットまたはロールバックを拒否します。これは、アトミック性が損なわれるためです。
セーブポイント
セーブポイントは、トランザクション全体ではなく、トランザクションの一部をロールバックできるようにするトランザクション内のマーカーです。 セーブポイントは、SQLite、PostgreSQL、Oracle、およびMySQL(InnoDBストレージエンジンを使用している場合)のバックエンドで使用できます。 他のバックエンドはセーブポイント機能を提供しますが、それらは空の操作であり、実際には何もしません。
Djangoのデフォルトの動作である自動コミットを使用している場合、セーブポイントは特に役に立ちません。 ただし、 atomic()を使用してトランザクションを開くと、コミットまたはロールバックを待機する一連のデータベース操作が構築されます。 ロールバックを発行すると、トランザクション全体がロールバックされます。 セーブポイントは、transaction.rollback()
によって実行される完全なロールバックではなく、きめ細かいロールバックを実行する機能を提供します。
atomic()デコレータがネストされると、部分的なコミットまたはロールバックを可能にするセーブポイントが作成されます。 以下で説明する関数ではなく、 atomic()を使用することを強くお勧めしますが、これらは引き続きパブリックAPIの一部であり、非推奨にする予定はありません。
これらの各関数は、using
引数を取ります。これは、動作が適用されるデータベースの名前である必要があります。 using
引数が指定されていない場合は、"default"
データベースが使用されます。
セーブポイントは、 django.db.transaction の3つの関数によって制御されます。
- savepoint(using=None)
- 新しいセーブポイントを作成します。 これは、「良好な」状態にあることがわかっているトランザクションのポイントを示します。 セーブポイントID(
sid
)を返します。
- savepoint_commit(sid, using=None)
- セーブポイント
sid
を解放します。 セーブポイントが作成されてから実行された変更は、トランザクションの一部になります。
- savepoint_rollback(sid, using=None)
- トランザクションをセーブポイント
sid
にロールバックします。
セーブポイントがサポートされていない場合、またはデータベースが自動コミットモードの場合、これらの関数は何もしません。
さらに、ユーティリティ関数があります。
- clean_savepoints(using=None)
- 一意のセーブポイントIDを生成するために使用されるカウンタをリセットします。
次の例は、セーブポイントの使用法を示しています。
from django.db import transaction
# open a transaction
@transaction.atomic
def viewfunc(request):
a.save()
# transaction now contains a.save()
sid = transaction.savepoint()
b.save()
# 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()
セーブポイントは、部分的なロールバックを実行することにより、データベースエラーから回復するために使用できます。 atomic()ブロック内でこれを行っている場合でも、下位レベルで状況を処理したことがわからないため、ブロック全体がロールバックされます。 これを防ぐために、次の関数を使用してロールバック動作を制御できます。
- get_rollback(using=None)
- set_rollback(rollback, using=None)
ロールバックフラグをTrue
に設定すると、最も内側のアトミックブロックを終了するときに強制的にロールバックされます。 これは、例外を発生させずにロールバックをトリガーするのに役立つ場合があります。
False
に設定すると、このようなロールバックを防ぐことができます。 その前に、トランザクションを現在のアトミックブロック内の正常なセーブポイントにロールバックしたことを確認してください。 そうしないと、原子性が失われ、データが破損する可能性があります。
データベース固有の注意事項
SQLiteのセーブポイント
SQLiteはセーブポイントをサポートしていますが、sqlite3
モジュールの設計上の欠陥により、セーブポイントはほとんど使用できません。
自動コミットが有効になっている場合、セーブポイントは意味がありません。 無効にすると、sqlite3
はセーブポイントステートメントの前に暗黙的にコミットします。 (実際、SELECT
、INSERT
、UPDATE
、DELETE
、REPLACE
以外のステートメントの前にコミットします。)このバグには2つの結果:
MySQLでのトランザクション
MySQLを使用している場合、テーブルはトランザクションをサポートする場合とサポートしない場合があります。 MySQLのバージョンと使用しているテーブルタイプによって異なります。 (「テーブルタイプ」とは、「InnoDB」や「MyISAM」などを意味します。)MySQLトランザクションの特性はこの記事の範囲外ですが、MySQLサイトには MySQLトランザクションに関する情報があります。
MySQLセットアップがトランザクションをサポートしない場合、Djangoは常に自動コミットモードで機能します。ステートメントは呼び出されるとすぐに実行され、コミットされます。 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()
によって行われた変更は、その操作自体でエラーが発生していなくても失われます。
セーブポイントのロールバック
セーブポイントを使用して、ロールバックの範囲を制御できます。 失敗する可能性のあるデータベース操作を実行する前に、セーブポイントを設定または更新できます。 そうすれば、操作が失敗した場合に、トランザクション全体ではなく、問題のある単一の操作をロールバックできます。 例えば:
a.save() # Succeeds, and never undone by savepoint rollback
sid = transaction.savepoint()
try:
b.save() # Could throw exception
transaction.savepoint_commit(sid)
except IntegrityError:
transaction.savepoint_rollback(sid)
c.save() # Succeeds, and a.save() is never undone
この例では、b.save()
が例外を発生させた場合、a.save()
は元に戻されません。