25

私は(私が思うに)カウンター用の非常に単純なデータモデルを実装しようとしています:

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

誰かがやってくると、visitTypeとvisitDateに一致する行を探します。この行が存在しない場合は、counter=0で作成されます。

次に、カウンターをインクリメントして保存します。

私の懸念は、このプロセスが完全に人種であるということです。2つのリクエストが同時にエンティティが存在するかどうかを確認し、両方がエンティティを作成する可能性があります。カウンターを読み取ってから結果を保存するまでの間に、別の要求が発生してそれをインクリメントする可能性があります(結果としてカウントが失われます)。

これまでのところ、Djangoのドキュメントでもチュートリアルでも、これを回避する良い方法は見つかりませんでした(実際、チュートリアルの投票部分に競合状態があるようです)。

これを安全に行うにはどうすればよいですか?

4

7 に答える 7

29

Django 1.1 以降、ORM の F() 式を使用できます。

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

詳細については、次のドキュメントを参照してください。

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

https://docs.djangoproject.com/en/1.8/ref/models/expressions/#django.db.models.F

于 2009-12-23T22:35:42.773 に答える
12

本当に正確なカウンターが必要な場合は、トランザクションを使用できますが、必要な同時実行の量により、大きな負荷がかかると、アプリケーションとデータベースが実際に低下します。代わりに、よりメッセージング スタイルのアプローチを採用し、カウンターをインクリメントしたい訪問ごとにカウント レコードをテーブルにダンプし続けることを考えてください。次に、訪問の合計数が必要な場合は、訪問テーブルでカウントを行います。また、訪問を合計して親テーブルに保存するバックグラウンド プロセスを 1 日に何度でも実行することもできます。スペースを節約するために、集計した子訪問テーブルからレコードも削除します。複数のエージェントが同じリソース (カウンター) を争っていなければ、同時実行コストを大幅に削減できます。

于 2008-11-11T14:51:14.103 に答える
5

2 つの提案:

モデルに unique_together を追加し、作成を例外ハンドラーでラップして、重複をキャッチします。

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

この後、カウンターの更新でマイナーな競合状態が発生する可能性があります。それを気にするほどのトラフィックが発生する場合は、トランザクションを調べて、よりきめ細かいデータベース制御を行うことをお勧めします。ORM がロック/同期を直接サポートしているとは思いません。取引書類はこちらから入手できます。

于 2008-11-11T05:56:39.907 に答える
1

データベースを並行性レイヤーとして使用してみませんか?テーブルに主キーまたは一意性制約をvisitTypeおよびvisitDateに追加します。私が間違っていなければ、djangoはデータベースのModelクラスでこれを正確にサポートしていないか、少なくとも例を見たことがありません。

テーブルに制約/キーを追加したら、次のことを行う必要があります。

  1. 行がそこにあるかどうかを確認します。そうである場合は、それをフェッチします。
  2. 行を挿入します。エラーがなければ、問題はなく、先に進むことができます。
  3. エラー(つまり、競合状態)がある場合は、行を再フェッチします。行がない場合、それは本物のエラーです。そうでなければ、あなたは大丈夫です。

この方法でそれを行うのは厄介ですが、それは十分に速いようで、ほとんどの状況をカバーします。

于 2008-11-11T06:21:04.600 に答える
1

これはちょっとしたハックです。生の SQL を使用すると、コードの移植性が低下しますが、カウンターのインクリメントで競合状態が解消されます。理論的には、クエリを実行するたびにカウンターがインクリメントされるはずです。私はこれをテストしていないので、リストがクエリで適切に補間されることを確認する必要があります。

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()
于 2008-11-12T02:58:40.237 に答える
0

この種の競合状態を回避するには、データベース トランザクションを使用する必要があります。トランザクションを使用すると、カウンターの作成、読み取り、インクリメント、および保存の操作全体を「オール オア ナッシング」ベースで実行できます。何か問題が発生した場合は、すべてがロールバックされ、再試行できます。

Django のドキュメントを参照してください。トランザクション ミドルウェアがあるか、ビューまたはメソッドの周りにデコレータを使用してトランザクションを作成できます。

于 2008-11-11T10:52:10.513 に答える