서비스를 만들다 보면 만약 사용자 정보 저장에 실패하게 될 경우,
Users 테이블은 생성됐는데, UserInfos 테이블의 사용자 정보는 저장되지 않은 상태로 남아있게 되는 상황이 발생할 수도 있다.
이런 종류의 문제를 해결하기 위해 MySQL과 같은 RDBMS에서는 Transaction 이라는 개념을 도입한다.
Transaction
여러 개의 작업(쿼리)을 묶어 하나의 작업 단위로 그룹화 하여 처리하는 방식이다.
Transaction은 ACID라는 아래 4개의 properties을 가진다.
ACID
Atomicity, Consistency, Isolation, Durability를 뜻한다.
DB에서 transaction이 안전하게 수행되기 위해서 반드시 가져야 하는 특성이기 때문에 이 4가지 특성이 transaction을 정의한다고 볼 수 있다.
1️⃣ Atomicity (원자성)
Transaction의 가장 대표적인 특징이다. 일괄적으로 처리되어야만 하는 연관된 여러 개의 작업(쿼리)들을 묶어서 하나의 작업 단위로 본다.
그룹화한 작업들이 전부 성공적으로 처리하거나, 아니면 전부 실패하도록 하면 작업의 완전성을 보장해줄 수 있다.
하나의 promise라도 rejected 되면 전부 rejected 되는 Promise.all()과 비슷한 개념이라고 할 수 있다.
A가 B에게 계좌이체라는 transaction을 할 때,
1. A 계좌의 금액을 차감한다.
2. B 계좌의 금액을 1에서의 금액만큼 증가시킨다.
작업1과 작업2는 같이 처리되어야 하며, 둘 중 하나만 성공하고 하나는 실패하는 일이 있어서는 안 된다. 이런 경우에 두 작업을 묶어서 transaction으로 처리한다고 볼 수 있다. 그러면 작업1과 작업2는 둘 다 성공하든지 둘 다 실패하든지 둘 중 하나만 가능하게 되는 것이다.
atom이란, 더이상 쪼갤 수 없다는 뜻이다. ( [어원] 고대 그리스어 atomos : a- (부정접두사) + tomos (자르다, 쪼개다) )
물리학에서도 원자(atom)는 우주 만물을 구성하고 있는, 더이상 쪼갤 수 없는, 물질의 가장 작은 단위를 의미했었다. (물론 현대 물리학에 와서는 원자를 구성하는 더 작은 입자 단위를 발견했고, 그 기본 입자들의 집합 또는 상호작용으로 자연현상을 설명한다. [참고] Standard Model)
2️⃣ Consistency (일관성)
Transaction 내부에서 처리되는 데이터가 Transaction 전과 후가 일관성 있게 Correct State로 유지되어야 한다.
데이터를 일관성 있게 유지하기 위해서 DB 내에서 정의한 데이터의 정상적인 상태. 도메인의 유효범위, 무결성 제약조건 등의 제약조건 등을 위배하지 않아야 한다.
Transaction 후의 데이터 상태가 Correct State가 되지 않는다면,
그 Transaction은 성공적으로 실행되지 않아야 한다는 뜻이다.
- Correct State: 잔액이 `0` 이상이어야 한다.
- Transaction: 잔액이 `0`인 계좌 A에서 `1,000`을 계좌이체
이때 Transaction 후의 잔액은 -1000이 되고 이는 Correct State가 아니므로,
이 Transaction은 실행되지 않아야 한다.
3️⃣ Isolation (격리성)
Transactions 간에 서로 영향을 주고 받지 않아야 한다.
즉, 어떤 Transaction이 실행 중인 경우 그 transaction이 읽거나 사용중인 DB 데이터가 다른 Transaction에 의해서 조회되거나 변경되지 않아야 한다는 것이다.
아래의 Concurrency(동시성)와 같은 문제가 생기는 걸 방지하여 데이터의 Integrity(무결성)을 보장하기 위한 중요한 특징이다.
Concurrency란 여러 클라이언트가 동시에 하나의 데이터를 사용하거나 공유하는 것을 뜻한다. 만약 여러 클라이언트가 동시에 같은 데이터를 접근하려고 한다면 (동시접속) 문제가 발생한다.
A 계좌에서 2번의 계좌이체가 거의 동시에 발생한다고 가정해보자.
1. Transaction 1에서는 A 계좌에서 1,000원을 차감:
A 계좌 잔고: `10,000` → `9,000`으로 수정
2. Transaction 2에서는 아직 `COMMIT` 되지 않은 Transaction 1의 데이터를 바탕으로 A 계좌에서 1,000원을 차감:
A 계좌 잔고: `9,000` → `8,000`으로 수정
3. Transaction 1에서는 Error가 발생하여, Transaction을 `ROLLBACK`하게 되었다.
A 계좌 잔고: 원래대로 `10,000`이 되었다.
4. 그런데 Transaction 2에서는 잘못된 데이터를 바탕으로 작업을 수행한 후 Transaction을 `COMMIT` 하게 되었다.
A 계좌 잔고: `8,000`으로 잘못 표시되는 문제 발생
따라서 transaction을 실행하면, 해당 transaction이 접근하는 DB 데이터에 다른 transaction이 접근하지 못하게 Lock을 걸어 Isolation을 구현하게 된다.
데이터베이스 전체를 Lock할 것인지, 테이블 하나를 Lock할 것인지, 아니면 특정 변수만 Lock할 것인지와 같이, 한 Transaction이 접근 중인 데이터의 어느 범위까지 다른 Transaction의 접근을 제한할 것인지를 설정한다.
Global Locks 또는 Database Locks
데이터베이스 내의 모든 테이블에 대한 접근을 제한한다.
예) FLUSH TABLES WITH READ LOCK;
Table Locks
특정 테이블을 지정해서 그 테이블 대한 접근을 제한한다.
예) LOCK TABLES 테이블이름 READ;
Named Locks
특정한 문자열에 대한 접근을 제한한다.
예) SELECT GET_LOCK('문자열이름', 제한시간_초단위);
Metadata Locks
다른 사용자가 작업 중인 테이블의 동일한 row, 동일한 object 를 동시에 수정하지 못하게 제한한다. MySQL은 테이블 구조를 변경할 때, 별도의 Metadata Lock을 설정할 필요 없이 자체적으로 Metadata Lock을 설정해준다.
예) ALTER TABLE 테이블이름 ADD COLUMN 컬럼이름 타입명;
한 Transaction이 접근 중인 데이터에 대한 다른 Transaction의 접근 강도를 설정한다. 아래 그림과 같이 4개의 Levels가 있고, Level이 높아지면 Transaction간 Isolation 강도가 높아지지만, 성능저하도 야기된다.

READ UNCOMMITTED : 락을 걸지 않는다. commit 되지 않은 데이터도 읽기가 가능하다. 가장 낮은 수준의 격리 수준이다.READ COMMITED : commit 하기 전까지 공유락을 건다. commit이 된 이후의 데이터만 읽기가 허용된다.REPEATABLE READ : commmit이 끝나도 공유락을 풀지 않는다. transaction이 완전히 종료될 때까지 락을 유지한다. 데이터 삽입은 가능하다.SERIALIZABLE : transaction이 완전히 종료될 때까지 해당 데이터를 읽기도, 삽입도 불가능하다. 가장 높은 수준의 격리 수준이다.4️⃣ Durability (지속성)
Transaction이 성공적으로 완료되든 완료되지 않든, 해당 Transaction이 접근한 데이터가 어떠한 상황에서도 보존되어야 한다.
START TRANSACTION; 명령어를 통해 Transaction을 시작한다.
Transaction이 성공적으로 완료되면, 해당 Transaction에 의해 생성 또는 변경된 데이터는 DB에 COMMIT; 명령어를 통해 영구적으로 저장된다.
하지만, Transaction 수행 도중 Transaction이 실패하거나 또는 시스템이 비정상 종료되더라도, 시스템은 Transaction Log를 통해 아직 Commit 되지 않은 Transaction을 해당 Transaction을 시작하기 이전 시점으로 ROLLBACK; 명령어를 통해 복구할 수 있다.
작업이 성공하면, 데이터는 작업이 완료된 상태를 저장하고,
작업이 실패하면, 데이터는 작업이 진행되던 도중의 어중간한 상태를 유지하는 것이 아니라, 작업을 시작하기 전 상태로 롤백한다.