데이터베이스 동시성 제어: 비관적 락 vs 낙관적 락
데이터베이스의 동시성 제어는 데이터 정합성을 보호하고 성능을 확보하기 위한 중요한 과제입니다. 이를 해결하는 대표적인 두 가지 전략으로 비관적 락(Pessimistic Lock)과 낙관적 락(Optimistic Lock)이 있습니다. 두 전략은 데이터 충돌을 바라보는 관점의 차이에서 시작하며, 각각의 특징과 장단점을 알아봅니다.
비관적 락과 낙관적 락 비교
| 항목 | 비관적 락 (Pessimistic Lock) | 낙관적 락 (Optimistic Lock) |
|---|---|---|
| 철학 | “충돌이 일어날 가능성이 높으니, 미리 잠그고 안전하게 작업하자.” | “웬만하면 충돌이 안 나니까 그냥 진행하고, 나중에 문제가 생기면 그때 처리하자.” |
| 작동 방식 | 트랜잭션 시작 시 필요한 데이터에 X-lock(배타 잠금)을 걸어 다른 트랜잭션의 접근을 막습니다. | 트랜잭션 동안 락을 걸지 않으며, 커밋 직전에 데이터가 수정되지 않았는지 검사합니다. |
| 구현 기술 | SELECT ... FOR UPDATE SQL 구문을 사용합니다. Spring JPA에서는 @Lock(PESSIMISTIC_WRITE)를 사용합니다. | 버전(version) 필드를 테이블에 추가하고, WHERE version=? 조건을 붙여 업데이트합니다. 성공 시 버전을 +1 증가시킵니다. |
| 장점 | 데이터 충돌 가능성이 원천적으로 차단됩니다. 구현이 비교적 단순할 수 있습니다. | 락 대기가 없어 초당 처리량(TPS)이 높습니다. Deadlock 위험이 없습니다. |
| 단점 | 락 대기 시간으로 인해 전체 처리 속도가 느려질 수 있습니다. Deadlock 발생 확률이 높아집니다. | 충돌 발생 시 재시도 로직이 필요하며, 충돌률이 높으면 오히려 비효율적일 수 있습니다. |
| 적합 환경 | 상품 재고 감소, 좌석 예약 등 충돌 빈도가 높은 경우. DB 단일 노드 서비스. | 데이터 충돌이 드문 경우(예: 마이페이지 수정). 분산 시스템이나 다중 서버 환경. |
주요 세부 비교
충돌 처리 방식
비관적 락은 트랜잭션이 시작될 때 데이터를 독점적으로 잠그기 때문에, 다른 트랜잭션은 락이 해제될 때까지 대기해야 합니다. 이는 동시 요청이 들어와도 안전하게 순차 처리가 가능함을 보장합니다.
반면, 낙관적 락은 락 없이 처리를 진행하고 커밋 시점에 버전을 비교하여 다른 트랜잭션의 개입 여부를 확인합니다. 충돌이 감지되면(버전 불일치) 해당 트랜잭션은 예외를 발생시키고 실패합니다. 이 경우, 서비스 레이어에서 자동 재시도 로직을 구현하여 문제를 완화해야 합니다.
성능 및 Deadlock 위험
일반적으로 낙관적 락은 락 대기가 없어 비관적 락보다 TPS(초당 처리량)가 높습니다.
비관적 락은 락을 오래 유지하여 대기 시간을 늘릴 수 있으며, 여러 자원에 락을 획득하는 순서가 일관되지 않으면 Deadlock(교착 상태)이 발생할 수 있습니다. Deadlock은 두 트랜잭션이 서로 상대방의 락 해제를 기다리며 무한 대기하는 상태를 의미합니다.
실무 적용 가이드
핵심 자원(돈, 재고)을 다루거나 경합이 잦은 상황에서는 데이터 정합성이 중요하므로 비관적 락 또는 조건부 UPDATE 단일 쿼리 패턴이 권장됩니다.
- 비관적 락 예시:
1
SELECT stock FROM product WHERE id = :pid FOR UPDATE;
- 낙관적 락 예시:
1 2 3
UPDATE product SET stock = stock - 1, version = version + 1 WHERE id = :pid AND version = :current_version;
결론
실무 환경에서는 하나의 전략만을 고수하기보다, 두 전략을 혼합하거나 DB의 무결성 제약(Unique 제약), 조건부 UPDATE 등 다양한 기술을 함께 고려하는 것이 중요합니다. 예를 들어, 경합이 심한 자원에는 비관적 락을, 비교적 경합이 적은 리소스에는 낙관적 락을 적용하여 시스템의 안정성과 성능의 균형을 맞추는 것이 바람직합니다.