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=を正しく理解していれば、これはトランザクションで関数をラップし、インクリメントをアトミックにする必要があります。しかし、機能せず、カウンタの更新に競合状態があります。このコードはどのように実行できますか?スレッドセーフにする?
Django 1.1 の新機能
Counter.objects.get_or_create(name = name)
Counter.objects.filter(name = name).update(count = F('count')+1)
または F式 を使用:
counter = Counter.objects.get_or_create(name = name)
counter.count = F('count') +1
counter.save( update_fields=["count"] )
更新するフィールドを必ず指定してください。そうしないと、モデルの他の可能なフィールドで競合状態が発生する可能性があります!
このアプローチに関連する 競合状態に関するトピック が公式ドキュメントに追加されました。
Django 1.4には SELECT ... FOR UPDATEのサポート 句があり、データベースロックを使用して、誤ってデータが同時にアクセスされないようにします。
カウンターの値を設定するときにその値を知る必要がない場合は、トップの答えが間違いなく最善の策です。
counter = Counter.objects.get_or_create(name = name)
counter.count = F('count') + 1
counter.save()
これは、データベースにcount
の値に1を追加するように指示します。これは、他の操作をブロックすることなく完全にうまく機能します。欠点は、設定したcount
を知る方法がないことです。 2つのスレッドがこの関数を同時にヒットすると、両方が同じ値を参照し、両方にdbに1を追加するように指示します。dbは期待どおりに2を追加することになりますが、どちらが先に実行されたかはわかりません。
カウントを今すぐ気にする場合は、select_for_update
Emil 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の詳細については the docs を参照してください。
シンプルに保ち、@ Oduvanの答えに基づいて構築します。
counter, created = Counter.objects.get_or_create(name = name,
defaults={'count':1})
if not created:
counter.count = F('count') +1
counter.save()
ここでの利点は、オブジェクトが最初のステートメントで作成された場合、それ以上の更新を行う必要がないことです。
Django 1.7
from Django.db.models import F
counter, created = Counter.objects.get_or_create(name = name)
counter.count = F('count') +1
counter.save()