web-dev-qa-db-ja.com

djangoのカウンターのアトミック増分

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

50

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"] )

更新するフィールドを必ず指定してください。そうしないと、モデルの他の可能なフィールドで競合状態が発生する可能性があります!

このアプローチに関連する 競合状態に関するトピック が公式ドキュメントに追加されました。

82
Oduvan

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

16
Emil Stenström

カウンターの値を設定するときにその値を知る必要がない場合は、トップの答えが間違いなく最善の策です。

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 を参照してください。

10
Xephryous

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

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

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

7
mlissner

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()
5
derevo