오늘은 DB에 대해서 공부할 때 알아두면 좋은 데이터베이스 트랜잭션과 일관성 유지 기법 ACID 에 대해서 글을 작성하려 한다.
트랜잭션(Transaction)
-
트랜잭션은 논리적 작업의 단위로, 하나의 작업 단위를 의미
-
모든 작업이 성공적으로 완료되거나, 전부 취소(롤백)되는 것을 보장
-
트랜잭션은 데이터의 일관성과 무결성을 유지하기 위해 중요
-
예시
- 은행 송금: 두 작업이 하나의 트랜잭션으로 처리
- 랜(선)뽑(기): 패배 기록이 안남게 경기에서 질거 같으면 인터넷을 끊음
보통 MySQL 을 사용하는 이유가 InnoDB 가 Transaction을 거의 완벽하게 지원해준다.
트랜잭션의 특징 = ACID

- Atomicity(원자성)
- 트랜잭션의 모든 작업이 전부 수행되거나 전혀 수행되지 않음을 보장
- 실패 시, 모든 변경 사항이 Rollback
- Consistency(일관성)
- 트랜잭션 실행 전과 실행 후 DB가 일관된 상태를 유지함을 보장
- Isolation(격리성)
- 트랜잭션이 동시에 실행될 때, 각각의 트랜잭션이 독립적으로 실행되도록 보장
- 트랜잭션간 간섭 X
- 첫 번째 시도한 송금이 실패했다고 두번 째 송금에 영향을 주면 안된다.
- Durability(지속성)
- 트랜잭션이 성공적으로 완료되면, 그 결과는 영구적으로 저장
- 시스템 장애가 발생하더라도 데이터는 손실 X
여기서 session 의 개념이 적용된다
- connection 하나를 가져와서 connection에서 session을 생성하고 그 session안에 쿼리를 잔뜩 쌓아놓은 다음에 이 session을 한번에 적용한다.
accounts 라는 테이블 만들고 데이터 넣어놓았다.

현재 1번계좌에 돈이 있고 2번에 없기 때문에 1번이 2번으로 송금을 해야 한다.
쿼리문 한줄 씩 먼저 transaction을 돌리고 100 원을 뺴준 뒤 데이터 확인 해보면

100 빠졌다.

만약 이상태에서 ROllBACK 을 하면 어떻게 될까?
- ROllBACK을 하고 다시 데이터 조회해보면

100 을 뺏던게 원상태로 돌아오면서 ACID 에서 원자성과 일관성을 잘 된걸 확인할 수 있다.

이번에는 1에서 100을 빼고 2에 100을 넣어주는 쿼리를 한번에 실행한 후 데이터 조회해보면

2에다가 100 이 송금됨

이후 COMMIT 을 하게 되면 최종적으로 적용이 되는 것이고

COMMIT 을 통해 적용 후 ROLLBACK 을 하여도 이미 DB에 반영되었기에 다시 ROLLBACK 되지 않는다.

이후 INNODB 에서 상태변화를 체크해 보기 위해서 쿼리문 다시 실행하고

SHOW ENGINE INNODB STATUS; 쿼리 통해서 값 체크해보면 아래처럼 나오는데
- 현재 위에서 1에 100을 빼는 쿼리만 실행하고 아직 commit을 하지 않았기 때문에 젤 하단에 값 보면 lock 걸려 있다는 내용을 볼 수 있음(아직 트랜잭션이 묶여있다.)

이후 COMMIT 을 하고 다시 SHOW ENGINE INNODB STATUS 을 해보면
- 아까 존재하던 lock 이 사라짐

정리 : INNODB 를 통해 현재 트랜잭션이 얼마나 쌓였나 관찰 가능
트랜잭션의 상태
트랜잭션이 현재 어떤 상태인지 아는 것도 중요하다.
Transaction과 ACID
- Active: 트랜잭션이 실행 중이며, 아직 완료되지 않은 상태.
- Partially Committed: 트랜잭션의 마지막 연산이 실행된 후, 모든 변경 사항이 커밋되기 전 상태.
- Committed: 트랜잭션이 성공적으로 완료되어 모든 변경 사항이 데이터베이스에 저장된 상태.
- Failed: 트랜잭션 중 오류가 발생하여 더 이상 실행을 진행할 수 없는 상태.
- Aborted: 트랜잭션이 실패하여 모든 변경 사항이 롤백된 상태.

처음에 start transaction 이 되면 active 상태가 된다 (쿼리 언제들어 오나 살펴보고 있는 상태)
- 위에서 InnoDB를 통해 봤던 active 된 transaction
Partially Committed 에서 쿼리가 하나씩 처리됨
그러다 Failed 상태가 되면 ROllBACK 을 통해 Aborted 상태가 된다.
반면에 모든 쿼리가 성공하면 Committed 상태로 완료됨
정리 : 트랜잭션은 크게 2가지의 위의 흐름으로 진행된다.
트랜잭션 관리
-
로그: 트랜잭션의 변경 사항을 기록해서 장애시 복구
- 로그만 남기는 것 보다는 어디서 끊겼는지를 알아야하기에 체크포인트 중요하다

-
잠금(Lock) 매커니즘: 동시성 제어로 트랜잭션 간 간섭을 방지
- 실패한다하면 트랜잭션을 끊고 ROLLBACK 한다음에 다시 나중에 시도해야 한다.
-
체크포인트: 현재 시스템 상태를 저장해서 장애 발생시 체크포인트부터 복구할 수 있도록 구성
- 트랜잭션 중간 중간에 이 트랜잭션 상태를 어디다가 저장을 할 수 있는 곳을 만들어 두어야 한다.
트랜잭션 관리를 위한 격리 수준
- Read Uncommitted
- 트랜잭션이 커밋되지 않은 데이터를 읽을 수 있음. 데이터 무결성이 낮음
- 위의 예시 실습에서 accounts 계좌에 1에서 100 뺴주는 쿼리 실행 후 바로 계좌 확인 가능했다.
- COMMIT 되지 않은 데이터는 언제든지 ROLLBACK 이 가능하기 때문에 대도록이면 Uncommitted 상태는 읽으면 안된다.
- Read Committed
- 트랜잭션이 커밋된 데이터만 읽을 수 있음. 다른 트랜잭션의 중간 변경 사항을 읽을 수 없음
- Repeatable Read
- 트랜잭션이 시작된 시점의 데이터를 읽으며
- 동일한 쿼리를 여러 번 실행해도 동일한 결과를 반환
- start transaction 이 시작되면 그 상태에서 memory 에 띄워놓고 유지하고 있는 것
- 이렇게 되면 COMMIT 되기를 기다릴 필요없다. COMMIT 되기 전 데이터를 보고 있기 때문
- Serializable(가장 높은 격리 수준)
- 트랜잭션이 순차적으로 실행되는 것처럼 동작
- 동시성 제어를 엄격하게 하여 데이터 무결성을 보장
보통 MySQL 에서 Read Committed 만 사용해도 충분함
- 그 이상을 원한다면 MySQL 에서 transaction lock 을 쓸게 아니라 memory 상에서 병렬 처리나 동시성 처리를 할 수 있게 따로 사용하는 것이 좋다. (요즘 GO 많이 사용함)
Serializable 은 트랜잭션에 또 트랜잭션을 거는 수준이기에 ROLLBACK 이 잦다.
- 하위 트랜잭션이 ROLLBACK 나면 당연히 상위도 ROLLBACK 되기 떄문
- 무결성은 보장하지만 많은 retry가 필요할 수 있다.