64

Django で単純なカウンターをアトミックにインクリメントしようとしています。私のコードは次のようになります。

from models import Counter
from django.db import transaction

@transaction.commit_on_success
def increment_counter(name):
    counter = Counter.objects.get_or_create(name = name)[0]
    counter.count += 1
    counter.save()

私が Django を正しく理解していれば、関数をトランザクションでラップし、インクリメントをアトミックにする必要があります。しかし、それは機能せず、カウンターの更新に競合状態があります。このコードをスレッドセーフにするにはどうすればよいですか?

4

6 に答える 6

106

F 式を使用します。

from django.db.models import F

いずれかでupdate():

Counter.objects.get_or_create(name=name)
Counter.objects.filter(name=name).update(count=F("count") + 1)

またはオブジェクトインスタンスで:

counter, _ = Counter.objects.get_or_create(name=name)
counter.count = F("count") + 1
counter.save(update_fields=["count"])

指定update_fieldsしないと、モデルの他のフィールドで競合状態が発生する可能性があります。

F 式を使用することで回避される競合状態に関する注記が公式ドキュメントに追加されました。

于 2009-10-21T06:43:45.020 に答える
18

Django 1.4 では、SELECT ... FOR UPDATE句がサポートされており、データベース ロックを使用して、データが誤って同時にアクセスされないようにします。

于 2012-01-17T21:26:25.243 に答える
16

設定時にカウンターの値を知る必要がない場合は、一番の答えが間違いなく最善の策です。

counter, _ = Counter.objects.get_or_create(name = name)
counter.count = F('count') + 1
counter.save()

これは、データベースに の値に 1 を追加するように指示します。これは、count他の操作をブロックすることなく完全にうまく実行できます。欠点は、何を設定したかを知る方法がないことcountです。2 つのスレッドが同時にこの関数にヒットすると、両方とも同じ値が表示され、データベースに 1 を追加するように指示されます。データベースは予想どおり 2 を追加することになりますが、どちらが最初に実行されたかはわかりません。

今すぐカウントを気にする場合は、select_for_updateEmil Stenstrom が参照しているオプションを使用できます。これは次のようになります。

from models import Counter
from django.db import transaction

@transaction.atomic
def increment_counter(name):
    counter = (Counter.objects
               .select_for_update()
               .get_or_create(name=name)[0]
    counter.count += 1
    counter.save()

これにより、現在の値が読み取られ、トランザクションが終了するまで一致する行がロックされます。一度に読み取れるワーカーは 1 つだけです。select_for_updateの詳細については、ドキュメントを参照してください。

于 2017-09-29T19:14:38.197 に答える
8

シンプルに保ち、@Oduvanの答えに基づいて構築します:

counter, created = Counter.objects.get_or_create(name = name, 
                                                 defaults={'count':1})
if not created:
    counter.count = F('count') +1
    counter.save()

ここでの利点は、オブジェクトが最初のステートメントで作成された場合、それ以上の更新を行う必要がないことです。

于 2013-09-21T20:44:27.533 に答える
6

ジャンゴ1.7

from django.db.models import F

counter, created = Counter.objects.get_or_create(name = name)
counter.count = F('count') +1
counter.save()
于 2015-01-30T03:53:05.723 に答える
-2

または、永続オブジェクトではなくカウンターのみが必要な場合は、C で実装されている itertools カウンターを使用できます。GIL は必要な安全性を提供します。

――サイ

于 2010-08-26T07:33:45.590 に答える