2013-07-15 2 views
0

나는이 accounts 모델을 모든 사용자의 계정 잔액 (사용 가능한 금액)을 저장하는 내 django 프로젝트에 가지고 있습니다. 사용자의 계좌에서 거의 모든 공제는 금액 확인, 즉 사용자가 x 금액 이상을 가지고 있는지 확인합니다. 그렇다면 그 금액을 공제하십시오.select_for_update 종류의 django 1.3에서 경쟁 조건을 피하기 위해

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

이제 그 경쟁 조건을 피할 수 있도록, 첫 .get() 문에 잠금을 넣어합니다. 사용자가 요청을 두 번하고 응용 프로그램이 위의 코드를 두 번 동시에 실행하면 요청 중 하나가 다른 요청을 무시하게됩니다.

나는 select_for_update()이 내가 원하는 것을 정확히 알아 냈습니다. 트랜잭션이 끝날 때까지 행을 잠급니다.

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

는하지만 그것은 단지 장고 1.4에서 사용할 수 이상이고 나는 아직도 장고 1.3을 사용하고 지금 할 수없는 새 버전으로 이동하고있다. 어떤 아이디어라도 현재의 장고 버전에서 어떻게 이것을 할 수 있습니까?

답변

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

yourapp은 startapp를 실행할 때 부여했을 응용 프로그램의 이름입니다. 나는 당신이 어떤 종류의 사용자 모델에 대한 AccountDetails에 외래 키 관계를 가지고 있다고 가정하고있다.

The current implementation of select_for_update on Django 1.5은 다음과 같습니다

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 모델에 대한 쿼리 만 필요합니다. 그래서 지금 당장 거기에 두십시오. 나중에 다른 모델에서 필요할 것입니다. 모델간에 코드를 공유하는 방법을 결정해야합니다.

+0

+1을 모델에 추가하는 방법을 제안합니다. :) 원시 SQL 쿼리를 함께 사용하는 것을 피할 수 있습니까? – Sudipta

+0

업데이트 된 답변보기 나는 대답이 '아니오'라고 생각한다. Django의 최신 버전에서 많은 것을 backporting하지 않아도됩니다. – aychedee

+0

@ayechedee 설명 주셔서 감사합니다. 나는'get_locked_for_update()'를 인스턴스 메소드가 아닌 클래스 메소드로 구현하면 더 좋지 않을까 궁금해했다. 'AccountDetails.objects.get (user = userid) .get_locked_for_update()'보다 'AccountDetails.get_locked_for_update (user)'와 같이 클래스에서 호출하는 것이 더 합리적 일 것입니다. 권리? – Sudipta

관련 문제