반응형

개요

InnoDB의 잠금에 대해 알아보기 전 비관적 잠금과 낙관적 잠금에 대해 알아보았다.

 

낙관적 잠금

각 트랜잭션이 같은 레코드를 변경할 가능성이 낮다고 낙관적으로 가정하는 상황에서의 잠금이다. 리소스를 잠그지 않고 데이터를 업데이트 한 후, 마지막 커밋 시 충돌을 확인하는 방식이다.

충돌이 감지되면(트랜잭션이 시작되고 끝나는 사이, 다른 트랜잭션이 데이터를 수정) 트랜잭션이 롤백되고 오류를 반환한다.

 

충돌을 어떻게 확인할까?

테이블에 수정 시점을 추적할 수 있는 버전 필드 또는 타임스탬프를 추가하는 것이다.

버전 필드 (Version Field)
낙관적 잠금에서 각 데이터 레코드에 추가되는 필드로, 해당 레코드가 마지막으로 수정된 시점을 추적하는 용도로 사용된다. 레코드가 업데이트 될 때마다 이 버전 번호가 증가하는 형태이다.

 

트랜잭션이 데이터를 읽을 때 버전 필드를 함께 읽고, 트랜잭션이 커밋할 때 버전 번호를 확인하여 일치하는지를 확인한다. 만약 버전 필드 값이 달라졌다면, 다른 트랜잭션에서 데이터를 수정한 것이라 판단하고 트랜잭션을 롤백 또는 재시작한다.

 타임스탬프를 사용할 경우 해당 레코드의 수정 시간을 기재한다. 마찬가지로 트랜잭션이 시작, 종료될 때 레코드의 수정 시간이 달라졌다면 롤백을, 같다면 커밋을 하는 방식이다.

 

-- 1. 데이터 읽을 때 버전 정보도 함께 읽음
SELECT product_id, product_name, price, version
FROM products
WHERE product_id = 1;

-- 2. 수정 시 버전 확인
UPDATE products
SET price = 200.00, version = version + 1
WHERE product_id = 1 AND version = 2;  -- '2'는 읽은 버전 번호

 

낙관적 잠금 장단점

자원에 대한 잠금이 없기때문에, 대기 시간이 짧고, 처리 속도가 향상될 수 있다. 특히 데이터 충돌이 드문 경우에는 트랜잭션이 빠르게 처리된다. 또한 여러 트랜잭션이 동시에 데이터를 읽고 수정할 수 있으므로, 데이터 충돌이 발생하는 상황이 적다면 그만큼 높은 동시성을 유지하게된다.

 

하지만 데이터 충돌이 발생할 경우 트랜잭션을 다시 시도해야 한다.  충돌이 자주 발생하거나 처리 비용이 높은 트랜잭션이라면, 재시도 비용도 증가하게 된다. 또한 충돌에 대한 예외 로직이 필요하다. 변경을 다시 시도하거나, 사용자에게 알림을 주는 등의 별도 처리가 필요하다. 이러한 예외 코드를 잘못 구현하기라도 한다면 데이터 무결성이 손상될수 있으므로 주의해야한다.

 

 

비관적 잠금

각 트랜잭션이 같은 레코드를 변경할 가능성이 높다고 비관적으로 가정하는 상황에서의 잠금이다.즉, 하나의 트랜잭션이 데이터를 조회하는 시점에 락을 걸고, 트랜잭션이 끝날때까지 유지한다.

조회 시점에 잠금을 획득하는 대표적인 방법은 SELECT FOR UPDATE 이다.

 

비관적 잠금의 장단점

트랜잭션의 동시 접근을 확실하게 방지할 수 있기에 데이터의 무결성이 보장된다.

 

하지만 동시성이 떨어져 처리 속도가 느리다. 트랜잭션이 각각의 자원을 점유하는 상태에서 서로의 자원을 요청한다면, 데드락이 발생하게 된다.

 

비관적 잠금 데드락 재현

users 테이블

 

1) 1번 트랜잭션이 users 테이블의 id = 1인 레코드에 대해 비관적 잠금을 설정한다. 트랜잭션은 끝나지 않고, 다른 작업들을 처리하고 있다.

 

select * from users where id = 1 FOR UPDATE;

 

2) 도중 2번 트랜잭션이 시작되었고, users 테이블의 id = 10인 레코드에 대해 비관적 잠금을 설정한다. 마찬가지로 트랜잭션은 끝나지 않고, 다른 작업들을 처리하고 있다.

 

select * from users where id = 10 FOR UPDATE;

 

3) 1번 트랜잭션의 작업 중 유저 정보 일괄 수정 위해 업데이트 쿼리가 나갔지만, 2번 트랜잭션에서 id = 10인 레코드에 대한 잠금으로 인해 '대기상태'에 들어간다.

 

update users set name = '이름';

 

4) 2번 트랜잭션에서도 마찬가지로 유저 정보 일괄 수정 위해 업데이트 쿼리가 나갔지만, 1번 트랜잭션에서 id = 1인 레코드에 대한 잠금으로 인해 '대기상태'에 들어간다.

 

5) 트랜잭션 1, 2번은 데드락 상태에 빠지게 된다.

 

하지만 실제 테스트를 해보니 무기한 데드락이 걸리지 않고 한쪽 트랜잭션을 롤백시키는 것을 볼 수 있는데, 이는 InnoDB에서 제공하는 자동 데드락 탐지 기능에 의해 한쪽 트랜잭션이 롤백되는 것이다.

데드락 상황을 인지하고, 트랜잭션 재시작

 

또한 락에 대한 타임아웃도 설정되어있기에, 락이 걸린 자원을 획득하지 못한다면 무한정 대기상태에 들어가지 않고, 에러 및 롤백 후 트랜잭션 재시작을 시도한다.

Lock wait timeout 초과 시 트랜잭션 재시작

 

잠금에 대한 대기 시간 변수는 innodb_lock_wait_timeout이며 , 기본 50초로 설정되어 있다.

innodb_lock_wait_timeout

 

 

반응형

'DB > MySQL' 카테고리의 다른 글

[MySQL] MySQL 엔진 / 실행 구조 / 버퍼  (0) 2024.10.16

+ Recent posts