데이터베이스 락의 모든 것 - 공유 락, 배타 락, 그리고 분산 락

2026. 2. 1. 18:04·데이터베이스

0. 들어가며

최근에 데이터베이스를 공부하면서 동시에 여러 트랜잭션이 같은 데이터를 처리할 때 발생할 수 있는 문제들을 접하게 되었습니다.
처음에는 단순히 SQL 쿼리와 테이블 설계만 생각했는데 공부를 이어가다 보니 락(Lock)이라는 개념이 얼마나 중요한지 깨닫게 되었습니다.

 

예를 들어 은행 계좌의 잔액을 동시에 수정하거나, 쇼핑몰에서 재고를 차감할 때 락이 없으면 데이터가 꼬일 수 있다는 사실을 알게 되었습니다.

 

이 글에서는 제가 공부하면서 이해하게 된 공유 락, 배타 락, 분산 락에 대해서  정리해보려고 합니다.


1. 락이란 무엇인가 - 동시성 제어의 핵심

락은 동시에 실행되는 여러 트랜잭션이 같은 데이터에 접근할 때  데이터의 일관성을 보장하기 위한 것입니다. 쉽게 설명하자면 

누군가 데이터를 사용하고 있을 때 다른 사람이 간섭하지 못하도록 잠그는 것입니다.

왜 락이 필요할까요?
데이터베이스는 기본적으로 여러 트랜잭션을 동시에 처리합니다.
이는 성능을 위해 필수적인 구조지만, 같은 데이터를 동시에 읽고 쓰면 문제가 발생할 수 있습니다.


 락이 없다면?

예를 들어  계좌 잔액이 100원인 사용자가 있다고 가정해보겠습니다.

 

  • 트랜잭션 T1: 30원 출금
  • 트랜잭션 T2: 50원 출금

두 요청이 거의 동시에 들어올 경우 락이 없다면 트랜잭션은 다음과 같은 흐름이 발생할 수 있습니다.

 

  • T1과 T2가 동시에 잔액 조회 → 둘 다 100원
  • T1: 100 - 30 = 70 → 업데이트
  • T2: 100 - 50 = 50 → 업데이트

 

 

하지만 실제로는 30원 + 50원 = 80원이 출금되어야 하므로 정상적인 잔액은 20원이어야 합니다.

이처럼 여러 트랜잭션이 서로의 변경 내용을 덮어써서 발생하는 문제를 Lost Update(갱신 손실) 라고 하며 대표적인 데이터 정합성 오류입니다.



 

 

이런 문제를 방지하려면 누군가 데이터를 수정하는 동안 다른 사람이 접근하지 못하도록 해야 합니다.  이것을 락을 통해 해결할수 있습니다.


락이 적용되면 어떻게 달라질까?

락을 잡는다는 것은 “이 데이터는 현재 제가 처리 중이니 다른 트랜잭션은 잠시 기다려 주세요”라고 시스템에 명시하는 것을 의미합니다. 같은 상황에서 락이 적용되면 데이터 처리 흐름이 달라지게 됩니다.

 

락이 적용되면 흐름은 달라집니다.

  1. T1이 계좌 row에 락을 획득
  2. T2는 같은 계좌에 접근하려다 대기 상태
  3. T1 출금 완료 → 커밋 → 락 해제
  4. T2는 변경된 최신 잔액을 기준으로 처리

이 과정을 통해 동시 접근 상황에서도 데이터의 일관성과 신뢰성을 유지할 수 있습니다.


락의 기본 종류

락에는 기본적으로 두 가지 종류가 있습니다.
- 공유 락(Shared Lock) :  여러 트랜잭션이 동시에 읽을 수 있게 하지만 쓰기는 막음
- 배타 락(Exclusive Lock) :    읽기와 쓰기를 모두 독점


락을 사용하는 방식도 두 가지로 나뉩니다.

 - 비관적 락(Pessimistic Lock) :  "충돌이 자주 발생할 것"이라고 가정하고 미리 락을  걸어놓는다.
 - 낙관적 락(Optimistic Lock) :  "충돌이 드물 것"이라고 가정하고 커밋 시점에 확인

 

그리고 애플리케이션이 여러 서버로 확장되면 데이터베이스 락만으로는 부족합니다. 서버 A의 메모리에 있는 락을 서버 B는 알 수 없기 때문입니다. 이때 필요한 것이 분산 락(Distributed Lock)입니다. Redis나 Zookeeper 같은 외부 시스템을 사용하여, 모든 서버가 공유하는 락을 구현합니다.

 

 

 락에 대해 알았으니 이제부터 락의 기본 종류(공유락, 배타락)과 락의 사용방식인 비관적락과 낙관적락 그리고 분산락에  대해 자세히 알아봅시다


2. 공유 락(Shared Lock) - 읽기는 함께, 쓰기는 혼자

공유 Lock은 데이터를 읽기 전용으로 보호하기 위해 사용됩니다. 여러 트랜잭션이 동시에 공유 Lock을 획득할 수 있으며, 이 동안 다른 트랜잭션은 데이터를 변경할 수 없습니다.

 

즉 “나는 데이터를 읽기만 할 테니 다른 트랜잭션이 수정하지 못하도록 막아주세요”라는 개념입니다.

 

공유 Lock을 사용하면, 다른 트랜잭션이 읽기 작업을 수행하는 것은 허용되지만, 쓰기 작업을 수행하려고 하면 대기 상태가 됩니다.

 

예를 들어, 트랜잭션 A가 공유 Lock을 잡고 데이터를 조회 중이면 트랜잭션 B도 읽기는 가능하지만 업데이트나 삭제는 수행할 수 없습니다.


공유락이 필요한 이유

왜 이런 락이 필요할까요? 읽기만 하는 트랜잭션들은 서로 간섭하지 않습니다.
A가 데이터를 읽는 것이 B가 같은 데이터를 읽는 것을 방해할 이유가 없습니다. 하지만 누군가 데이터를 수정하는 동안에는 읽기도 일관된 값을 봐야 하기 때문입니다.


언제 사용하면 좋을까?

공유 락은 언제 사용해야 할까요?
가장 흔한 경우는 일관된 스냅샷을 읽어야 할 때입니다. 여러 테이블을 조회하면서 데이터가 중간에 변경되지 않아야 하는 경우입니다.

예를 들어 재무 보고서를 생성한다고 생각해봅시다. 주문 테이블과 결제 테이블을 조회하여 매출을 계산하는데, 조회하는 동안 누군가 데이터를 수정하면 안 됩니다. 조회 시점의 일관된 데이터로 보고서를 만들어야 합니다.


공유락의 함정 - 데드락과 성

공유 락에도 몇 가지 함정이 있습니다.

첫 번째는 데드락(Deadlock) 위험입니다.

 

예를 들어, 두 트랜잭션이 각각 공유 락을 잡고 있다가, 동시에 배타 락으로 업그레이드하려고 하면 서로가 상대방을 기다리는 상태가 되어 교착 상태가 발생할 수 있습니다.
이를 방지하려면 처음부터 배타 락을 사용하거나 락을 획득하는 순서를 일관되게 유지하는 것이 중요합니다.


두 번째는 성능 문제입니다.

공유 락도 결국 하나의 락이기 때문에 불필요하게 사용하면 락 관리 비용과 컨텍스트 스위칭으로 인해 전체 성능에 영향을 줄 수 있습니다.

 

예를 들어 주문 조회 API처럼 읽기 중심의 트랜잭션이 초당 1,000건 정도 발생하고 그중 쓰기 트랜잭션 비율이 1% 수준이라고 가정해보겠습니다. 이 경우 대부분의 트랜잭션은 서로 다른 행을 조회하기 때문에 동일 로우 충돌 확률은 매우 낮습니다. 그런데 이 상황에서 모든 읽기 트랜잭션에 공유 락을 걸면, 락을 획득하고 해제하는 과정에서 불필요한 오버헤드가 발생합니다. 반면, MVCC 기반의 일반 SELECT를 사용하면 락 없이도 스냅샷 읽기가 가능하여, 락 충돌이 거의 없는 환경에서는 응답 시간이 평균 10~30% 이상 빠르게 나타나는 경우가 많습니다. 특히 TPS가 높은 환경에서는 그 차이가 더 크게 드러납니다. 따라서 단순히 최신 데이터를 읽는 것이 목적이라면 공유 락 대신 MVCC 읽기를 사용하는 것이 효율적입니다

 

반대로 재고 차감과 같이 동시 쓰기 충돌 가능성이 높은 상황에서는 접근 방식이 달라집니다.

예를 들어 초당 200건 정도의 트랜잭션이 발생하고, 동일 상품에 대한 동시 요청이 빈번하며, 평균 트랜잭션 시간이 50ms 정도라고 가정해보겠습니다. 이때 낙관적 락(@Version)을 적용하면 충돌률이 10% 정도만 되어도, 10건 중 1건은 재시도를 수행해야 하며, 재시도를 포함한 평균 응답 시간은 80~100ms 수준으로 늘어날 수 있습니다. 반면 비관적 락(SELECT ... FOR UPDATE)을 적용하면 충돌이 발생할 경우 대기 시간이 발생하지만 평균 대기 시간은 10~20ms 정도에 불과하고, 전체 평균 응답 시간은 60~70ms로 상대적으로 짧습니다. 따라서 충돌 가능성이 5~10% 이상이고, 재시도 비용이 큰 상황에서는 낙관적 락보다는 비관적 락이 더 적합합니다.

공유 락 vs MVCC 성능 비교: 읽기/쓰기 환경별 차이

 

결론적으로 공유 락은 단순 조회 목적이라면 불필요한 성능 오버헤드를 유발할 수 있으므로 가급적 피하고, MVCC 기반의 스냅샷 읽기를 활용하는 것이 좋습니다. 반대로 쓰기 충돌 가능성이 높고 재시도 비용이 큰 상황에서는 공유 락이나 비관적 락을 선택하여 데이터 일관성을 보장하는 것이 필요합니다.


3. 배타 락(Exclusive Lock) - 완전한 독점

배타 Lock은 데이터를 쓰기 전용으로 보호합니다. 한 트랜잭션만 해당 데이터를 접근할 수 있으며, 다른 모든 읽기/쓰기 작업은 차단됩니다.

“지금 제가 데이터를 수정하고 있으니, 다른 트랜잭션은 절대 접근하지 마세요”라는 의미로 이해하시면 됩니다.


한 트랜잭션이 배타 락을 획득하면, 다른 트랜잭션은 그 데이터를 읽을 수도 쓸 수도 없습니다. 완전한 독점입니다.

배타 락의 핵심은 "나 혼자만 사용한다"는 것입니다. 수정하는 동안 다른 사람이 읽으면 중간 상태를 볼 수 있고, 다른 사람이 수정하면 충돌이 발생합니다. 그래서 배타 락은 모든 접근을 막습니다.

 

 

배타 Lock이 걸린 상태에서는 다른 트랜잭션은 읽기, 쓰기 모두 수행할 수 없습니다.
따라서 배타 Lock은 INSERT, UPDATE, DELETE 작업이나 SELECT ... FOR UPDATE 에서 주로 사용되며, 금액 차감, 재고 감소와 같이 정합성이 매우 중요한 로직에서 반드시 필요합니다.


배타락이 필요한 이유

데이터를 수정하는 동안 다른 트랜잭션이 동시에 접근하면 충돌이나 정합성 오류가 발생할 수 있습니다.


예를 들어 은행 계좌에서 출금을 처리하는 경우
두 트랜잭션이 동시에 잔액을 차감하면 Lost Update 문제가 발생할 수 있습니다. 배타 락은 이러한 문제를 방지하고, 수정 중인 데이터가 다른 트랜잭션에 의해 읽히거나 변경되지 않도록 보장합니다.즉 배타 락은 데이터 무결성을 지키기 위한 안전장치라고 볼 수 있습니다.


언제 사용하면 좋을까?

배타 락은 정합성이 매우 중요한 로직에서 반드시 사용해야 합니다.

 

온라인 쇼핑몰에서 상품 재고를 관리하는 상황을 생각해봅시다. 사용자 A와 사용자 B가 동시에 같은 상품을 구매할 경우
재고가 충분하다면 두 트랜잭션이 동시에 재고를 조회하고 차감할 수 있습니다. 하지만 락이 없으면 동시 출고로 재고가 음수가 되거나 판매 가능한 수량보다 많은 주문이 처리될 수 있습니다.

 

배타 락을 걸면 하나의 트랜잭션이 재고를 차감하는 동안 다른 트랜잭션은 대기하게 되어정확한 재고 관리가 가능합니다.


4.  분산 락 - 여러 서버 환경의 동시성 제어

분산 Lock은 DB Lock과 달리, 여러 서버나 인스턴스 환경에서 하나의 작업만 실행되도록 보장하는 Lock입니다. 여러 서버에서 동시에 같은 자원에 접근할 때 발생할 수 있는 중복 실행을 방지할 수 있습니다.

 

쉽게 비유하자면  여러 사람이 하나의 화장실을 사용할 때 문에 잠금장치가 있는 것과 비슷합니다. 한 사람이 들어가서 문을 잠그면 다른 사람들은 밖에서 기다려야 하죠.


분락이 필요한 이유

예를 들어, 무신사와 같은 쇼핑몰에서 재고가 단 1개 남은 상품이 있다고 가정해보겠습니다.
동시에 100명의 사용자가 "구매" 버튼을 클릭하면, 여러 서버에서 동시에 재고를 확인하고 차감하려고 시도하게 됩니다.

이때 데이터베이스 락만으로는 충분하지 않습니다.

 

왜냐하면 서버 A와 서버 B는 서로의 메모리에 있는 락 상태를 알 수 없기 때문입니다. 결과적으로, 재고가 1개인데 여러 명에게 판매되는 오버셀링(over-selling) 문제가 발생할 수 있습니다.


5.  비관적 락(Pessimistic Lock) — “충돌은 반드시 일어난다”

비관적 락은 이름 그대로 “비관적인 가정”을 전제로 합니다.

“여러 트랜잭션이 동시에 이 데이터를 수정하려고 할 가능성이 높다”
“그러니 아예 미리 잠가서 충돌 자체를 막자”

 

여러 트랜잭션이 동시에 같은 데이터를 수정하려고 할 가능성이 높다고 보고 충돌이 발생하기 전에 아예 미리 락을 걸어 다른 접근을 차단합니다.


즉, 누군가 데이터를 읽거나 수정하려는 순간부터 “이 데이터는 지금 사용 중이니 다른 트랜잭션은 기다려야 한다”는 상태를 만드는 방식입니다.

 

이 방식에서는 트랜잭션이 데이터를 조회하는 시점에 락을 획득하고 해당 트랜잭션이 커밋되거나 롤백될 때까지
다른 트랜잭션은 그 데이터에 접근하지 못하고 대기하게 됩니다.

 

그 결과 동시 수정으로 인한 충돌이나 Lost Update 같은 문제는 원천적으로 발생하지 않습니다.

 

다만 그 대가로 성능 비용이 발생합니다. 동시에 접근하는 트랜잭션이 많아질수록 대기 시간이 늘어나고
전체 처리량은 자연스럽게 감소합니다.

 

그럼에도 불구하고 비관적 락이 필요한 이유는 금액 차감이나 재고 감소처럼 정합성이 조금이라도 틀리면 치명적인 로직에서는
성능보다 안정성이 훨씬 중요하기 때문입니다.


그래서 은행 계좌 출금, 재고가 거의 없는 상품 처리 같은 영역에서는 비관적 락이 사실상 필수적인 선택이 됩니다.


6. 낙관적 락(Optimistic Lock) — “충돌은 거의 없다”

낙관적 락은 동시 수정이 자주 발생하지 않을 것이라고 가정합니다.

그래서 데이터를 읽고 수정하는 과정에서는 아무런 락도 걸지 않고 자유롭게 처리합니다.

 

대신 트랜잭션을 커밋하는 시점에 “이 데이터를 내가 수정하는 동안, 다른 누군가 먼저 수정하지는 않았는가?”를 확인합니다.

 

이 확인을 위해 보통 버전 번호나 타임스탬프 같은 값을 사용합니다.
트랜잭션이 처음 데이터를 읽었을 때의 버전과 커밋 시점의 버전을 비교해서, 값이 달라졌다면
이미 다른 트랜잭션이 먼저 수정했다는 의미가 됩니다.

 

이 경우 해당 트랜잭션은 실패 처리되고 애플리케이션 레벨에서 다시 시도하도록 설계합니다.

 

이 방식의 가장 큰 장점은 성능입니다.
락을 걸지 않기 때문에 트랜잭션 간 대기 시간이 거의 없고 읽기 요청이 많은 서비스에서는 훨씬 높은 처리량을 낼 수 있습니다.
특히 동시에 같은 데이터를 수정할 가능성이 낮은 경우라면 충돌로 인한 재시도 비용보다 락으로 인한 성능 저하가 더 큰 문제가 될 수 있습니다.

 

그래서 게시글 수정, 사용자 프로필 변경, 설정 값 수정처럼 동시 수정 가능성이 낮고, 충돌이 발생하더라도 다시 시도할 수 있는 로직에서는 낙관적 락이  합리적인 선택이 됩니다.


7.  락 종류 비교 표 

락 이름 (Lock Type)목적적용 범위동시성DB 의존성사용 난이도주 사용 사례
공유 락 (Shared Lock) 읽기 보호 DB 행 / 테이블 높음 O 중 보고서 조회
배타 락 (Exclusive Lock) 쓰기 독점 DB 행 / 테이블 낮음 O 중 재고 / 금액 처리
분산 락 (Distributed Lock) 전역 동기화 서버 전체 (멀티 서버) 낮음 X 높음 멀티 서버 동시 접근 제어

 


마치며

이번 글을 정리하면서, 특히 쇼핑몰처럼 사용자 수가 많고 트래픽이 집중되는 환경에서는 락 설계 하나가 서비스 신뢰성과 직결된다는 것을 배웠습니다.

 

무신사 같은 플랫폼을 생각하면, 재고 1개 남은 상품에도 수십, 수백 명이 동시에 구매 버튼을 누를 수 있겠죠.
이럴 때 공유 락, 배타 락, 분산 락을 적절히 활용하면 안정적으로 재고를 관리하고 고객 경험을 지킬 수 있다는 점을 직접 체감할 수 있었습니다.

 

 

 

 

저작자표시 비영리 (새창열림)

'데이터베이스' 카테고리의 다른 글

MySQL SQL 파서(Parser)와 옵티마이저(Optimizer) 내부 동작 정리  (0) 2026.02.06
MySQL 스토리지 엔진 완전 정복 - InnoDB, MyISAM, ARCHIVE의 선택과 이해  (0) 2026.01.30
MYSQL DB 4가지 트랜잭션 격리레벨에 대해  (0) 2026.01.30
'데이터베이스' 카테고리의 다른 글
  • MySQL SQL 파서(Parser)와 옵티마이저(Optimizer) 내부 동작 정리
  • MySQL 스토리지 엔진 완전 정복 - InnoDB, MyISAM, ARCHIVE의 선택과 이해
  • MYSQL DB 4가지 트랜잭션 격리레벨에 대해
깊은바다속꼬북이
깊은바다속꼬북이
  • 깊은바다속꼬북이
    CodeBlossom
    깊은바다속꼬북이
  • 전체
    오늘
    어제
    • 분류 전체보기 (53) N
      • 라이징 캠프 (4)
      • 객채지향 개발론 (3)
      • 스프링 (10) N
      • 네트워크 (2)
      • 자바 (16)
      • 자료구조 (3)
      • 운영체제 (0)
      • 데이터베이스 (4)
      • 디자인패턴 (7)
      • JSP (1)
      • 개발 알쓸신잡 (2)
      • 일반 교양 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    디자인 패턴
    한국어 검색
    싱글턴 패턴(Singleton Pattern)
    어댑터 패턴(Adapter Pattern)
    자바 Socket 클래스
    템플릿 메서드 패턴(Template Method Pattern)
    jit-compiler
    JUnnit5
    프로그램밍 언어
    자료구조
    MySQL 파서
    java
    객체지향
    MySQL 옵티마이저
    GC
    java data area
    스프링
    트랜잭션 전파레벨
    mockito라이브러리
    spring
    jvm 클래스 로더
    java 버전별 특징
    개발자 철학
    개발 철학
    디자인패턴
    JVM
    전략 패턴(Strategy Pattern)
    junnit5프레임워크
    개발 교훈
    백엔드
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
깊은바다속꼬북이
데이터베이스 락의 모든 것 - 공유 락, 배타 락, 그리고 분산 락
상단으로

티스토리툴바