CS

트랜잭션과 격리 수준 완벽 가이드

민이(MInE) 2024. 11. 12. 15:08
반응형

목차

  1. 트랜잭션 기초
  2. ACID 속성
  3. 트랜잭션 격리 수준
  4. 격리 수준에 따른 문제점
  5. 실전 예제
  6. 성능과 격리 수준
  7. 모범 사례

트랜잭션 기초

트랜잭션이란?

트랜잭션은 데이터베이스의 상태를 변화시키는 하나의 논리적 작업 단위입니다. 예를 들어, 계좌 이체는 출금과 입금이라는 두 개의 작업이 하나의 트랜잭션으로 처리되어야 합니다.

기본 예제

-- 계좌 이체 트랜잭션
START TRANSACTION;

UPDATE accounts SET balance = balance - 1000 WHERE account_id = 'A';
UPDATE accounts SET balance = balance + 1000 WHERE account_id = 'B';

COMMIT;

ACID 속성

1. Atomicity (원자성)

START TRANSACTION;

UPDATE accounts SET balance = balance - 1000 WHERE account_id = 'A';
-- 여기서 에러 발생시 모든 변경사항 롤백
UPDATE accounts SET balance = balance + 1000 WHERE account_id = 'B';

COMMIT;

2. Consistency (일관성)

-- 일관성 제약 조건 예시
CREATE TABLE accounts (
    account_id VARCHAR(10) PRIMARY KEY,
    balance DECIMAL(10,2) CHECK (balance >= 0)
);

3. Isolation (격리성)

-- 트랜잭션 격리 수준 설정
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

4. Durability (지속성)

-- MySQL 예제
SET innodb_flush_log_at_trx_commit = 1;

트랜잭션 격리 수준

1. READ UNCOMMITTED

가장 낮은 격리 수준으로, 다른 트랜잭션의 커밋되지 않은 변경사항을 읽을 수 있습니다.

-- 트랜잭션 1
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
SELECT balance FROM accounts WHERE account_id = 'A';
-- 다른 트랜잭션의 커밋되지 않은 변경사항도 조회됨

2. READ COMMITTED

커밋된 데이터만 읽을 수 있습니다.

-- 트랜잭션 1
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT balance FROM accounts WHERE account_id = 'A';
-- 다른 트랜잭션이 커밋한 데이터만 조회됨

3. REPEATABLE READ

트랜잭션 내에서 같은 쿼리를 실행하면 항상 같은 결과가 보장됩니다.

-- 트랜잭션 1
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;

-- 첫 번째 조회
SELECT balance FROM accounts WHERE account_id = 'A';

-- 다른 트랜잭션이 데이터를 변경하고 커밋해도
-- 두 번째 조회에서 같은 결과를 보장
SELECT balance FROM accounts WHERE account_id = 'A';

COMMIT;

4. SERIALIZABLE

가장 높은 격리 수준으로, 트랜잭션을 순차적으로 실행합니다.

-- 트랜잭션 1
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
SELECT * FROM accounts WHERE balance > 1000;
-- 다른 트랜잭션의 방해 없이 순차적 실행
COMMIT;

격리 수준에 따른 문제점

1. Dirty Read (더티 리드)

커밋되지 않은 데이터를 읽는 현상

-- 트랜잭션 1
START TRANSACTION;
UPDATE accounts SET balance = balance - 1000 WHERE account_id = 'A';

-- 트랜잭션 2 (READ UNCOMMITTED에서 발생)
SELECT balance FROM accounts WHERE account_id = 'A';
-- 아직 커밋되지 않은 변경된 잔액을 읽게 됨

2. Non-Repeatable Read (반복 불가능한 읽기)

같은 쿼리를 두 번 실행했을 때 결과가 다른 현상

-- 트랜잭션 1
START TRANSACTION;
SELECT balance FROM accounts WHERE account_id = 'A';  -- 잔액: 1000

-- 트랜잭션 2
UPDATE accounts SET balance = 2000 WHERE account_id = 'A';
COMMIT;

-- 트랜잭션 1
SELECT balance FROM accounts WHERE account_id = 'A';  -- 잔액: 2000 (READ COMMITTED에서 발생)

3. Phantom Read (팬텀 리드)

범위 쿼리를 실행할 때 다른 트랜잭션의 삽입/삭제로 인해 결과가 달라지는 현상

-- 트랜잭션 1
START TRANSACTION;
SELECT * FROM accounts WHERE balance BETWEEN 1000 AND 2000;

-- 트랜잭션 2
INSERT INTO accounts VALUES ('C', 1500);
COMMIT;

-- 트랜잭션 1
SELECT * FROM accounts WHERE balance BETWEEN 1000 AND 2000;
-- 새로운 레코드가 조회됨 (REPEATABLE READ에서도 발생 가능)

실전 예제

은행 계좌 이체 시스템

-- 격리 수준 설정
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 이체 트랜잭션
START TRANSACTION;

-- 잔액 검증
SELECT balance FROM accounts WHERE account_id = 'A' FOR UPDATE;
-- FOR UPDATE는 다른 트랜잭션의 수정을 막음

-- 출금
UPDATE accounts SET balance = balance - 1000 WHERE account_id = 'A';

-- 입금
UPDATE accounts SET balance = balance + 1000 WHERE account_id = 'B';

-- 잔액 재검증
SELECT balance FROM accounts WHERE account_id = 'A';
SELECT balance FROM accounts WHERE account_id = 'B';

COMMIT;

재고 관리 시스템

-- 동시성 제어를 위한 재고 관리
START TRANSACTION;

-- 재고 확인 (락 획득)
SELECT stock FROM inventory WHERE product_id = 100 FOR UPDATE;

-- 재고 감소
UPDATE inventory 
SET stock = stock - 1 
WHERE product_id = 100 AND stock > 0;

-- 주문 생성
INSERT INTO orders (product_id, quantity) VALUES (100, 1);

COMMIT;

성능과 격리 수준

격리 수준별 성능 비교

성능: READ UNCOMMITTED > READ COMMITTED > REPEATABLE READ > SERIALIZABLE
안정성: READ UNCOMMITTED < READ COMMITTED < REPEATABLE READ < SERIALIZABLE

성능 최적화 전략

-- 인덱스를 활용한 락 범위 최소화
CREATE INDEX idx_accounts_balance ON accounts(balance);

-- 트랜잭션 실행 시간 최소화
START TRANSACTION;
SELECT balance FROM accounts WHERE account_id = 'A' FOR UPDATE;
-- 빠른 처리를 위한 최소한의 작업만 수행
COMMIT;

모범 사례

1. 트랜잭션 시간 최소화

-- BAD
START TRANSACTION;
-- 긴 시간이 걸리는 외부 API 호출
UPDATE accounts SET balance = balance - 1000;
COMMIT;

-- GOOD
-- 외부 API 호출
START TRANSACTION;
UPDATE accounts SET balance = balance - 1000;
COMMIT;

2. 적절한 격리 수준 선택

-- 읽기 작업만 있는 경우
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 중요한 금전적 처리
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 매우 중요한 데이터 정합성이 필요한 경우
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

3. 데드락 방지

-- 데드락 방지를 위한 일관된 순서로 락 획득
START TRANSACTION;
SELECT * FROM accounts WHERE account_id IN ('A', 'B') ORDER BY account_id FOR UPDATE;
-- 항상 낮은 ID부터 락 획득

데이터베이스별 기본 격리 수준

MySQL (InnoDB)

-- 기본값: REPEATABLE READ
SHOW VARIABLES LIKE 'transaction_isolation';

-- 세션 격리 수준 변경
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

PostgreSQL

-- 기본값: READ COMMITTED
SHOW transaction_isolation;

-- 트랜잭션 격리 수준 변경
BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

마치며

트랜잭션 격리 수준은 데이터베이스의 일관성과 성능 사이의 균형을 맞추는 중요한 요소입니다. 애플리케이션의 요구사항에 따라 적절한 격리 수준을 선택하고, 발생할 수 있는 문제점들을 이해하고 대비하는 것이 중요합니다.

핵심 포인트

  1. 트랜잭션의 ACID 속성 이해
  2. 각 격리 수준의 특징과 문제점 파악
  3. 성능과 데이터 일관성 사이의 균형
  4. 적절한 락 전략 수립
  5. 데드락 방지 방안 마련
반응형