0

accountsすべてのユーザーの口座残高 (利用可能なお金) を格納する私の django プロジェクトにこのモデルがあります。ユーザーのアカウントからのほとんどすべての控除の前に、金額チェック、つまりユーザーが x の金額以上を持っているかどうかのチェックが行われます。はいの場合は、先に進んで金額を差し引いてください。

account = AccountDetails.objects.get(user=userid)
if int(account.amount) >= fare:
    account.amount = account.amount-fare
    account.save()

.get()ここで、競合状態を回避できるように、最初のステートメントにロックを入れたいと思います。ユーザーがリクエストを 2 回行うと、アプリケーションは上記のコードを同時に 2 回実行し、一方のリクエストがもう一方のリクエストをオーバーライドします。

select_for_update()がまさに私が望むことを行うことがわかりました。トランザクションが終了するまで行をロックします。

account = AccountDetails.objects.select_for_update().get(user=userid)

しかし、これは Django 1.4 以降でしか利用できません。私はまだ Django 1.3 を使用しており、新しいバージョンに移行することは今のところできません。現在の Django バージョンでこれをどのように達成できますか?

4

1 に答える 1

1

生のSQLを使用する必要があるようです。私は現在のコードに目を通しましたが、SQL を記述するよりも、これを自分でバックポートしようとする方が面倒だと思います。

account = AccountDetails.objects.raw(
    "SELECT * FROM yourapp_accountdetails FOR UPDATE WHERE user = %s", [user.id]
)

便宜上、コードを DRY に保つために、これをメソッドとして AccountDetails モデルなどに追加できます。

class AccountDetails(models.Model):

    @classmethod
    def get_locked_for_update(cls, user):
        return cls.objects.raw(
            "SELECT * FROM yourapp_accountdetails FOR UPDATE WHERE user = %s", [user.id]
        )

yourappstartapp を実行したときに指定したアプリケーションの名前です。AccountDetails に何らかのユーザーモデルへの外部キー関係があると仮定しています。

Django 1.5 での select_for_update の現在の実装は次のようになります。

def select_for_update(self, **kwargs):
    """
    Returns a new QuerySet instance that will select objects with a
    FOR UPDATE lock.
    """
    # Default to false for nowait
    nowait = kwargs.pop('nowait', False)
    obj = self._clone()
    obj.query.select_for_update = True
    obj.query.select_for_update_nowait = nowait
    return obj

これは非常に単純なコードです。クエリ オブジェクトにいくつかのフラグを設定するだけです。ただし、これらのフラグは、クエリの実行時には何の意味もありません。そのため、ある時点で生の SQL を記述する必要があります。この時点では、 AccountDetails モデルに対するクエリのみが必要です。ですから、とりあえずそこに置いてください。後で別のモデルで必要になるかもしれません。そのとき、モデル間でコードを共有する方法を決定する必要があります。

于 2013-07-15T10:09:54.980 に答える