MVCC·격리 수준·잠금
한 요청이 데이터를 바꾸는 동안 다른 요청은 무엇을 읽을까? 다른 요청도 같은 행을 바꾸려 하면 어떻게 될까? 첫 질문은 조회 스냅샷, 둘째는 잠금과 충돌 처리로 살펴본다.
이 장에서 처음 나오는 말3개
MVCCMulti-Version Concurrency Control- 행의 여러 버전을 이용해 각 조회에 맞는 데이터 상태를 보여 주는 방식.
스냅샷Snapshot- 현재 조회에 어떤 트랜잭션의 변경이 보이는지 정하는 기준.
잠금Lock- 서로 충돌하는 작업을 동시에 진행하지 않도록 조정하는 장치.
두 연결에서 보는 데이터
섹션 제목: “두 연결에서 보는 데이터”공통 예제를 준비하고 터미널 두 개에서 각각 psql을 연다. 아래 표의 순서대로 실행한다. A와 B는 서로 다른 연결이다.
| 순서 | 연결 A | 연결 B | 관찰 |
|---|---|---|---|
| 시작 | BEGIN; | A 트랜잭션 시작 | |
| 첫 조회 | SELECT done FROM public.tasks WHERE id = 1; | false | |
| 변경 | UPDATE public.tasks SET done = true WHERE id = 1; | B가 자동 커밋 | |
| 두 번째 조회 | 같은 SELECT | true | |
| 종료 | ROLLBACK; | A 종료 |
기본 Read Committed에서는 일반 SELECT 문장마다 새 스냅샷을 사용한다. 따라서 같은 트랜잭션 안에서도 두 조회 결과가 다를 수 있다. (격리 수준)
먼저 B에서 UPDATE public.tasks SET done = false WHERE id = 1;로 복구한다.
이번에는 A의 시작을 BEGIN ISOLATION LEVEL REPEATABLE READ;로 바꾸고 같은 순서를 반복한다.
두 SELECT 모두 false를 본다. A가 종료된 뒤 새 SELECT를 하면 B가 확정한 true가 보인다.
끝나면 다시 false로 복구한다.
| 격리 수준 | 일반 조회의 기준 | 앱이 준비할 것 |
|---|---|---|
| Read Committed | 문장마다 새 스냅샷 | 읽고 계산한 값을 나중에 덮어쓰는 경쟁 조건 점검 |
| Repeatable Read | 트랜잭션의 첫 일반 쿼리 기준 스냅샷 | 동시 변경에 따른 실패 재시도, 여러 행의 업무 규칙 점검 |
| Serializable | 성공한 트랜잭션들이 어떤 직렬 순서와 같은 결과 | 직렬화 실패 시 전체 트랜잭션 재시도 |
MVCC가 모든 잠금을 없애지는 않는다. 특히 같은 행의 변경끼리는 충돌할 수 있다.
같은 행의 변경은 기다린다
섹션 제목: “같은 행의 변경은 기다린다”A에서 실행하고 트랜잭션을 열어 둔다.
BEGIN;UPDATE public.tasks SET done = true WHERE id = 1;B에서 실행한다.
SET lock_timeout = '2s';UPDATE public.tasks SET title = '잠금 실험' WHERE id = 1;B는 기다리다가 lock timeout 오류로 끝난다. A의 미확정 변경이 같은 행을 잠그고 있기 때문이다.
A에서 ROLLBACK;, B에서 RESET lock_timeout;으로 정리한다. 두 변경 모두 남지 않는다.
읽기 전용 SELECT는 보통 A를 기다리는 대신 자신에게 보이는 이전 버전을 읽는다.
(행 잠금)
읽고 나중에 덮어쓰기 줄이기
섹션 제목: “읽고 나중에 덮어쓰기 줄이기”현재 값을 앱에서 읽고 계산해 고정값으로 저장하면 다른 요청의 변경을 덮을 수 있다. 가능하면 조건과 변경을 한 SQL에 둔다. 다음 예는 “아직 미완료일 때만 완료 처리”다.
BEGIN;UPDATE public.tasks SET done = trueWHERE id = 1 AND done = falseRETURNING id;ROLLBACK;실제 앱에서는 반환 행이 없을 때 이미 처리됐거나 대상이 없다는 상황을 다룬다.
여러 문장 사이에 행을 보호해야 하면 SELECT ... FOR UPDATE를 검토하되 잠금을 짧게 유지한다.
서로 다른 트랜잭션이 행을 반대 순서로 잠그면 **deadlock(교착)**이 생길 수 있다. 같은 순서로 접근해 가능성을 줄이고, PostgreSQL이 한 트랜잭션을 중단하면 전체 작업을 재시도한다. 사용자 입력이나 네트워크 응답을 기다리며 트랜잭션을 열어 두지 않는다.
이해 확인: Repeatable Read에서 두 번 같은 값을 읽었다면 다른 요청의 UPDATE가 없었다고 말할 수 있을까? 아니다. 내 스냅샷에 그 변경이 보이지 않는 것일 수 있다.