Database ‐ 트랜잭션 - dnwls16071/Backend_Summary GitHub Wiki
📚 트랜잭션이 왜 필요한지?
트랜잭션(Transaction)
- 논리적으로 쪼갤 수 없는 하나 이상의 데이터베이스 작업 묶음을 말한다.
📚 커밋과 롤백
START TRANSACTION
- 트랜잭션을 시작하라고 선언하는 부분으로 이 명령어 이후에 실행되는 모든 쿼리는 하나의 트랜잭션으로 묶여 관리된다.
COMMIT
- 트랜잭션 내의 모든 작업이 성공적으로 완료가 되었으니. 지금까지의 변경 사항을 디스크에 영구적으로 저장한다.
ROLLBACK
- 문제가 발생해 작업을 중단하라는 의미로 해당 트랜잭션 내에서 실행했던 모든 변경 사항을 전부 취소하고, 트랜잭션이 시작되기 직전의 상태로 완벽하게 되돌리라는 의미의 작업 취소 명령이다.
❗MySQL의 autocommit
- MySQL은 기본적으로 autocommit 모드가 활성화되어 있다. 이것은 실행하는 SQL문 하나하나를 각각의 트랜잭션으로 간주해 성공적으로 실행되는 즉시 자동으로 commit해버린다는 의미이다.
📚 트랜잭션의 ACID⭐
Atomicity : 원자성
- 트랜잭션은 하나의 원자처럼 더 이상 쪼갤 수 없는 논리적 단위이며, 전부 성공하거나 실패한다.
- 모두 성공(COMMIT)
- 모두 실패(ROLLBACK)
Consistency : 일관성
- 트랜잭션이 성공적으로 완료되면, 데이터베이스는 항상 일관된 상태를 유지해야 한다.
Isolation : 격리성
- 하나의 트랜잭션이 실행 중일 때, 다른 트랜잭션이 해당 트랜잭션의 중간 결과에 끼어들어 간섭할 수 없다.
❗트랜잭션의 격리성이 없다면 발생하는 문제 정리
- A가 B에게 1만원을 이체하는 트랜잭션(T1)을 실행 중이다.
UPDATE로 A의 잔고는 4만원으로 바뀌었지만, 아직COMMIT은 하지 않아 B의 잔고는 2만원인 '중간 상태'다. 그림에서 오른쪽의[50000]의 값은 변경 전의 원본 값이다. - 바로 그 순간 옆 창구에서 은행 직원이 전체 고객의 총잔고를 계산하는 트랜잭션(T2)을 실행한다.
- T2는 A의 잔고를 4만원으로, B의 잔고를 2만원으로 읽어서 총액이 6만원이라는 잘못된 결과를 얻게 된다. T1의 아직 완료되지 않은 불안정한 상태를 본 것이다.(이를 Dirty Read라고 한다.)
- T1은 B의 값에 10000원을 더한다.
- 트랜잭션을 커밋한다.
- 전체 잔고를 다시 계산해보면 70000원이 된다.
❗트랜잭션의 격리성이 보장되는 경우
- A가 B에게 1만원을 이체하는 트랜잭션(T1)을 실행 중이다.
UPDATE로 A의 잔고는 4만원으로 바뀌었지만, 아직COMMIT은 하지 않아 B의 잔고는 2만원인 '중간 상태'다. 그림에서[50000]의 값은 변경 전의 원본 값이다. - 바로 그 순간 옆 창구에서 은행 직원이 전체 고객의 총잔고를 계산하는 트랜잭션(T2)을 실행한다. T2는 T1이 아직
COMMIT되지 않았기 때문에, T1이 임시로 변경한 내용을 보지 못한다. T2는 T1이 시작되기 전의 값, 즉 A의 잔고 5만원과 B의 잔고 2만원을 읽어서 정확한 총액 7만원을 계산해낸다. - T1은 B의 값에 10000원을 더한다. 그림에서
[20000]의 값은 변경 전의 원본 값이다. - 트랜잭션을 커밋한다.
- 전체 잔고를 다시 계산해보면 70000원이 된다.
Duration : 지속성
- 성공적으로 완료되어 COMMIT된 트랜잭션의 결과는 시스템에 장애가 발생하더라도 영구적으로 보존된다.
📚 트랜잭션의 격리 수준⭐
데이터 정합성(Correctness)과 동시성/성능(Concurrency/Performance) 트레이드 오프
- 격리 수준을 높이면 데이터 정합성은 올라가나 동시성이 떨어져 성능이 저하되고, 격리 수준을 낮추면 성능은 올라가나 특정 데이터 부정합 문제가 발생할 수 있다는 점을 염두에 두어야 한다.
동시성 문제
- 더티 리드(Dirty Read)
- 정의 : 한 트랜잭션이 아직 COMMIT하지 않은 수정 중인 데이터를 다른 트랜잭션이 읽는 것.
- Ex. 트랜잭션 A가 특정 상품의 가격을 100원에서 120원으로 바꾸고 아직
COMMIT하지 않았다. 이 때, 트랜잭션 B가 이 상품의 가격을 조회했더니 '120원'이 보였다. 하지만 잠시 후 트랜잭션 A가 작업을 취소(ROLLBACK)해버리면, 가격은 다시 100원이 된다. 트랜잭션 B는 결국 존재하지도 않는 '더러운' 데이터를 읽은 셈이다.
- 반복 불가능 읽기(Non-Repeatable Read)
- 정의 : 한 트랜잭션 내에서 똑같은 SELECT 쿼리를 두 번 실행했는데, 그 사이에 다른 트랜잭션이 값을 수정하고 COMMIT하는 바람에 두 쿼리 결과가 다르게 나오는 상황
- Ex. 트랜잭션 A가 특정 상품의 재고 필드 값이 '10개'인 것을 확인했다. 잠시 다른 작업을 하다가 다시 재고를 확인했더니, 그 사이에 다른 트랜잭션 B가 그 상품을 하나 사가서 재고 필드 값이 '9개'로 바뀌어 있다. 트랜잭션 A 안에서 같은 데이터의 반복 조회가 불가능해진 것이다.
- 유령 읽기(Phantom Read)
- 정의 : 한 트랜잭션 내에서 특정 범위 데이터를 두 번 읽었는데 첫 번째 조회에서는 없었던 새로운 행이 두 번째 조회에서 나타나는 현상. 다른 트랜잭션이 새로운 행을 INSERT하고 COMMIT했기 때문에 발생한다.
- Ex, 트랜잭션 A가 '전자기기' 카테고리의 상품 수를 세었더니 '5개'였다. 잠시 후 똑같이 수를 세었더니, 그 사이에 다른 트랜잭션 B가 새로운 '전자기기' 상품을 등록해서 '6개'가 되었다. 없었던 유령 상품이 나타난 것이다.
4가지 격리 표준 수준
- SQL 표준은 이런 문제들을 방지하는 강도에 따라 4가지 격리 수준을 정의한다.
READ UNCOMMITED: 거의 아무것도 막지 않는 가장 낮은 수준. 정합성 이슈가 많아 거의 사용되지 않는다.READ COMMITED: 더티 리드를 방지한다. 즉, COMMIT된 데이터만 읽을 수 있고 Oracle, SQL Server등 많은 데이터베이스의 기본 격리 수준이다.REPEATABLE READ: 한 트랜잭션 안에서는 데이터의 일관된 조회를 보장해준다. MySQL의 InnoDB 스토리지 엔진이 사용하는 기본 격리 수준이다.SERIALIZABLE: 가장 엄격한 수준으로 동시성 문제는 완벽히 차단되나 트랜잭션을 거의 순서대로 실행시켜 동시 처리 성능이 가장 낮다.
READ UNCOMMITTED로 낮추는 것을 고려하는 경우
- 대용량 데이터에 대한 실시간 집계나 통계 작업 수행 시
- 그러나 실무에서 거의 사용되지 않는다.
READ COMMITTED로 낮추는 것을 고려하는 경우
- 성능과 동시성 확보가 매우 중요하고 약간의 데이터 비일관성을 감수할 수 있는 선에서
SERIALIZABLE로 높이는 것을 고려하는 경우
- 거의 잘 사용되지 않는다.
- 대부분의 경우 애플리케이션 코드 레벨에서
SELECT ... FOR UPDATE와 같은 비관적 락을 사용하거나 버전 번호를 두는 낙관적 락을 구현해 문제를 해결하는 경우가 많다.