교착 상태, 잠금, MVCC
데이터베이스가 충돌을 피하는 방식과 잠금과 스냅샷의 절충점을 알아봅니다.
교착 상태, 잠금, MVCC은(는) CoddyKit의 무료 SQL Interview Prep 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 SQL Interview Prep 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. SQL Interview Prep 강의에는 총 4개의 강의가 포함되어 있습니다.
데이터베이스가 실제로 격리를 구현하는 방법
격리 수준은 약속이고, 잠금과 MVCC는 그 약속을 지키는 메커니즘입니다. 트랜잭션이 충돌할 때 내부에서 어떤 일이 일어나는지 이해하고 있는지 확인하기 위해 면접관은 이 내용을 질문합니다.
크게 두 가지 전략이 있습니다.
- 비관적 방식(잠금): 잠금이 해제될 때까지 충돌하는 접근을 차단합니다.
- 낙관적 방식 / MVCC: 모든 트랜잭션이 일관된 스냅샷을 읽게 하고 확정 시 충돌을 감지합니다.
이 수업에서는 잠금, 교착 상태, MVCC와 각각의 장단점을 다룹니다.
공유 잠금과 배타 잠금
전통적인 잠금에는 두 가지 주요 모드가 있습니다.
- 공유(S) 잠금은 읽기에 사용됩니다. 여러 트랜잭션이 같은 행에 동시에 공유 잠금을 보유할 수 있습니다.
- 배타(X) 잠금은 쓰기에 사용됩니다. 하나의 트랜잭션만 이를 보유할 수 있으며, 해당 행에 대한 다른 모든 잠금을 차단합니다.
규칙은 다음과 같습니다. S는 S와 호환되지만 X는 어떤 잠금과도 호환되지 않습니다. 쓰기 작업은 모든 읽기 작업이 끝날 때까지 기다려야 하고, 읽기 작업은 쓰기 작업이 끝날 때까지 기다려야 합니다.
SELECT FOR UPDATE를 사용한 명시적 잠금
읽기만 하는 행에도 쓰기 잠금을 요청하여 작업을 수행하기 전에 다른 트랜잭션이 해당 행을 변경하지 못하게 할 수 있습니다. 이는 읽고 수정한 뒤 쓰는 과정에서 갱신 손실을 방지하는 표준적인 방법입니다.
SELECT ... FOR UPDATE는 행에 배타 잠금을 설정하며, 해당 행은 COMMIT 또는 ROLLBACK할 때까지 잠긴 상태로 유지됩니다.
BEGIN;
-- lock the row so no one else can modify it concurrently
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT; -- lock released here교착 상태란 무엇인가
교착 상태는 둘 이상의 트랜잭션이 서로 상대방이 필요로 하는 잠금을 보유하여, 어느 트랜잭션도 진행할 수 없는 순환 구조가 형성될 때 발생합니다.
전형적인 사례는 다음과 같습니다. T1이 행 A를 잠근 뒤 행 B를 원하고, T2가 행 B를 잠근 뒤 행 A를 원합니다. 서로 상대방을 기다리므로 둘 다 영원히 진행하지 못합니다.
데이터베이스는 대기 그래프를 사용해 이를 감지합니다. 순환이 발견되면 엔진은 희생 트랜잭션을 선택해 중단하고, 교착 상태 오류를 반환하여 나머지 트랜잭션이 계속 진행할 수 있게 합니다.
교착 상태: 시간 순서
잠금 순서가 서로 교차하는 모습을 보십시오. T1은 행 1을 잠근 뒤 행 2를 요청하고, T2는 행 2를 잠근 뒤 행 1을 요청합니다. 어느 쪽도 잠금을 해제하지 않으므로 엔진은 둘 중 하나를 중단합니다.
중단된 트랜잭션은 deadlock detected와 같은 오류를 받고 다시 시도해야 합니다. 살아남은 트랜잭션은 정상적으로 확정합니다.
-- T1 | -- T2
BEGIN; | BEGIN;
UPDATE accounts SET balance=balance-10 | UPDATE accounts SET balance=balance-10
WHERE id=1; -- locks row 1 | WHERE id=2; -- locks row 2
UPDATE accounts SET balance=balance+10 | UPDATE accounts SET balance=balance+10
WHERE id=2; -- waits for T2 | WHERE id=1; -- waits for T1 -> CYCLE
-- one transaction is chosen as victim and rolled back교착 상태 방지
교착 상태를 완전히 없앨 수는 없지만 드물게 만들 수는 있습니다. 면접에서 말할 표준적인 답변은 다음과 같습니다.
- 일관된 잠금 순서: 항상 같은 순서(예: 식별자 오름차순)로 행을 확보합니다. 이렇게 하면 순환이 끊어집니다.
- 트랜잭션을 짧게 유지: 가능한 한 짧은 시간 동안만 잠금을 보유합니다.
- 안전하다면 격리 수준 낮추기: 잠금과 충돌을 줄입니다.
- 재시도 로직 추가: 교착 상태의 희생 트랜잭션이 자동으로 다시 시도하게 합니다.
일관된 순서 지정이 가장 효과적인 단일 해결책이며, 면접관이 가장 먼저 듣고 싶어 하는 답입니다.
잠금 세분성
잠금은 서로 다른 범위에서 설정할 수 있으며, 동시성과 관리 비용 사이의 절충이 필요합니다.
- 행 수준 잠금은 높은 동시성을 허용하지만 관리 비용이 더 많이 듭니다.
- 페이지 또는 테이블 잠금은 추적하기에는 더 저렴하지만 더 많은 트랜잭션을 차단합니다.
일부 엔진은 트랜잭션이 너무 많은 행에 접근하면 행 잠금을 잠금 승격을 통해 테이블 잠금으로 바꿉니다. 이 원리를 알면 대량 UPDATE 하나가 갑자기 모든 트랜잭션을 차단하는 이유를 설명할 수 있습니다.
MVCC: 스냅샷 방식
MVCC(다중 버전 동시성 제어)는 포스트그레스, 오라클, InnoDB가 대부분의 읽기 잠금을 피하는 방식입니다. 데이터베이스는 잠그는 대신 각 행의 여러 버전을 보관합니다.
가장 큰 장점이자 면접에서 자주 쓰이는 핵심 표현은 다음과 같습니다. 읽기 작업은 쓰기 작업을 차단하지 않고, 쓰기 작업은 읽기 작업을 차단하지 않습니다.
각 트랜잭션은 특정 시점 기준의 일관된 스냅샷을 보고, 쓰기 작업은 값을 원래 위치에 덮어쓰는 대신 새 행 버전을 만듭니다.
MVCC의 내부 동작
행이 갱신되면 MVCC는 새 버전을 기록하고 이전 버전을 유지합니다. 각 버전에는 트랜잭션 식별자 메타데이터가 포함됩니다. 포스트그레스에서는 xmin과 xmax가 해당 버전이 언제 보이게 되었고 언제 다른 버전으로 대체되었는지를 표시합니다.
트랜잭션의 스냅샷이 어떤 버전을 볼지 결정합니다. 어떤 트랜잭션에서도 더 이상 볼 수 없는 이전 버전은 무효 튜플이 되며, 나중에 정리 작업을 통해 회수됩니다. 포스트그레스에서는 그 작업이 VACUUM입니다. 이를 실행하지 않으면 자주 이어지는 질문인 테이블 팽창이 발생합니다.
잠금과 MVCC: 장단점
비교 내용을 간결하게 정리해 보겠습니다.
- 순수 잠금 방식: 정확성을 단순하게 보장하지만 읽기 작업과 쓰기 작업이 서로 차단되어 동시성이 저하됩니다.
- MVCC: 읽기 동시성이 뛰어나고 읽기 잠금이 없지만, 버전 저장과 정리(VACUUM, 팽창)에 비용이 들며 쓰기 간 충돌에는 여전히 잠금이 필요합니다.
MVCC 엔진도 쓰기 시에는 잠금을 사용합니다. 같은 행을 갱신하는 두 트랜잭션은 순차적으로 처리되어야 합니다. MVCC가 없애는 것은 읽기 작업과 쓰기 작업 간의 경합이지, 쓰기 작업 간의 경합이 아닙니다.
낙관적 잠금과 버전 열
엔진 수준의 MVCC 외에도 애플리케이션은 긴 사용자 세션에서 읽고 수정한 뒤 쓰는 작업을 처리하기 위해 낙관적 잠금을 추가하는 경우가 많습니다. version 열을 추가하고 그 값을 읽은 다음, 갱신할 때 버전이 일치하도록 요구하면서 값을 증가시킵니다.
다른 트랜잭션이 먼저 행을 갱신했다면 버전이 더 이상 일치하지 않으므로 영향을 받는 행은 0개가 됩니다. 그러면 코드가 행을 다시 불러와 재시도해야 한다는 것을 알 수 있습니다. 사용자가 생각하는 동안에는 잠금을 보유하지 않으므로 동시성을 높게 유지할 수 있습니다. 면접관은 "두 사용자가 같은 행을 편집하면 어떻게 처리합니까?"라는 질문에 대한 답으로 이 방식을 높이 평가합니다.
-- read: SELECT id, data, version FROM items WHERE id = 1; -- version = 7
UPDATE items
SET data = 'new value', version = version + 1
WHERE id = 1 AND version = 7;
-- if rows affected = 0, someone else changed it: reload and retry빠른 확인
MVCC의 핵심 표현을 확인해 보십시오.
복습: 잠금, 교착 상태와 MVCC
이제 격리의 내부 동작을 설명할 수 있습니다.
- 공유/배타 잠금은 접근을 조정하고,
SELECT FOR UPDATE는 명시적인 쓰기 잠금을 설정합니다. - 교착 상태는 잠금의 순환입니다. 엔진은 희생 트랜잭션을 중단하며, 일관된 잠금 순서가 대부분의 교착 상태를 방지합니다.
- MVCC는 행 버전을 유지하여 읽기 작업과 쓰기 작업이 서로 차단하지 않게 하는 대신 정리 비용(VACUUM, 팽창)이 발생합니다.
이러한 메커니즘을 앞선 수업에서 배운 격리 수준 및 이상 현상과 함께 이해하면 동시성에 관한 면접 질문을 처음부터 끝까지 다룰 수 있습니다.
자주 묻는 질문
“교착 상태, 잠금, MVCC” 강의는 무료인가요?
네 — “교착 상태, 잠금, MVCC” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 SQL Interview Prep 강의 전체를 잠금 해제할 수 있습니다. SQL Interview Prep 강의에는 총 4개의 강의가 포함되어 있습니다.
“교착 상태, 잠금, MVCC”에서 뭘 배우나요?
데이터베이스가 충돌을 피하는 방식과 잠금과 스냅샷의 절충점을 알아봅니다. 브라우저에서 직접 실행하는 실습 코드로 SQL Interview Prep을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
SQL Interview Prep을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 SQL Interview Prep은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.
“교착 상태, 잠금, MVCC” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 SQL Interview Prep 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 SQL Interview Prep 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- ACID 속성 설명
- 네 가지 격리 수준
- 더티 리드, 반복 불가능 읽기, 팬텀 리드
- 교착 상태, 잠금, MVCC