본문 바로가기

소프트웨어-이야기/프로그래밍 언어와 프레임워크

(Django) Django ORM에서 Row Lock 잡기 - select_for_update

 

Row Lock과 SELECT * FOR UPDATE

UPDATE / DELETE 없이, SELECT 만으로 Row Lock을 잡고 싶을 때는, "SELECT * FOR UPDATE" 쿼리를 사용하면 된다.

이렇게 Row Lock을 잡고있는 도중에는 다른 트랜잭션에서 해당 Row를 변경 / 삭제할 수 없다. 

 

select_for_update

Django에서 SELECT * FOR UPDATE 쿼리를 사용할 때는, Django ORM select_for_update 함수를 사용하면 된다. 이 함수는 항상 transaction과 함께 사용된다.

 

 

Lock을 잡으려는데, 이미 Lock이 잡혀있는 경우

일반적으로는, 락이 풀릴 때까지 기다린다. ( 이 경우, DB Connection이 무한정 쌓이고 밀릴 수 있다. )

락이 풀릴때까지 기다리지 않고, 오류를 내뱉고 싶으면 select_for_update에 nowait=True 옵션을 추가하면 된다. 

락이 잡혀있어도, 무시하고 싶으면 skip_locked=True 옵션을 추가하면 된다. 

그래서 nowait 옵션과 skip_locked 옵션은 공존할 수 없다. 

class UserPoint(models.Model):
    id = models.AutoField(primary_key=True)
    user = models.ForeignKey(User)
    total_point = models.IntegerField(default=0)

 

with transaction.atomic():
    user_point = UserPoint.objects.get(pk=id).select_for_update()
    user_point.total_point += point
    user_point.save()

 

주의사항

Django에서 select_for_update를 사용하면, select_related 되어있는 테이블이나, FK으로 연결되어있는 객체 테이블에도 락이 함께 잡힌다.

예를 들면,  아래와 같은 모델에 select_for_update를 적용하게 되면, user 테이블에도 락이 잡힌다.

of 옵션을 주면, 락 잡을 테이블을 정할 수 있다. ( Django 2.0부터 사용할 수 있다 )

그런데 FK로 연결되어있는 레코드의 PK가 변경되지 않는다면, 락이 함께 잡혀도 별 영향은 없어보인다. ( Selecting for Share and Update in PostgreSQL - The Effect of Select For Update on Foreign Keys )

 

참고 글 

[Medium] How to manage concurrency in Django models

[Django] QuerySet - Select For Update