본문 바로가기

소프트웨어 이야기/장고와 루비온레일즈

(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과 함께 사용된다.

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


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

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

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

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

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


주의사항

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