문제

다음은 잠재적인 경쟁 조건이 있는 django 뷰의 간단한 예입니다.

# myapp/views.py
from django.contrib.auth.models import User
from my_libs import calculate_points

def add_points(request):
    user = request.user
    user.points += calculate_points(user)
    user.save()

경쟁 조건은 상당히 명확해야 합니다.사용자는 이 요청을 두 번 할 수 있으며 애플리케이션이 잠재적으로 실행될 수 있습니다. user = request.user 동시에 요청 중 하나가 다른 요청을 재정의하게 됩니다.

함수를 가정해보자 calculate_points 상대적으로 복잡하며 단일 항목에 배치할 수 없는 모든 종류의 이상한 항목을 기반으로 계산을 수행합니다. update 저장 프로시저에 넣기가 어려울 수 있습니다.

내 질문은 다음과 같습니다.이와 유사한 상황을 처리하기 위해 django에서는 어떤 종류의 잠금 메커니즘을 사용할 수 있나요?

도움이 되었습니까?

해결책

Django 1.4+ 지원 select_for_update, 이전 버전에서는 원시 SQL 쿼리를 실행할 수 있습니다. select ... for update 기본 DB에 따라 업데이트에서 행을 잠그는 경우 트랜잭션이 끝날 때까지 해당 행으로 원하는 모든 것을 수행 할 수 있습니다. 예를 들어

from django.db import transaction

@transaction.commit_manually()
def add_points(request):
    user = User.objects.select_for_update().get(id=request.user.id)
    # you can go back at this point if something is not right 
    if user.points > 1000:
        # too many points
        return
    user.points += calculate_points(user)
    user.save()
    transaction.commit()

다른 팁

Django 1.1 현재 ORM의 F () 표현식을 사용 하여이 특정 문제를 해결할 수 있습니다.

from django.db.models import F

user = request.user
user.points  = F('points') + calculate_points(user)
user.save()

자세한 내용은 문서를 참조하십시오.

https://docs.djangoproject.com/en/1.8/ref/models/instances/#updating-attributes 기반-온-존재하는 존재

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

데이터베이스 잠금은 여기로가는 방법입니다. Django에 "업데이트를위한 선택"지원을 추가 할 계획이 있습니다 (여기), 그러나 지금은 가장 간단한 것은 RAW SQL을 사용하여 점수를 계산하기 전에 사용자 객체를 업데이트하는 것입니다.


비관적 잠금은 이제 기본 DB (예 : Postgres)가 지원할 때 Django 1.4의 ORM에 의해 지원됩니다. 참조 Django 1.4A1 릴리스 노트.

이런 종류의 작업을 단일 스레드로 처리하는 방법에는 여러 가지가 있습니다.

한 가지 표준 접근 방식은 다음과 같습니다. 먼저 업데이트.행에 대한 배타적 잠금을 확보하는 업데이트를 수행합니다.그런 다음 일을 하십시오.마지막으로 변경 사항을 커밋합니다.이것이 작동하려면 ORM의 캐싱을 우회해야 합니다.

또 다른 표준 접근 방식은 복잡한 계산에서 웹 트랜잭션을 분리하는 별도의 단일 스레드 응용 프로그램 서버를 보유하는 것입니다.

  • 웹 애플리케이션은 채점 요청 대기열을 생성하고 별도의 프로세스를 생성한 다음 이 대기열에 채점 요청을 쓸 수 있습니다.스폰은 Django에 넣을 수 있습니다 urls.py 웹앱 시작시 발생합니다.아니면 따로 넣어도 되지만 manage.py 관리 스크립트.또는 첫 번째 채점 요청을 시도할 때 "필요에 따라" 수행할 수 있습니다.

  • urllib2를 통해 WS 요청을 수락하는 Werkzeug를 사용하여 별도의 WSGI 기반 웹 서버를 생성할 수도 있습니다.이 서버에 대한 포트 번호가 하나만 있는 경우 요청은 TCP/IP에 의해 대기열에 추가됩니다.WSGI 핸들러에 하나의 스레드가 있으면 직렬화된 단일 스레딩을 달성한 것입니다.채점 엔진은 WS 요청이고 어디에서나 실행될 수 있으므로 이는 약간 더 확장성이 뛰어납니다.

또 다른 접근 방식은 계산을 수행하기 위해 획득하고 보유해야 하는 다른 리소스를 확보하는 것입니다.

  • 데이터베이스의 Singleton 개체입니다.고유 테이블의 단일 행을 세션 ID로 업데이트하여 제어권을 확보할 수 있습니다.세션 ID로 업데이트: None 통제를 해제합니다.필수 업데이트에는 다음이 포함되어야 합니다. WHERE SESSION_ID IS NONE 다른 사람이 잠금을 보유하고 있을 때 업데이트가 실패하도록 필터링합니다.이는 본질적으로 경합이 없기 때문에 흥미롭습니다. 즉, SELECT-UPDATE 시퀀스가 ​​아닌 단일 업데이트입니다.

  • 다양한 종류의 세마포어를 데이터베이스 외부에서 사용할 수 있습니다.대기열은 (일반적으로) 낮은 수준의 세마포어보다 작업하기가 더 쉽습니다.

이것은 상황을 과도하게 단순화 할 수 있지만 JavaScript 링크 교체는 어떻습니까? 다시 말해서 사용자가 링크를 클릭하거나 버튼을 클릭하면 링크를 즉시 비활성화 / "회색"으로 링크를 "로드 ..."또는 "제출 요청 ..."정보 등으로 대체하는 JavaScript 함수로 요청을 랩핑 할 때. 비슷한. 그게 당신을 위해 일할까요?

이제 사용해야합니다.

Model.objects.select_for_update().get(foo=bar)
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top