Frage

Ich versuche zu implementieren (was ich denke, ist) ein ziemlich einfaches Datenmodell für einen Zähler:

class VisitorDayTypeCounter(models.Model):
    visitType = models.CharField(max_length=60)
    visitDate = models.DateField('Visit Date')
    counter = models.IntegerField()

Wenn jemand durchkommt, wird es für eine Zeile suchen, die die visitType und visitDate übereinstimmt; Wenn diese Zeile nicht vorhanden ist, wird es mit Zählern erstellt wird = 0 ist.

Dann erhöhen wir die Zähler und speichern.

Meine Sorge ist, dass dieser Prozess völlig ein Rennen ist. Zwei Anfragen könnten gleichzeitig prüfen, ob das Unternehmen gibt, und beide könnten es schaffen. Zwischen den Zählerstand und das Ergebnis speichern, könnte eine weitere Anforderung kommen durch und erhöht sie (was zu einer mehr mitzählen).

Ich habe festgestellt Bisher nicht wirklich eine gute Möglichkeit, um diesen entweder in der Django-Dokumentation oder im Tutorial (in der Tat, es sieht aus wie das Tutorial eine Race-Bedingung in der Abstimmung eines Teil davon hat).

Wie kann ich das tun sicher?

War es hilfreich?

Lösung

Dies ist ein bisschen wie ein Hack. Die rohe SQL wird Ihr Code weniger portabel machen, aber es wird der Race-Bedingung auf dem Zählerinkrement loszuwerden. Theoretisch sollte dies den Zähler jedes Mal, wenn Sie eine Abfrage tun erhöhen. Ich habe nicht getestet, so sollten Sie die Liste stellen Sie sicher, richtig in der Abfrage interpoliert wird.

class VisitorDayTypeCounterManager(models.Manager):
    def get_query_set(self):
        qs = super(VisitorDayTypeCounterManager, self).get_query_set()

        from django.db import connection
        cursor = connection.cursor()

        pk_list = qs.values_list('id', flat=True)
        cursor.execute('UPDATE table_name SET counter = counter + 1 WHERE id IN %s', [pk_list])

        return qs

class VisitorDayTypeCounter(models.Model):
    ...

    objects = VisitorDayTypeCounterManager()

Andere Tipps

Wie von Django 1.1 können Sie die ORM F () Ausdrücke verwenden.

from django.db.models import F
product = Product.objects.get(name='Venezuelan Beaver Cheese')
product.number_sold = F('number_sold') + 1
product.save()

Weitere Informationen finden Sie in der Dokumentation:

https: / /docs.djangoproject.com/en/1.8/ref/models/instances/#updating-attributes-based-on-existing-fields

https: //docs.djangoproject .com / de / 1.8 / ref / models / Ausdrücke / # django.db.models.F

Wenn Sie wirklich der Zähler genau sein wollen Sie eine Transaktion, sondern die Menge der Parallelität verwenden könnten Ihre Anwendung und Datenbank zieht wirklich unter nennenswerter Last benötigt wird. Stattdessen denken Sie an mit einem Messaging-Stil Ansatz gehen und hält nur für jeden Besuch Zahl Datensätze in eine Tabelle Dumping, wo Sie den Zähler erhöhen wollen würden. Dann, wenn Sie die Gesamtzahl der Besucher wollen eine Zählung auf der Besucher-Tabelle. Sie könnten auch einen Hintergrundprozess, die beliebig oft am Tag liefen, dass die Besuche Summe würde und dann das Speichern in der übergeordneten Tabelle. Um auf Platz zu sparen wäre es löscht auch alle Datensätze aus der Kind-Besuchen Tabelle, die es aufsummiert. Sie werden abgeholzt auf Ihrer Gleichzeitigkeit eine riesige Menge kostet, wenn Sie für die gleichen Ressourcen nicht wetteifern mehrere Agenten (den Zähler).

Sie können mit Patch von http://code.djangoproject.com/ticket/2705 für Datenbank-Level Locking Unterstützung.

Dieser Code Mit dem Patch wird atomarer:

visitors = VisitorDayTypeCounter.objects.get(day=curday).for_update()
visitors.counter += 1
visitors.save()

Zwei Vorschläge:

Fügen Sie einen unique_together zu Ihrem Modell, und wickeln die Erstellung in einem Ausnahmehandler Duplikat zu fangen:

class VisitorDayTypeCounter(models.Model):
    visitType = models.CharField(max_length=60)
    visitDate = models.DateField('Visit Date')
    counter = models.IntegerField()
    class Meta:
        unique_together = (('visitType', 'visitDate'))

Danach konnte man stlll haben eine kleine Race-Bedingung auf die Aktualisierung des Zählers. Wenn Sie genug Verkehr nach wie vor besorgt darüber bekommen, würde ich in Transaktionen für feinkörnige Datenbank Kontrolle empfehlen suchen. Ich glaube nicht, das ORM direkte Unterstützung für Verriegelungs- / Synchronisation hat. Die Transaktionsdokumentation ist verfügbar noreferrer"> hier rel="nofollow.

Warum nicht die Datenbank als die Gleichzeitigkeit Schicht verwenden? Fügen Sie einen Primärschlüssel oder eindeutige Einschränkung der Tabelle zu visitType und visitDate. Wenn mich nicht alles täuscht, hat django nicht genau diese Unterstützung in ihrer Datenbank Modellklasse oder zumindest habe ich nicht ein Beispiel gesehen.

Sobald Sie die Einschränkung / Schlüssel zu der Tabelle hinzugefügt, dann alles, was Sie tun müssen, ist:

  1. prüfen, ob die Zeile vorhanden ist. wenn es ist, ihn zu holen.
  2. die Zeile einzufügen. wenn es keine Fehler, den Sie in Ordnung sind und weitermachen können.
  3. , ob es ein Fehler (d.h. race condition), erneut abzurufen die Zeile. wenn es keine Zeile ist, dann ist es ein echter Fehler. Ansonsten sind Sie in Ordnung.

Es ist böse es auf diese Art und Weise zu tun, aber es scheint schnell genug und würde die meisten Situationen abdecken.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top