이 글은 장고 Docs를 참고해 작성했습니다.
앱을 사용하다 보면 여러 사람이 동시에 같은 row를 조회하게 되는 경우가 생길 수 있다.
가령 고객에게 DB에 있는 상품권을 지급하는 경우 여러 유저가 동시에 상품권을 조회하게 된다면 같은 상품권을 조회하게 될 수 있다는 문제점이 있다. 물론 상품권 조회에 대해 transaction lock을 걸어 놓겠지만 그렇게 되면 동시에 접근한 두 유저 중 한 유저는 에러를 보게 될 것이다.
이런 문제점을 해결하기 위해 사용할 수 있는 쿼리셋이 바로 select_for_update()
이다. 이 쿼리셋을 이용하면 락이 걸린 rows는 건너 뛰거나 아니면 트랜잭션이 끝날 때까지 기다리도록 설정할 수 있다.
select_for_update()를 사용하면 이 쿼리셋을 통해 만들어지는 로우쿼리는 SELECT ... FOR UPDATE
형태가 된다.
skip_locked
nowait
of
select_for_update()
는 조회하는 모든 쿼리에 락을 잡는다. 따라서 select시 참조하는 모든 모델에 락을 잡는다. 그런데 만약 이러한 상황을 원하지 않는다면 of
옵션을 이용해 원하는 objects를 지정해 지정된 objects만 락을 잡도록 설정할 수 있다.transaction.atomic() 블럭 안에서 select_for_update()
를 사용해야 한다.
select_for_update()
는 작동하지 않는다.skip_locked
옵션과 nowait
옵션은 상호 배타적인 관계로, 만약 두 옵션을 모두 사용하게 되면 ValueError가 발생 한다.
select_for_update()
는 null을 참조하는 경우에는 사용할 수 없다.
Person.objects.select_related('hometown').select_for_update().exclude(hometown=None)
select_for_update()
는 eager loading을 한다.
select_for_update()
사용시 where 조건을 걸어 특정 유저 혹은 id에 대한 쿼리를 실행하지 않으면 해당 테이블 전체에 락을 잡아 앱의 성능이 매우 느려지는 이슈가 발생한다.select_for_update()
는 기존의 select
쿼리와 달리 db를 바로 조회하는 것 같다. # 사용 O
order = Order.objects.select_for_update(nowait=False).get(user=user)
# 사용 X
order_qs = Order.objects.select_for_update(nowait=False)
order = order_qs.get(user=user)
잘 읽고 갑니다