このデータベース トランザクションを外部 API 呼び出し (S3 バケットの存在を確認する) と組み合わせる必要があることを除いて、この回答に沿った代替get_or_create
メソッドがあるとします。データベース レベル (MySQL InnoDB) で一意の制約があるとします。イベントレットを使用して、複数の Gunicorn ワーカー プロセス内でこのコードを実行しています。
@transaction.commit_on_success
def get_or_create_with_func(obj, func_to_call=None, **kwargs):
try:
result, created = obj.objects.create(**kwargs), True
except IntegrityError:
transaction.commit()
result, created = obj.objects.get(**kwargs), False
func_result = None
if func_to_call is not None and created:
func_result = func_to_call()
return result, created, func_result
トランザクションの成功は、func_to_call
(例外を発生させずに) 正常に戻ることが条件です。例外が発生した場合、明らかにトランザクションはロールバックします。
私が確信していないのは、2 つのスレッドが同時にこの関数を使用している場合create
、データベースにエントリを保存するか (それIntegrityError
が発生し、呼び出しがオブジェクトを返す)、終了get
するまで「待機」する必要があるかどうかです。func_to_call
?
基本的に、上記の関数を次のように呼び出して、このシナリオを想像しています。
query_result, created, func_result = get_or_create_with_func(MyModel, external_api_func, model_column=some_value)
- スレッド 1 と 2 の両方
get_or_create_with_func
がほぼ同時に呼び出されます。 - 両方とも、経由でエントリを作成しようとします
obj.objects.create
- スレッド 1 がそれを正常に作成したとしましょう。次に、try/except ブロックを終了してから を実行し
func_to_call
ます。 - スレッド 2 もオブジェクトの作成を試みます。
- 理想的には、スレッド 2 は
IntegrityError
. - スレッド 1 は の呼び出し
func_to_call
を終了しますが、例外は発生しません。 - スレッド 2 は「コミット」する (スレッド 1 も既にコミットを終了している) ため、 を呼び出して
get
、スレッド 1 によって書き込まれた DB オブジェクトを期待どおりに取得できます。
また
- スレッド 1 が呼び出さ
func_to_call
れ、例外が発生します。 - スレッド 1 により、django はトランザクションをロールバックするため、DB エントリは存在しません。
IntegrityError
スレッド2 は引き続きget
.
私の質問は、ステップ 4 でcreate
、テーブルの一意の制約が原因で失敗するか、それともスレッド 1 が実際にはまだトランザクションをコミットしていないため (func_to_call
かなり遅いとしましょう)、成功するでしょうか?