2009-10-21 21 views
39

Django'da basit bir sayıcıyı atomik olarak artırmaya çalışıyorum. Benim kod şöyle görünür:Django'da bir sayacın atomik artışı

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

Ben doğru Django anladıysam, bu bir işlemde işlevini sarın ve artım atomik yapmalıdır. Ama işe yaramıyor ve karşı güncellemede bir yarış durumu var. Bu kod nasıl thread-safe yapılabilir?

+0

Hangi veritabanını kullanıyorsunuz? –

+0

Bana göre yarış koşullarından kaçınmak için '+ =' kullanmama gibi bir israfa benziyor. Python kullanıcıları zaten 'a + = b' ve' a = a + b' arasında bir fark olduğunu bilmeli, bu yüzden neden kullanmıyorsunuz? Belki bazı önbellek verileriyle çakışır mı? Emin değil. – aliqandil

cevap

62

New in Django 1.1

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

veya an F expression kullanarak:

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

bu bir commit_on_success yönteminde sarılmalıdır? – alexef

+6

Bunun ile ilgili bir sorun, daha sonra güncelleştirilmiş değere ihtiyacınız varsa, veritabanından getirmeniz gerekir. Bazı durumlarda, kimlik üretimi gibi, bu yarış koşullarına neden olabilir. Örneğin, iki iş parçacığı bir kimliği atomik olarak artırabilir (1'den 3'e kadar diyelim), ancak sonra her ikisi de geçerli değer için sorgular ve 3'ü alır, eklemeyi dener, patlamaya… Sadece düşünecek bir şey. – Bialecki

+0

İkinci sürümde, neden get_or_create için kwarg varsayılanlarını kullanmıyorsunuz ve sonra F nesnesini 'eğer oluşturulmuş 'bloğun içine koydunuz? Yaratılış durumunda daha hızlı olmalı, değil mi? İleri gittim ve ne demek istediğimi söyleyemem. – mlissner

-3

Ya da sadece bir sayacı değil sağlayacaktır C. GIL uygulanan sen itertools sayacı kullanabileceğiniz bir kalıcı nesne istiyorsanız gerekli güvenlik.

--Sai

Django 1.4 olarak
+0

Asker, veritabanındaki bir alanın atomik olarak nasıl artırılacağını özellikle soruyordu. – slacy

+0

Ditto slacy adlı kullanıcının yorumu –

+0

Bu oyuncunun savunmasında, açık bir şekilde belirtilmemiştir. Açıkça belli ki amaçlanmış. –

14

veri olduğundan emin yanlışlıkla aynı anda erişir hale getirmek için veritabanı kilitleri kullanılarak, support for SELECT ... FOR UPDATE hükümler vardır.

+0

Bu işlem, process.commit_on_success içinde bloğu birleştirmekle birlikte sona eren çözümdü. – Bialecki

4

@ Oduvan cevabı üzerine basit ve bina tutulması:

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

Buradaki avantaj nesne ilk açıklamada oluşturulmuşsa, başka güncellemeler yapmak zorunda kalmamasıdır.

5

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() 
4

ayarlamadan zaman, üst cevabı kesinlikle en iyi bahis sayacın değerini bilmek gerekir yoksa:

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

Bu söyler senin veritabanı, diğer işlemleri engellemeden mükemmel bir şekilde yapabileceği count değerine 1 ekleyecektir. Bunun dezavantajı, yeni ayarlanmış olan count'un ne olduğunu bilmenin bir yolu olmamasıdır. İki iş parçacığı eşzamanlı olarak bu işleve basarsa, ikisi de aynı değeri görür ve her ikisi de db'yi 1'e eklemesini söyler. Db beklendiği gibi 2 ekleyerek sona erer, ancak hangisinin ilk başladığını bilemezsiniz.

Şu anda sayımla ilgileniyorsanız, Emil Stenstrom tarafından başvurulan select_for_update seçeneğini kullanabilirsiniz.

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

Bu akım değeri ve hareket sonuna kadar satır eşleşen kilitleri okur: İşte böyle böyle görünüyor budur. Artık sadece bir işçi bir anda okuyabiliyor. Select_for_update hakkında daha fazla bilgi için bkz. the docs.

+0

Bu cevap en iyi açıklamaya sahiptir. Bunu okuyana kadar ben değildim ki "sayım = F ('sayım') + 1 'çalışacaktı –