DB 트랜잭션 완벽 가이드: ACID 속성으로 데이터 무결성 보장하기

데이터베이스 트랜잭션과 ACID 속성을 실전 예제로 배웁니다. 송금 시스템으로 원자성, 일관성, 격리성, 지속성을 이해합니다.

럿지 AI 팀
6분 읽기

DB 트랜잭션 완벽 가이드



트랜잭션이란?



**정의:** 하나의 논리적 작업 단위

**핵심:** 모두 성공 or 모두 실패

실전 예제: 송금 시스템



시나리오



홍길동 → 김철수에게 10,000원 송금

필요한 작업



1. 홍길동 잔액 -10,000원
2. 김철수 잔액 +10,000원

문제 상황



``sql
-- 1. 홍길동 잔액 차감
UPDATE accounts SET balance = balance - 10000 WHERE user_id = 1;
-- 성공!

-- 2. 여기서 시스템 다운!

-- 3. 김철수 잔액 증가 (실행 안 됨)
UPDATE accounts SET balance = balance + 10000 WHERE user_id = 2;
`

**결과:** 10,000원이 증발!

ACID 속성



A - 원자성 (Atomicity)



**의미:** 모두 성공 or 모두 실패

**트랜잭션 적용:**

`sql
START TRANSACTION;

UPDATE accounts SET balance = balance - 10000 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 10000 WHERE user_id = 2;

COMMIT; -- 모두 성공!
`

**오류 발생 시:**

`sql
START TRANSACTION;

UPDATE accounts SET balance = balance - 10000 WHERE user_id = 1;
-- 여기서 오류 발생!
UPDATE accounts SET balance = balance + 10000 WHERE user_id = 2;

ROLLBACK; -- 모두 취소!
`

C - 일관성 (Consistency)



**의미:** DB 규칙 항상 유지

**제약 조건:**

`sql
-- 잔액은 항상 0 이상
ALTER TABLE accounts
ADD CONSTRAINT check_balance CHECK (balance >= 0);
`

**테스트:**

`sql
START TRANSACTION;

-- 잔액 5,000원인데 10,000원 송금 시도
UPDATE accounts SET balance = balance - 10000 WHERE user_id = 1;
-- ERROR: Check constraint 위반!

ROLLBACK; -- 자동 롤백
`

I - 격리성 (Isolation)



**의미:** 동시 트랜잭션 간 간섭 없음

**문제 상황:**

`sql
-- 트랜잭션 1 (A가 B에게 송금)
START TRANSACTION;
UPDATE accounts SET balance = balance - 10000 WHERE user_id = 1;
-- 여기서 대기...

-- 트랜잭션 2 (C가 A의 잔액 조회)
SELECT balance FROM accounts WHERE user_id = 1;
-- 뭘 보여줘야 할까?
`

**격리 수준:**

`sql
-- READ COMMITTED (일반적)
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- SERIALIZABLE (최고 수준)
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
`

D - 지속성 (Durability)



**의미:** COMMIT된 데이터는 영구 저장

**테스트:**

`sql
START TRANSACTION;
UPDATE accounts SET balance = balance + 10000 WHERE user_id = 1;
COMMIT;

-- 여기서 서버 다운해도...
-- 재시작 후 데이터 유지!
`

실전 예제



1. 송금 시스템



`sql
START TRANSACTION;

-- 1. 잔액 확인
SELECT balance FROM accounts WHERE user_id = 1 FOR UPDATE;
-- FOR UPDATE: 다른 트랜잭션의 수정 차단

-- 2. 잔액 차감
UPDATE accounts SET balance = balance - 10000
WHERE user_id = 1 AND balance >= 10000;

-- 3. 영향받은 행 확인
IF ROW_COUNT() = 0 THEN
ROLLBACK;
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '잔액 부족';
END IF;

-- 4. 수취인 잔액 증가
UPDATE accounts SET balance = balance + 10000 WHERE user_id = 2;

-- 5. 거래 기록
INSERT INTO transactions (from_user, to_user, amount) VALUES (1, 2, 10000);

COMMIT;
`

2. 주문 처리



`sql
START TRANSACTION;

-- 1. 주문 생성
INSERT INTO orders (member_id, total_amount) VALUES (1, 50000);
SET @order_id = LAST_INSERT_ID();

-- 2. 주문 상세
INSERT INTO order_items (order_id, product_id, quantity, price)
VALUES (@order_id, 101, 2, 25000);

-- 3. 재고 차감
UPDATE products SET stock = stock - 2
WHERE product_id = 101 AND stock >= 2;

-- 4. 재고 부족 시 롤백
IF ROW_COUNT() = 0 THEN
ROLLBACK;
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '재고 부족';
END IF;

-- 5. 포인트 사용
UPDATE members SET points = points - 5000
WHERE member_id = 1 AND points >= 5000;

COMMIT;
`

격리 수준 비교



READ UNCOMMITTED



`sql
-- 트랜잭션 1
START TRANSACTION;
UPDATE accounts SET balance = 100000 WHERE user_id = 1;
-- COMMIT 전!

-- 트랜잭션 2
SELECT balance FROM accounts WHERE user_id = 1;
-- 100000 보임 (Dirty Read)

-- 트랜잭션 1
ROLLBACK; -- 취소!
-- 트랜잭션 2는 잘못된 값을 봤음!
`

READ COMMITTED (권장)



`sql
-- 트랜잭션 1
START TRANSACTION;
UPDATE accounts SET balance = 100000 WHERE user_id = 1;

-- 트랜잭션 2
SELECT balance FROM accounts WHERE user_id = 1;
-- 이전 값 보임 (Dirty Read 방지)

-- 트랜잭션 1
COMMIT;

-- 트랜잭션 2
SELECT balance FROM accounts WHERE user_id = 1;
-- 이제 100000 보임
`

REPEATABLE READ



`sql
-- 트랜잭션 1
START TRANSACTION;
SELECT balance FROM accounts WHERE user_id = 1; -- 50000

-- 트랜잭션 2
UPDATE accounts SET balance = 100000 WHERE user_id = 1;
COMMIT;

-- 트랜잭션 1
SELECT balance FROM accounts WHERE user_id = 1;
-- 여전히 50000 (반복 읽기 보장)

COMMIT;
`

교착 상태 (Deadlock)



문제



`sql
-- 트랜잭션 1
START TRANSACTION;
UPDATE accounts SET balance = balance - 1000 WHERE user_id = 1;
-- 트랜잭션 2를 기다림...

-- 트랜잭션 2
START TRANSACTION;
UPDATE accounts SET balance = balance - 1000 WHERE user_id = 2;
-- 트랜잭션 1을 기다림...

-- 둘 다 무한 대기! (Deadlock)
`

해결책



`sql
-- 항상 같은 순서로 잠금
START TRANSACTION;
-- user_id 작은 것부터
UPDATE accounts SET balance = balance - 1000
WHERE user_id = 1;
UPDATE accounts SET balance = balance - 1000
WHERE user_id = 2;
COMMIT;
`

오류 처리



자동 롤백



`sql
START TRANSACTION;
UPDATE accounts SET balance = balance - 10000 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 10000 WHERE user_id = 999; -- 없는 사용자
-- 오류 발생 → 자동 ROLLBACK
`

명시적 롤백



`sql
START TRANSACTION;

-- 조건 확인
SELECT balance INTO @balance FROM accounts WHERE user_id = 1;

IF @balance < 10000 THEN
ROLLBACK;
ELSE
UPDATE accounts SET balance = balance - 10000 WHERE user_id = 1;
COMMIT;
END IF;
`

성능 vs 안전성



빠름 (위험)



`sql
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- Dirty Read 가능
`

느림 (안전)



`sql
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 완벽한 격리, 느림
`

권장 (균형)



`sql
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 대부분의 경우 충분
`

핵심 포인트



1. 항상 트랜잭션 사용



**금융, 주문 등:**
데이터 무결성 필수

2. 짧게 유지



**나쁨:**
`sql
START TRANSACTION;
-- 복잡한 계산 (30초)
UPDATE ...;
COMMIT;
`

**좋음:**
`sql
-- 계산 먼저 (트랜잭션 밖)
-- 빠른 UPDATE (트랜잭션)
START TRANSACTION;
UPDATE ...;
COMMIT;
``

3. 격리 수준 선택



**대부분:** READ COMMITTED

**특수:** SERIALIZABLE (중요 거래)

더 배우기



김영한의 실전 데이터베이스
- 트랜잭션 심화
- 실전 패턴
- 성능 최적화

---

**태그**: #트랜잭션 #ACID #데이터무결성 #DB안전성 #튜토리얼

L

럿지 AI 팀

AI 기술과 비즈니스 혁신을 선도하는 럿지 AI의 콘텐츠 팀입니다.