지난글
Transaction들이 동시에 실행될 때 발생 가능한 이상 현상
Dirty read
두 트랜잭션이 있다고 생각해보자.
- tx1은 데이터A를 읽기 작업을 하고 데이터A를 이용해 쓰기 작업을 한다.
- tx2는 해당 데이터A 쓰기 작업을 한다.
두 트랜잭션은 아래와 같은 순서대로 진행될 수 있다.
- tx1이 실행
- tx2가 실행된 후 tx1이 데이터A를 읽기전에 tx2가 쓰기 작업을 수행
- tx1이 tx2가 쓰기 작업을 한 데이터A를 읽음
- tx1이 읽은 데이터 A를 이용해 쓰기 작업을 수행
- tx1이 커밋
- tx2가 문제가 생겨 롤백
이 상황에서 tx1은 tx2가 롤백이 되면서 유효하지 않은 데이터를 사용해서 작업을 했음에도 불구하고 커밋을 이미 해버려서 롤백을 하지 못하게 된다.
📌 Dirty read
commit 되지 않은 변화를 읽음
Non-repeatable read
이번엔
- tx1: 데이터A를 읽기 작업 2번하는 트랜잭션
- tx2: 데이터A를 읽기 작업 1번, 쓰기 작업 1번하는 트랜잭션
두 트랜잭션에 대해서 생각해보자.
- tx1 시작
- tx1 데이터A 읽기 작업 ➡️ tx2가 쓰기 전인 기존의 데이터 A
- tx2 시작
- tx2 데이터 A 읽기 작업
- tx2 데이터 A 쓰기 작업
- tx2 커밋
- tx1 데이터 A 읽기 작업 ➡️ tx2가 쓰기 작업한 데이터 A
- tx1 커밋
여기서 무슨 문제가 있을까? 바로 tx1이 수행한 과정 2, 7번을 보면 isolation 관점에서 이상한 현상임을 알 수 있다.
왜냐하면 트랜잭션의 isolation은 여러 트랜잭션이 동시에 실행되어도 각각의 트랜잭션이 마치 혼자서 실행되는 것처럼 동작해야하기 때문이다.
tx1이 혼자 작업한다면 tx1은 읽기 작업만 하기 때문에 tx1 내에서는 데이터 A의 값이 변경될리가 없는데 tx2가 끼어들면서 다른 결과가 나오게 된 것이므로 isolation 관점에서 이상한 현상이라고 볼 수 있다.
📌 Non-repeatable read
같은 데이터의 값이 달라지는 현상
(Fuzzy read 라고도 불린다.)
Phantom read
또 다른 예시를 보자.
- 튜플
- t1(..., v=10)
- t2(..., v=50)
- 트랜잭션
- tx1: v가 10인 데이터 읽기 작업 2번
- tx2: t2의 v를 10으로 바꾸는 작업
위의 상황이 있을 때 아래와 같은 상황이 나타날 수 있다.
- tx1이 v=10인 튜플 읽는다. ➡️ t1
- tx2가 t2의 v를 10으로 바꾼다.
- tx2 commit
- tx1이 v=10인 튜플을 읽는다. ➡️ t1, t2
- tx1 commit
위의 상황에서도 조회를 2번하는 tx1이 tx2에 의해서 조회의 결과가 다르게 나타난다. 여기서는 tx2로 인해 없던 데이터가 생기는 현상을 볼 수 있다.
📌 Phantom read
없던 데이터가 생김
Isolation level
이상 현상은 모두 발생하지 않게 막아야할까?
위와 같은 Dirty read, Non-repeatable read, Phantom read 등의 이상현상을 모두 발생하지 않게 만들 수 있지만
제약사항이 많이지기 때문에 동시 처리 가능한 트랜잭션 수가 줄어들어 결국 DB의 전체 처리량(throughput)이 하락하게 된다.
일부 이상 현상은 허용하는 몇 가지의 level을 만들어서 사용자가 필요에 따라 적절하게 선택할 수 있도록 하고자 나온 아이디어가 Isolation level이다.
Isolation level | Dirty read | Non-repeatable read | Phantom read |
Read uncommitted | 허용 | 허용 | 허용 |
Read committed | x | 허용 | 허용 |
Repeatable read | x | x | 허용 |
Serializable | x | x | x |
여기서 serialiazable은 위 세 가지 현상 뿐 아니라 아예 이상한 현상 자체가 발생하지 않은 level을 의미한다.
세 가지 이상 현상을 정의하고 어떤 현상을 허용하는지에 따라 각각의 isolation level이 구분된다.
개발자들은 isolation level로 전체 처리량과 데이터 일관성 사이의 어느 정도 거래를 할 수 있다.
🐣 standard SQL 92 isolation level 비판
위의 내용은 1992년 11월에 발표된 SQL 표준에 정의된 내용이다.
1995년에 ANSI/ISO standard SQL 92에서 정의한 isolation level을 비판하는 논문이 나왔다.
1. 세가지 이상 현상의 정의가 모호하다.,
2. 이상 현상은 세 가지 외에도 더 있다.
3. 상업적인 DBMS에서 사용하는 방법을 반영해서 isolation level을 구분하지 않았다.
추가적인 이상 현상
Dirty write
- 트랜잭션
- tx1: x를 10으로 바꾸는 작업
- tx2: x를 100으로 바꾸는 작업
- 데이터
- 기존 x값: 0
아래의 상황을 보자
- tx1 시작
- tx1이 x를 10으로 업데이트
- tx2 시작
- tx2 x를 100으로 업데이트
- tx1 abort ➡️ x=0
위의 상황으로 보면 tx2가 100으로 업데이트 한 것이 사라진다.
이를 막고자 tx1 abort에서 x=0으로 돌리는 작업을 하지 않는다면 어떻게될까?
- tx1 시작
- tx1이 x를 10으로 업데이트
- tx2 시작
- tx2 x를 100으로 업데이트
- tx1 abort
여기까진 괜찮을 수 있다. 다만 이 다음에 tx2도 abort를 하게된다면 x의 값은 tx2가 시작되기 이전의 값 10으로 돌려놓을 것이다. tx1은 abort되었기 때문에 tx1이 작업했던 데이터로 바꾸게 되면 결국 유효하지 않은 값으로 x의 값이 변경되는 것이다.
이처럼 두개의 트랜잭션이 write할 때 rollback이 발생되었을 때 이상한 현상을 Dirty write라고 한다.
📌 Dirty write
commit이 안된 데이터를 write 함
rollback 시 정상적인 recovery는 매우 중요하기 때문에 모든 isolation level에서 dirty write를 허용하면 안된다.
Lost update
- 트랜잭션
- tx1: x에 50을 더한다.
- tx2: x에 150을 더한다.
- 데이터
- x=100
아래와 같이 실행될 수 있다.
- tx1 시작
- tx1 읽기 ➡️ x=50
- tx2 시작
- tx2 x데이터 읽기 ➡️ x=50
- tx2 x데이터에 +150 하기 ➡️ x = 50+ 150 = 200
- tx2 commit
- tx1 x데이터에 +50 하기 ➡️ x= 50+50 = 100
- tx1 commit
tx2가 작업한 x 값은 사라지게 되었다.
📌 Lost update
업데이트를 덮어 씀
Dirty read 확장
- 트랜잭션
- tx1: x가 y에 40을 이체한다.
- tx2: x와 y를 읽는다.
- 데이터
- x=10
- y=50
아래 상황을 보자.
- tx1시작
- tx1 x 읽기 ➡️ x = 50
- tx1 x 40 뺌 ➡️ x = 50-40 = 10
- tx2 x 읽기 ➡️ x = 10
- tx2 y 읽기 ➡️ y = 50
- tx2 commit
- tx1 y 읽기 ➡️ y = 50
- tx1 y 쓰기 ➡️ y=90
- tx1 commit
여기서 결과는 x = 10, y는 90 이다
10+90인 100은 일관성이 있어야한다. 반면 4,5번에서 tx2가 읽은 값은 10+50이다. 이는 데이터 정합성이 깨졌다고 볼 수 있다.
여기서 주목해야할 포인트는 abort가 발생하지 않아도 dirty read가 될 수 있다는 것이다.
Read skew
바로 위에서 봤던 상황을 조금 바꿔보자.
- 트랜잭션
- tx1: x가 y에 40을 이체
- tx2: x와 y를 읽는다.
- 데이터
- x=50
- y=50
- tx2 시작
- tx2 x 읽기 ➡️ x = 50
- tx1시작
- tx1 x 읽고 40 빼기 ➡️ x = 50 - 40 = 10
- tx1 y 읽고 40 더하기 ➡️ y = 50 + 40 + 90
- tx1 commit
- tx2 y 읽기 ➡️ y = 90
tx2가 x는 50 y는 90으로 읽었기 때문에 동일하게 데이터 불일치가 발생하게 된 것이다.
📌 Read skew
inconsistent(일관성 없는)한 데이터 읽기
write skew
- 트랜잭션
- x에 80 인출
- y에 90 인출
- 데이터
- x = 50
- y = 50
- 제약사항 x + y 는 0 이상이 되어야한다.
- tx1 시작
- tx1 x y 읽기 ➡️ x=50 y=50
- tx2 시작
- tx2 x y 읽기 ➡️ x=50 y=50
- tx1 x 쓰기 ➡️ x = 50-80 = -30
- tx2 y 쓰기 ➡️ y = 50-90 = -40
- tx1 commit
- tx2 commit
결과적으로 서로 다른 데이터를 읽고 썼음에도 데이터 불일치를 만들었다.
📌 write skew
inconsistent(일관성 없는)한 데이터 쓰기
phantom read 확장판
- 데이터
- 튜플
- t1: v가 7
- cnt: v가 10보다 큰 개수
- 튜플
- 트랜잭션
- tx1: v>10 데이터와 cnt를 읽는다
- tx2: v=15인 튜플을 추가하고 cnt를 1증가 시킨다.
- tx1시작
- tx1 v>10인 데이터 읽기 ➡️ 데이터 없음
- tx2 v=15인 데이터 추가
- tx2 cnt 읽기
- tx2 cnt 증가 ➡️ cnt = 1
- tx2 commit
- tx1 cnt읽기 ➡️ cnt = 1
- tx1 commit
2번과 7번의 tx1을 보면 2번에서는 데이터가 없는데 7번에서는 cnt가 1로 뜨면서 데이터 불일치가 생긴다.
꼭 같은 값을 두번 읽는 것이 아니어도 서로 연관된 데이터가 있는 경우에는 이렇게 중간에 어떤 데이터가 추가되었을 때 지금처럼 이상한 현상이 발생할 수 있다. 이러한 상황도 Phantom read로 봐야한다고 주장한다.
Snapshot isolation
비판 3번 째인 상업적인 DBMS에서 사용하는 방법을 반영해서 isolation level을 구분하지 않았다. 의 내용이 있는데 해당 내용에서 나온 레벨이다.
기존의 표준에서 정의한 isolation은 이상 현상을 3가지 정의하고 3가지중 얼마만큼 허용하는지에 따라 레벨을 분리 했다.
반면 snapshot isolation은 concurrency control이 어떻게 동작할지 구현을 바탕으로 정의한 레벨이다.
스냅샷: 특정 시점에서의 형상
- 트랜잭션
- tx1: x가 y 에 40 이체
- tx2: y에 100 입금
- 데이터
- x=50
- y=50
과정을 표로 정리해보았다.
tx1 | tx2 | tx1의 스냅샷 | tx2의 스냅샷 | DB |
시작 | x=50, y=50 | |||
x 읽음 | x=50 | x=50, y=150 | ||
x - 40 | x=10 | |||
시작 | y=50 | |||
y 읽기 | y=50 | |||
y+100 | y=150 | |||
commit | 적용 후 폐기 | y=150 | ||
y 읽음 | x=10, y=50 | |||
y + 40 | x=10, y=90 | |||
commit | y update 충돌 | ➡️ 먼저 commit된 트랜잭션(tx2)만 인정 |
||
abort | 폐기 |
이런 과정을 snapshot isolation이라고 한다.
snapshot isolation은 MVCC(multi version concurrency control)중 하나이다.
📌 MVCC
특정 측정 시점의 스냅샷을 기준으로 각각의 버전이 있었기 때문에 이런 것을 MVCC라고 한다.
📌 snapshot isolation
- tx 시작 전에 commit 된 데이터만 보임
- First-committer win
실무에서의 RDBMS isolation level
실무에서 사용되는 여러 RDBMS에서는 이 isolation level을 어떻게 정리하고 있을까?
MySQL
MySQL(innoDB엔진)은 표준에서 정의한 isolation level(4가지)과 동일하게 정의한다.
Oracle
Isolation Level 표준 4가지를 언급하고 있으나 read uncommitted는 제공하지 않고
아래 두 level은 serializable로 사용하라고 되어 있어서
결국 실질적으로 사용하는 level은 read committed와 serializable 두개라 볼 수 있다.
여기서 오라클의 serializable은 snapshot isolation과 동일하게 작동한다고 보면 된다.
SQL server
SQL server도 마찬가지로 표준에서 정의한 isolation level을 사용하고 + snapshot isolation을 사용하고 있다고 보면된다.
postgreSQL
postgreSQL도 표준 isolation level을 바탕으로 정의한다. 표준과 다른 점은 3가지 이상현상 외에 등장할 수 있는 이상현상을 추가로 표기해준다.
여기서는 Repeatable read가 snapshot isolation level에 해당한다.
주요 RDBMS은 SQL 표준에 기반해서 isolation level을 정의한다.
RDBMS마다 제공하는 isolation level이 다르다.
같은 이름의 isolation level 이라도 동작 방식이 다를 수 있다.
때문에 개발자들은 사용하는 RDBMS의 isolation level을 잘 파악해서 적절한 isolation level을 사용할 수 있도록 해야한다.
이런 다양한 형상들을 알고 있고 사용하는 RDBMS에서 isolation level이 어떻게 구현되는 지를 잘 파악을 한다면
트랜잭션을 사용할 때 이상한 현상이 발생하는 것을 미연에 방지를 할 수 있고, 서비스에서 이상 현상이 발생했을 때도
세가지 상황만 알고 있는 상태에서 무엇이 문제인지 파악하려는 것보다는 다양한 현상들을 모두 알고 있는 상태에서 왜 이런 이상한 현상이 발생했는지 문제를 찾아나가는 게 훨씬 더 빨리 문제를 파악할 수 있을 것 이다.
유튜브 쉬운 코딩님의 영상을 보고 정리한 글입니다.
https://www.youtube.com/watch?v=bLLarZTrebU&t=1804s
추가 참고할만한 글
[MySQL] 트랜잭션의 격리 수준(Isolation Level)에 대해 쉽고 완벽하게 이해하기
[Spring] @Transactional 어노테이션 이해하기(1) 전파유형(Propagation) 과 격리수준(Isolation)
'knowledge > computer science' 카테고리의 다른 글
concurrency control 기초: schedule과 serializability (0) | 2024.06.19 |
---|---|
[DB] 트랜잭션과 ACID (0) | 2024.06.18 |
[성능 테스트] Artillery 시나리오, 파라미터 (0) | 2024.06.07 |
[성능 테스트] Artillery 설치 (0) | 2024.06.06 |
[성능 테스트] 필요한 배경 지식 (0) | 2024.06.03 |