문제

카운터에 대한 매우 간단한 데이터 모델을 구현하려고합니다.

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

누군가가 오면 VisitType와 VisitDate와 일치하는 행을 찾을 것입니다. 이 행이 존재하지 않으면 카운터 = 0으로 생성됩니다.

그런 다음 카운터를 증가시키고 저장합니다.

내 관심사는이 과정이 완전히 인종이라는 것입니다. 두 가지 요청이 동시에 엔터티가 있는지 확인할 수 있으며 둘 다를 만들 수 있습니다. 카운터를 읽고 결과를 저장하는 것 사이에서 다른 요청이 발생하여 증가 할 수 있습니다 (카운트 손실이 발생).

지금까지 나는 Django 문서 나 튜토리얼에서 이것에 대한 좋은 방법을 실제로 찾지 못했습니다 (실제로 자습서가 투표 부분에 레이스 조건이있는 것처럼 보입니다).

안전하게 어떻게해야합니까?

도움이 되었습니까?

해결책

이것은 약간의 해킹입니다. 원시 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()

다른 팁

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 기반-온-존재하는 필드

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

카운터를 진정으로 정확하게 원한다면 트랜잭션을 사용할 수 있지만 필요한 동시성 금액은 실제로 상당한 부하로 응용 프로그램과 데이터베이스를 아래로 끌어냅니다. 대신 더 많은 메시징 스타일 접근 방식을 사용하고 카운터를 증가시키려는 방문마다 테이블에 카운트 레코드를 계속 덤프하십시오. 그런 다음 총 방문 횟수를 원할 때 방문 테이블에서 계산됩니다. 또한 방문을 합산 한 다음 부모 테이블에 보관할 수있는 배경 프로세스를 하루에 여러 번 실행할 수도 있습니다. 공간을 절약하기 위해 아동 방문 테이블에서 요약 한 레코드를 삭제합니다. 동일한 자원 (카운터)에 대해 여러 에이전트가없는 경우 동시성 비용을 막을 수 있습니다.

패치를 사용할 수 있습니다 http://code.djangoproject.com/ticket/2705 지원 데이터베이스 레벨 잠금의 경우.

패치를 사용하면이 코드는 원자가됩니다.

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

두 가지 제안 :

모델에 고유 한 _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이 잠금/동기화를 직접 지원한다고 생각하지 않습니다. 거래 문서를 사용할 수 있습니다 여기.

데이터베이스를 동시 레이어로 사용하지 않는 이유는 무엇입니까? 기본 키 또는 고유 한 제약 조건 테이블을 방문하여 VisitType 및 VisitDate에 추가하십시오. 내가 착각하지 않았다면 Django는 데이터베이스 모델 클래스에서 이것을 정확하게 지원하지 않거나 적어도 예를 보지 못했습니다.

제약 조건/키를 테이블에 추가 한 후에는 다음과 같습니다.

  1. 행이 있는지 확인하십시오. 그렇다면 가져 오십시오.
  2. 행을 삽입하십시오. 오류가 없으면 괜찮고 계속 진행할 수 있습니다.
  3. 오류가 있으면 (즉, 레이스 조건) 행을 다시 가져 오십시오. 행이 없으면 진정한 오류입니다. 그렇지 않으면 괜찮습니다.

이런 식으로 그렇게하는 것은 불쾌하지만, 충분히 빠르며 대부분의 상황을 다룰 것입니다.

이러한 종류의 인종 조건을 피하기 위해 데이터베이스 트랜잭션을 사용해야합니다. 트랜잭션을 사용하면 "전부 또는 전혀"기반에 카운터를 작성, 읽기, 증분 및 저장하는 전체 작업을 수행 할 수 있습니다. 잘못되면 모든 것이 롤백되고 다시 시도 할 수 있습니다.

장고를 확인하십시오 문서. 트랜잭션 중간웨어가 있거나 뷰 또는 방법 주위에 데코레이터를 사용하여 트랜잭션을 생성 할 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top