(모든 설명은 MySQL 8.0 기준 입니다.)
트랜잭션이란?
트랜잭션은 하나의 논리적인 기능을 수행하기 위해 여러 개의 작업 셋을 하나로 묶은 작업의 단위다. 트랜잭션은 원자성, 일관성, 격리성, 지속성 4가지의 특징을 갖는다. (ACID)
ACID
원자성 (Atomicity)
트랜잭션을 구성하는 작업 전체가 성공하거나 아니면 전체가 실패하는 것 둘 중 하나만을 보장한다는 특징이다.
트랜잭션을 구성하는 여러 쿼리 중 일부분만 성공하거나 일부분만 실패하면 데이터의 정합성에 문제가 생기게 된다.
예시를 보면서 이해해보자.
기능 : 은행 시스템의 송금
송금 기능의 순서
1. 계좌 잔액 테이블에서 송금자 A의 잔액을 차감한다.
2. 계좌 잔액 테이블에서 돈을 받는 B의 잔액을 추가한다.
3. 거래 내역 테이블에서 A가 B로 송금한 내역을 추가한다.
하지만 이 과정에서 2번이 실패했으나 원자성이 보장되지 않을 경우에 A는 송금을 했지만 B의 계좌에는 송금된 잔액이 추가되지 않아 문제가 발생하게 된다.
이처럼 하나의 기능이 성공했거나 실패했을 때 데이터의 정합성을 해치지 않게 하는 것이 원자성이다.
일관성 (Consistency)
트랜잭션 실행 이전의 데이터 베이스의 도메인 무결성 제약조건을 유효하게 보장하고 있는 상태라면 트랜잭션 실행 이후에도 일관되게 도메인 무결성 제약조건을 유효하게 보장해주어야 하는 특징이다.
예시로 계좌에 만원이 있을 때 만원 이상으로 출금을 하거나 송금을 할 수 없어야 할 것이다.
격리성 (Isolation)
실행중인 트랜잭션의 중간 결과를 다른 트랜잭션이 접근할 수 없다는 특징이다.
트랜잭션과 관련된 격리성 이슈와 이를 해결하기 위한 격리 수준은 아래에서 더 다뤄보겠다.
지속성 (Durability)
성공적으로 반영된 트랜잭션은 어떠한 변경에도 영속적으로 반영되어야 하는 특징을 의미한다.
트랜잭션 격리 수준
트랜잭션 격리 수준이란 여러 개의 트랜잭션이 동시에 처리 될 때 특정 트랜잭션이 다른 트랜잭션에서 변경한거나 조회하는 데이터를 볼 수 있게 허용할지 말지 결정하는 것이다.
일반적으로 신뢰성, 일관성과 성능은 반비례 하게 된다. 격리 수준을 높게 설정 할 경우 성능은 떨어질 수 있어도 데이터의 신뢰성, 일관성을 보장할 수 있고, 반대로 격리 수준을 낮출 수록 신뢰성과 일관성은 떨어지지만 성능은 올라가게 된다. 격리수준에 따라 완전히 선형적으로 성능이 올라가거나 떨어지는 것은 아니지만 적절한 격리 수준을 설정하여 신뢰성과 일관성을 유지하면서 성능 또한 적절히 유지할 수 있는 단계를 찾아야 한다.
격리 수준은 다음과 같은 단계로 나눠져있다.
1. READ UNCOMMITTED (커밋되지 않은 읽기)
2. READ COMMITTED (커밋되지 않은 읽기)
3. REPEATABLE READ (반복 가능한 읽기)
4. SERIALIZABLE (직렬화 가능)
밑으로 갈수록 격리 수준이 올라가며 동시 처리 성능이 떨어지게 된다. 반대로 위로 갈 수록 격리 수준이 떨어지면서 데이터 부정합 문제가 발생하지만 동시 처리 성능은 올라가게 된다.
그럼 다음으로 격리 수준을 보기 앞서서 격리 수준에 따라서 발생할 수 있는 문제점을 먼저 살펴보자.
트랜잭션 격리 수준에 따른 문제점
Dirty Read
더티 리드는 특정 트랜잭션에 의해 데이터가 변경되었지만, 아직 커밋되지 않은 상황에서 다른 트랜잭션이 해당 변경 사항을 조회할 수 있는 문제를 말한다.
이것이 왜 문제가 될까? 어처피 변경 할 데이터 였는데 미리 읽으면 좋은거 아닌가?
항상 트랜잭션이 성공적으로 커밋된다면 상관이 없겠지만 현실세계 에서는 전혀 그렇지 않다. 작업 도중 물리적이든 아니든 트랜잭션 A가 데이터를 변경하고 커밋하지 않은 상태에서 다른 트랜잭션 B가 변경된 데이터를 조회했을 경우 트랜잭션 A가 롤백을 한다면 트랜잭션 B가 다시 조회를 하면 다른 값을 조회되게 된다. 만약 그대로 트랜잭션 B가 해당 데이터를 수정 할 경우에 데이터 일관성을 잃게 된다.
Non-Repeatable Read (반복 불가능한 조회)
같은 트랜잭션 내에서 같은 데이터를 여러번 조회 했을 때 조회한 데이터가 일관되지 않은 경우를 의미한다.
Phantom Read
Non-Repeatable Read의 한 종류로 조회해온 결과의 행이 새로 생기거나 없어지는 현상이다.
READ UNCOMMITTED
커밋되지 않은 트랜잭션의 데이터 변경 내용을 다른 트랜잭션이 조회하는 것을 허용하는 격리 수준이다.
데이터 부정합 문제가 발생할 가능성이 높지만 동시 접근 성능은 가장 빠르다. 데이터를 어림잡아 집계하는 등의 연산에서 사용하면 좋다. (ex. 조회수)
만약 여기서 트랜잭션 A가 롤백을 할 경우에 크리티컬한 문제가 발생하게 된다.
위와 같이 치명적인 데이터 부정합 문제가 발생한다.
만약 금융거래 기능에서 이런 문제가 발생할 경우 큰 손실 비용이 발생하게 된다.
발생할 수 있는 문제
- Dirty Read
- Non-Repeatable Read
- Phantom Read
READ COMMITTED
커밋이 완료된 트랜잭션의 변경사항만 다른 트랜잭션에서 조회할 수 있도록 허용하는 격리 수준이다.
만약 다른 트랜잭션에서 아직 커밋되지 않은 데이터에 접근하려 하는 경우에 Undo 로그에 있는 변경 이전의 데이터를 가져와 SELECT 하게 된다. ORACLE 에서 기본값으로 사용하는 격리 수준이다.
하지만 트랜잭션 내에서 커밋되지 않은 데이터를 조회한 후에 트랜잭션 A가 커밋을 한 후에 트랜잭션 B가 재조회를 할 경우에 다른 데이터 값이 나와 반복 불가능한 조회 문제 (Non-Repeatable Read)와 팬텀 리드가 발생하게 된다.
발생할 수 있는 문제
- Non-Repeatable Read
- Phantom Read
REPEATABLE READ
트랜잭션 내에서 특정 데이터를 조회 시 항상 같은 데이터를 응답하는 것을 보장하는 격리 수준이다. 하지만 SERIALIZABLE과 다르게 행이 추가되는 것을 막지 않는다. 이로 인해 팬텀 리드 현상이 발생할 수 있다.
MySQL의 InnoDB 스토리지 엔진의 경우 기본값이 REPEATABLE READ이다.
이렇게 다른 트랜잭션에서 커밋을 하더라도 일관된 조회가 가능한 이유는 Undo 로그와 트랜잭션 id 덕분이다.
그런데 READ COMMITTED 역시 undo 로그에서 변경 이전의 데이터를 동일하게 읽어온다. 차이점은 MVCC를 통한 트랜잭션 id를 통해 몇번 째 이전 버전까지 찾아 들어가느냐 이다.
트랜잭션 A가 커밋을 완료하더라도 커밋된 데이터의 트랜잭션 id가 트랜잭션 B의 트랜잭션 id보다 높기 때문에 이전 버전의 데이터를 undo 로그에서 찾아 조회한다. 이로 인해 항상 일관된 읽기가 가능하다.
발생할 수 있는 문제
- Phantom Read (Inno DB 제외)
SERIALIZABLE
특정 트랜잭션이 사용중인 테이블의 모든 튜플을 다른 트랜잭션들이 접근할 수 없도록 잠근다.
가장 높은 데이터 정합성을 갖으나, 동시 처리 성능은 이들 중 가장 떨어진다. 이 격리 수준에서는 단순한 SELECT 연산에도 데이터베이스 락이 걸려 다른 트랜잭션에서 데이터 접근 할 수 없게 된다.
보통 이정도 격리 수준까지는 성능 때문에 잘 사용하지 않는다.
추가적인 의문점들
- Inno DB는 왜 REPEATABLE READ 에서도 팬텀 리드가 발생하지 않을까?
- Undo 로그 & Redo 로그
- 데이터베이스 잠금
참고
데이터베이스 트랜잭션 격리 수준과 격리 수준에 따른 문제점 (hudi.blog)
데이터베이스 트랜잭션 격리 수준과 격리 수준에 따른 문제점
이 포스팅은 우아한테크코스 4기 2022 CS Plant 스터디에서 기술 면접을 대비하기 위해 작성되었습니다. 트랜잭션 격리 수준 복수개의 트랜잭션이 한번에 처리될 때, 특정 트랜잭션이 변경하거나
hudi.blog
Chat GPT 4.0
Real MySQL 8.0 1권
'Dev > DB' 카테고리의 다른 글
이상현상 (Anomaly) (0) | 2024.02.01 |
---|---|
디스크 읽기 방식과 쿼리 성능 개선 (1) | 2024.01.25 |
SQL 처리 과정 (0) | 2024.01.18 |