Last updated
Last updated
여러 사용자가 같은 데이터에 접근할 때, 데이터 무결성을 보장하기 위해 데이터에 접근(쓰기/읽기)하지 못하도록 데이터를 잠그는(Lock) 행위를 말한다.
멀티 쓰레드 환경에서 동시에 특정 데이터가 수정된다면 데이터 무결성이 깨지는 문제가 발생할 수 있다.
간단한 예시로, 쇼핑몰에서 재고가 1개 남았으며 상품 구매 트랜잭션에서는 다음의 순서로 쿼리를 수행한다고 하자.
그리고 거의 동시에 요청된 사용자 A, B의 주문에 의해 다음의 순서로 쿼리가 처리된다고 하자.
재고는 1개인데 두 사람 모두 구매에 성공하고 말았다. 이는 하나의 재고에 대해 중복된 주문이 생성된 것으로 명백히 데이터 무결성이 침해된 것이다.
이러한 문제 상황의 예방을 위해 동시성 제어
를 해야 하고, 이를 위한 방법이 바로 데이터베이스에 락(Lock)
을 거는 것이다.
락의 종류로는 두 가지가 있다.
낙관적 락은 말 그대로 낙관적으로 생각하는 잠금 기법이다.
낙관적 락에서는 데이터 갱신 시 경합(충돌)이 발생하지 않을 것이라고 가정한다.
따라서, 데이터베이스를 잠그는 대신 버전 정보를 관리하며 데이터 갱신 시 자신이 조회한 데이터와 버전 일치 여부를 확인하고, 일치한다면 데이터 갱신과 함께 버전 정보도 갱신한다. 일치하지 않는다면 데이터 갱신 내역은 롤백(RollBack)한다.
낙관적 락은 동시에 데이터가 갱신될 일이 거의 없는 경우
사용한다.
비관적 락은 말 그대로 비관적으로 생각하는 잠금 기법이다.
비관적 락에서는 데이터 갱신 시 경합(충돌)이 발생할 확률이 높다고 가정한다.
따라서, 데이터를 갱신할 때 해당 DB/테이블/레코드/필드를 잠근다.
해당 데이터를 갱신하려는 다른 사용자는 잠금이 해제될 때까지 대기한다.
비관적 락은 동시에 데이터가 갱신될 일이 잦은 경우
사용한다.
주로 멀티 쓰레드 환경에서는 동시에 데이터 갱신을 요청하는 일이 많다. 따라서, 비관적 락이 주로 사용된다.
비관적 락은 다시 공유 락(Shared Lock)
과 배타 락(Exclusive Lock)
으로 나뉜다.
공유 락은 데이터를 안전하게 읽기 위한 락이다.
해당 데이터를 읽는 것(SELECT)만 허용하고, 쓰는 것(INSERT/UPDATE/DELETE)은 막는다.
공유 락 끼리는 동시 접근이 가능하다. 여러 사용자가 동시에 데이터를 조회하는 것은 무결성을 침해하지 않기 때문이다.
배타 락은 데이터를 안전하게 쓰기 위한 락이다.
해당 데이터를 읽는 것, 쓰는 것을 모두 막는 락이다.
배타 락은 공유 락과도, 배타 락 끼리도 동시 접근이 불가능하다. 데이터를 새로 쓸 때에는 조회하거나 동시에 수정하는 행위 모두 무결성을 침해할 수 있기 때문이다.
락은 DB, 파일, 하나의 행(레코드), 하나의 필드 단위로 적용할 수 있다.
큰 범위에 적용할 수록 구현이 단순해지지만 성능이 저하된다. (병행 가능성 낮아짐)
반대로 작은 범위에 적용할 수록 구현이 복잡해지지만 성능이 좋아진다. (병행 가능성 높아짐)
접근하려는 데이터에 락이 걸려 있어 대기하는 상태를 말한다.
해당 데이터의 락이 해제되면(진행 중이던 트랜잭션이 commit되면 보통 해제됨) 블로킹도 해제된다.
서로 다른 락이 서로 다른 데이터 제어권을 선점한 상태에서 상대의 데이터를 갱신하려 할 때, 누구도 제어권을 얻지 못해 영원히 블로킹 상태가 되는 경우를 말한다.
A가 데이터1에 락을 걸고, B가 데이터2에 락을 걸고, A는 데이터2를 요구하고, B는 데이터1을 요구하는 경우를 생각해보자. 결국 A와 B 모두 자신의 락을 해제하지 않고 상대의 락이 해제되길 기다리므로 누구도 트랜잭션을 완료할 수 없다.
데드락은 극심한 성능 저하(심하면 서버 장애)로 이어지므로, 다음과 같은 해결방법을 시도해 최대한 해결하는 것이 좋다.
트랜잭션이 너무 오래 지속되면 타임아웃 예외를 발생시키도록 하여 교착상태를 강제종료한다.
데이터의 접근 순서를 최대한 동일하게 유지한다. 위의 예시에서는 A와 B 모두 데이터1, 2를 사용해야 하므로, 둘 다 1→2 순서로 사용하도록 하면 데드락이 발생할 일이 없다.
Lock은 데이터 무결성 보장을 위해 약간의 성능 저하를 감내하는 기법이다. 성능 저하를 최소화하기 위해서는 트랜잭션을 최대한 효율적으로 설계하는 방법 뿐이다.
자신이 주로 사용하는 DBMS에서 어떤 Lock을 지원하는지 알고 있는 것이 좋다.