Youtube 쉬운 코드 채널의 영상을 보고 정리한 내용입니다.
https://youtu.be/bLLarZTrebU?si=cQAeGZoEVi9tN5MT
SQL 표준 비판
아래와 같은 세 가지 이상 현상과 isolation level은 SQL 표준(standard SQL 92)에서 정의된 내용이다.
- Dirty read
- Non-Repeatable read / Fuzzy read
- Phantom read
격리 수준을 이용해, 위와 같은 이상 현상들이 모두 발생하지 않게 만들 수는 있다.
하지만 그러면 제약 사항이 많아져서 동시에 처리 가능한 트랜잭션 수가 줄어들어
DB의 처리량(throughput)이 하락하는 문제가 생길 수 있다.
그래서, 필요에 따라 일부는 허용할 수 있도록 Isolation 레벨을 나누게 된 것이다.
우리는 isolation level을 통해 전체 처리량과 데이터 일관성을 트레이드오프할 수 있다.
하지만 이 때 정의되지 않은 추가적인 이상 현상들도 있다.
standard SQL 92를 비판하는 논문의 내용을 기반으로 이를 알아보자.
이상 현상 살펴보기
Dirty write
commit되지 않은 데이터를 write하는 현상.
이 경우 롤백 시 큰 문제가 발생할 수 있는데, 예를 들면
x = 0
T1. write(x=10)
T2. write(x=100)
T1. rollback
T1을 rollback했을 때, T2가 쓴 x=100인 값을 유지하기 위해 T2는 롤백하지 않았다고 하자.
그런데 이후 T2에 문제가 생겨 이전 버전으로 롤백을 해야 하는 상황이 발생했다.
그러면 T1은 이미 롤백되어 없는 변경 사항이므로 x=0으로 되돌려야 하지만,
T2의 이전 버전인 x=10으로 되돌리는 이상한 현상이 발생할 수 있다.
롤백 시 정상적인 복구는 매우 중요하므로, Dirty write은 어떤 isolation level에서도 발생하면 안된다.
Lost update
업데이트를 덮어 썼을 때 발생하는 현상.
Dirty read와 Lost update의 차이
Dirty read는 한 트랜잭션이 다른 트랜잭션의 커밋되지 않은 데이터에 대해 쓰기 작업을 하는 것.
Lost update는 두 트랜잭션이 동일한 행을 읽고 업데이트하는데,
첫번째 커밋된 업데이트가 두번째 커밋된 업데이트에 의해 덮어쓰이는 것.
Dirty read
커밋되지 않은 변화를 읽어서 잘못된 데이터가 저장될 수 있는 현상.
커밋되지 않은 변화를 읽은 뒤 그것을 기반으로 쓰기 작업을 할 수 있기 때문이다.
Dirty read는 이미 격리 수준에서도 고려하는 현상이다.
하지만 일반적으로 커밋되지 않은 변화가 롤백되는 경우에만 문제가 된다고 생각하지만, 다른 상황도 있다.
아래와 같은 경우, 롤백이 발생하지 않아도 Dirty read 문제가 발생할 수 있다.
x = 50, y = 50
T1. read(x) => 50
T1. write(x = 10)
T2. read(x) => 10
T2. read(y) => 50
T2. commit
T1. read(y) => 50
T1. read(y = 90)
T1. commit
T2에서 x의 값은 정상적으로 보이지만, y의 값은 T1이 커밋되기 전 값이므로 데이터 불일치가 발생한다.
두 계좌의 합을 구하는 것과 같은 작업을 한다면(x+y) 문제가 된다.
이처럼 롤백이 발생하지 않아도 Dirty read로 인해 데이터 정합성이 깨질 수 있다.
Read skew
inconsistent한 데이터 읽기가 발생하는 현상.
x = 50, y = 50
T2. read(x) => 50
T1. read(x) => 50
T1. write(x = 10)
T1. read(y) => 50
T1. read(y = 90)
T1. commit
T2. read(y) => 90
T2. commit
T2에서 y의 값은 잘 읽었지만, x의 값은 과거의 값을 읽고 있다.
마찬가지로 x+y의 합을 구해야 하는 것과 같이 서로 관련 있는 데이터라면 문제가 된다.
중요한 문제다.
Non-Repeatable read와 Read skew의 차이
Non-Repeatable read는 하나의 데이터에 대한 일관성이 깨지는 경우,
Read skew는 서로 관련 있는 데이터들의 일관성이 깨지는 경우
Write skew
한 사람이 여러 계좌를 가지고 있지만, 모든 계좌 잔액의 합에 제한이 있다고 생각해보자.
그런데 서로 다른 트랜잭션에서 아래와 같이 인출 작업이 벌어진다면?
x = 50, y = 50 (x+y >= 0)
T1. read(x) => 50
T1. read(y) => 50
T2. read(x) => 50
T2. read(y) => 50
T1. write(x = -30) (x=50, y=50 이므로 80 인출 가능하다고 판단)
T2. write(y = -40) (x=50, y=50이므로 90 인출 가능하다고 판단)
T1. commit
T2. commit -> (x=-30, y=-40이므로 제약사항을 위배한다.)
Write skew는 이와 같이, inconsistent한 데이터 쓰기 현상을 말한다.
격리수준에 따라 T2를 커밋할 때 제약사항을 위배하면 T2, T1을 모두 롤백시킬 수 있겠지만
그렇지 않을 수도 있다.
Phantom read
없던 데이터가 보이면서 데이터 일관성을 해치는 현상.
마찬가지로 이미 격리 수준에서도 고려하는 현상이다.
하지만 특정 데이터가 보였다 안보였다 하는 것 외에도, 더 넓은 개념에서 정의할 수 있다.
특정 조건을 만족하는 데이터의 개수나, 개수를 기록하는 count 데이터가 있다고 하자.
이 역시도 Phantom read가 발생할 수 있다.
실무에서의 isolation level
SQL 표준을 비판하는 논문에는,
'상업적인 DBMS에서 사용되는 방법을 반영해서 isolation level을 구분하지 않았다'는 내용도 있다.
해당 비판과 함께, 제안하는 격리 방식인 Snapshot Isolation을 살펴보자.
Snapshot Isolation
이상 현상 세 가지를 기반으로 나눠진 기존 격리 수준과 달리,
동시성 제어를 어떻게 구현할 것인지에 기반해서 격리 수준을 정의한다.
Snapshot Isolation은 특정 시점(Snapshot)을 기준으로 격리한다.
이 때 특정 시점의 기준은, 트랜잭션이 시작하는 시점이다.
쓰기 작업을 한다면, 실제 데이터에 쓰기를 하는 게 아니라 Snapshot의 공간에 쓰기를 한다.
커밋을 하게 되는 순간, Snapshot의 변경 사항을 실제 데이터에 적용된다.
데이터가 변경된 시점 이후로 시작되는 트랜잭션들만 해당 변경 사항을 읽는다.
같은 데이터에 대해 쓰기 충돌이 발생하는 경우에는,
먼저 커밋된 트랜잭션만 인정해주는 방식으로 동시성을 제어한다.
이 방식은 MVCC의 한 종류이기도 하다.
실무에서는?
벤더 사에 따라 정의하고 있는 격리 수준을 확인할 수 있다. (공식 문서 기반)
아래와 같이, 주요 RDBS은 SQL 표준에 기반해서 격리 수준을 정의하고 있지만
같은 이름의 격리 수준이더라도 동작 방식은 다를 수 있다.
이를 잘 파악하고 적절한 격리 수준을 사용할 수 있도록 해야 한다.
- MySQL(innoDB)는 SQL 표준과 동일하게 정의하고 있다.
- Oracle은 SQL 표준에 기반하지만, Read uncommited는 제공하지 않는다.
- Read commited와 Serializable이 주로 사용된다.
- 하지만 Oracle의 Serializable은 실제로는 사실상 Snapshot Isolation처럼 동작한다.
- SQL server도 SQL 표준을 기반으로 정의하고 있다.
- Snapshot 레벨이 존재한다.
- PostgreSQL도 SQL 표준을 기반으로 정의하고 있다.
- 동일한 레벨을 제공하되, 추가적인 현상도 함께 표기하고 있다.
'공부 > Database' 카테고리의 다른 글
MySql의 last_insert_id() 함수는 동시성 문제로부터 안전할까? (0) | 2023.11.04 |
---|---|
Lock을 활용한 Concurrency Control 기법 (1) | 2023.10.10 |
동시성 제어(Concurrency Control) 기초 (2) Recoverability (3) | 2023.09.26 |
동시성 제어(Concurrency Control) 기초 (1) Schedule과 Serializability (0) | 2023.09.18 |
[Real MySQL 8.0] 05. 트랜잭션과 잠금 (2) MySQL의 격리 수준 (0) | 2023.09.13 |