SQL
SQL_24) 동시라는 것은 SQL에 좋지 않다.
2026. 1. 30. 15:32

Topic (오늘의 주제)

데이터베이스 동시성 이슈(Concurrency Issues)는 여러 트랜잭션이 동시에 같은 데이터에 접근할 때 발생하는 문제입니다. Lost Update, Dirty Read, Non-Repeatable Read, Phantom Read 등의 문제가 발생할 수 있으며, 이를 해결하기 위해 락(Lock) 메커니즘과 격리 수준(Isolation Level)을 사용합니다. 동시성과 데이터 일관성 사이의 트레이드오프를 이해하고 적절한 격리 수준을 선택하는 것이 중요합니다.


Why (왜 사용하는가? 왜 중요한가?)

  • 현대의 데이터베이스는 여러 사용자가 동시에 접근하는 환경입니다. 여러 트랜잭션이 동시에 같은 데이터를 읽거나 수정할 때, 적절한 제어가 없으면 데이터의 일관성이 깨지고 잘못된 결과가 발생할 수 있습니다. 예를 들어, 두 사용자가 동시에 계좌 잔액을 조회하고 출금하면, 한 사용자의 출금이 반영되지 않아 잔액이 잘못 계산될 수 있습니다.
  • 동시성 이슈를 해결하기 위해 락 메커니즘과 격리 수준을 사용하지만, 이는 성능과 일관성 사이의 트레이드오프를 수반합니다. 격리 수준이 높을수록 데이터 일관성은 보장되지만 동시성이 저하되고, 격리 수준이 낮을수록 동시성은 향상되지만 데이터 일관성 문제가 발생할 수 있습니다.
  • Lost Update, Dirty Read, Non-Repeatable Read, Phantom Read 등의 동시성 문제와 이를 해결하는 방법, 락의 종류와 동작 원리, 데드락의 발생 원인과 해결 방법을 이해해야 합니다.

1. 동시성 이슈란?

동시성 이슈의 정의

동시성 이슈(Concurrency Issues)는 여러 트랜잭션이 동시에 같은 데이터에 접근할 때 발생하는 데이터 일관성 문제입니다.

동시성 이슈가 발생하는 이유

데이터베이스는 여러 사용자가 동시에 접근하는 환경입니다:

사용자 A의 트랜잭션: 계좌 조회 → 출금
사용자 B의 트랜잭션: 계좌 조회 → 출금
                    ↓
            동시에 같은 데이터 접근
                    ↓
            데이터 일관성 문제 발생 가능

동시성 이슈의 종류

주요 동시성 이슈는 다음과 같습니다:

  1. Lost Update (갱신 손실)
  2. Dirty Read (더티 읽기)
  3. Non-Repeatable Read (반복 불가능한 읽기)
  4. Phantom Read (팬텀 읽기)

2. Lost Update (갱신 손실)

Lost Update의 정의

Lost Update(갱신 손실)는 두 트랜잭션이 동시에 같은 데이터를 수정할 때, 하나의 변경사항이 손실되는 문제입니다.

Lost Update 발생 예시

시나리오: 계좌 잔액 출금

-- 초기값: 계좌 잔액 = 10,000원

-- 트랜잭션 A: 3,000원 출금
BEGIN TRANSACTION;
SELECT 잔액 FROM 계좌 WHERE 계좌번호 = 'A001';
-- 결과: 10,000원

-- 트랜잭션 B (동시 실행): 5,000원 출금
BEGIN TRANSACTION;
SELECT 잔액 FROM 계좌 WHERE 계좌번호 = 'A001';
-- 결과: 10,000원 (트랜잭션 A가 아직 커밋하지 않음)

-- 트랜잭션 A
UPDATE 계좌 SET 잔액 = 잔액 - 3000 WHERE 계좌번호 = 'A001';
-- 계산: 10,000 - 3,000 = 7,000원
COMMIT;  -- 잔액 = 7,000원

-- 트랜잭션 B
UPDATE 계좌 SET 잔액 = 잔액 - 5000 WHERE 계좌번호 = 'A001';
-- 계산: 10,000 - 5,000 = 5,000원 (트랜잭션 A의 변경사항 무시!)
COMMIT;  -- 잔액 = 5,000원

-- 예상값: 10,000 - 3,000 - 5,000 = 2,000원
-- 실제값: 5,000원 (트랜잭션 A의 변경사항 손실!)

Lost Update의 문제점

  • 데이터 정확성 손실: 실제로는 2,000원이어야 하는데 5,000원으로 잘못 저장됨
  • 비즈니스 로직 위반: 출금 금액의 합계가 실제 차감 금액과 다름
  • 데이터 무결성 훼손: 계좌 잔액이 실제와 다름

Lost Update 해결 방법

1. 배타 락(Exclusive Lock) 사용

-- 트랜잭션 A
BEGIN TRANSACTION;
SELECT 잔액 FROM 계좌 WHERE 계좌번호 = 'A001' FOR UPDATE;
-- 배타 락 획득 (다른 트랜잭션 대기)

UPDATE 계좌 SET 잔액 = 잔액 - 3000 WHERE 계좌번호 = 'A001';
COMMIT;  -- 락 해제

-- 트랜잭션 B (트랜잭션 A 커밋 후 실행)
BEGIN TRANSACTION;
SELECT 잔액 FROM 계좌 WHERE 계좌번호 = 'A001' FOR UPDATE;
-- 결과: 7,000원 (트랜잭션 A의 변경사항 반영)

UPDATE 계좌 SET 잔액 = 잔액 - 5000 WHERE 계좌번호 = 'A001';
COMMIT;  -- 잔액 = 2,000원 (정확함!)

2. 낙관적 락(Optimistic Lock) 사용

-- 버전 컬럼 추가
ALTER TABLE 계좌 ADD COLUMN version INT DEFAULT 0;

-- 트랜잭션 A
BEGIN TRANSACTION;
SELECT 잔액, version FROM 계좌 WHERE 계좌번호 = 'A001';
-- 결과: 잔액 = 10,000, version = 0

UPDATE 계좌 
SET 잔액 = 잔액 - 3000, version = version + 1
WHERE 계좌번호 = 'A001' AND version = 0;
-- 성공: 1행 업데이트
COMMIT;

-- 트랜잭션 B
BEGIN TRANSACTION;
SELECT 잔액, version FROM 계좌 WHERE 계좌번호 = 'A001';
-- 결과: 잔액 = 7,000, version = 1

UPDATE 계좌 
SET 잔액 = 잔액 - 5000, version = version + 1
WHERE 계좌번호 = 'A001' AND version = 1;
-- 성공: 1행 업데이트
COMMIT;  -- 잔액 = 2,000원 (정확함!)

3. Dirty Read (더티 읽기)

Dirty Read의 정의

Dirty Read(더티 읽기)는 커밋되지 않은 데이터를 읽는 문제입니다. 한 트랜잭션이 데이터를 수정한 후 커밋하지 않은 상태에서, 다른 트랜잭션이 그 데이터를 읽으면 Dirty Read가 발생합니다.

Dirty Read 발생 예시

시나리오: 주문 금액 수정

-- 초기값: 주문 금액 = 36,000원

-- 트랜잭션 A: 주문 금액 수정
BEGIN TRANSACTION;
UPDATE 주문 SET 총금액 = 50,000 WHERE 주문ID = 'O100';
-- 아직 커밋하지 않음

-- 트랜잭션 B (READ UNCOMMITTED 격리 수준)
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN TRANSACTION;
SELECT 총금액 FROM 주문 WHERE 주문ID = 'O100';
-- 결과: 50,000원 (커밋되지 않은 데이터를 읽음 - Dirty Read!)

-- 트랜잭션 A: 오류 발생으로 롤백
ROLLBACK;  -- 총금액 = 36,000원으로 복구

-- 트랜잭션 B가 읽은 데이터는 잘못된 데이터!
-- 실제로는 36,000원인데 50,000원으로 잘못 인식

Dirty Read의 문제점

  • 잘못된 데이터 기반 의사결정: 커밋되지 않은 데이터를 기반으로 비즈니스 로직 실행
  • 데이터 일관성 훼손: 롤백된 데이터를 다른 트랜잭션이 사용
  • 정확성 보장 불가: 읽은 데이터가 실제로 존재하지 않을 수 있음

Dirty Read 해결 방법

READ COMMITTED 격리 수준 사용

-- 트랜잭션 A
BEGIN TRANSACTION;
UPDATE 주문 SET 총금액 = 50,000 WHERE 주문ID = 'O100';
-- 아직 커밋하지 않음

-- 트랜잭션 B (READ COMMITTED)
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN TRANSACTION;
SELECT 총금액 FROM 주문 WHERE 주문ID = 'O100';
-- 결과: 36,000원 (커밋된 데이터만 읽음 - Dirty Read 방지!)

-- 트랜잭션 A
COMMIT;  -- 총금액 = 50,000원으로 커밋

-- 트랜잭션 B (재조회)
SELECT 총금액 FROM 주문 WHERE 주문ID = 'O100';
-- 결과: 50,000원 (커밋된 데이터 읽음)

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

Non-Repeatable Read의 정의

Non-Repeatable Read(반복 불가능한 읽기)는 같은 트랜잭션 내에서 같은 쿼리를 반복 실행했을 때 다른 결과가 나오는 문제입니다.

Non-Repeatable Read 발생 예시

시나리오: 상품 가격 조회

-- 초기값: 상품 가격 = 18,000원

-- 트랜잭션 A: 가격 조회
BEGIN TRANSACTION;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT 가격 FROM 상품 WHERE 상품번호 = 'P001';
-- 결과: 18,000원

-- 트랜잭션 B: 가격 수정
BEGIN TRANSACTION;
UPDATE 상품 SET 가격 = 20,000 WHERE 상품번호 = 'P001';
COMMIT;  -- 가격 = 20,000원으로 커밋

-- 트랜잭션 A: 같은 쿼리 재실행
SELECT 가격 FROM 상품 WHERE 상품번호 = 'P001';
-- 결과: 20,000원 (다른 결과 - Non-Repeatable Read!)

-- 문제: 같은 트랜잭션 내에서 같은 데이터를 두 번 읽었는데 결과가 다름

Non-Repeatable Read의 문제점

  • 일관성 없는 데이터: 같은 트랜잭션 내에서 데이터가 달라짐
  • 비즈니스 로직 오류: 첫 번째 읽기와 두 번째 읽기의 차이로 인한 계산 오류
  • 의사결정 오류: 중간에 변경된 데이터로 인한 잘못된 판단

Non-Repeatable Read 해결 방법

REPEATABLE READ 격리 수준 사용

-- 트랜잭션 A: 가격 조회
BEGIN TRANSACTION;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT 가격 FROM 상품 WHERE 상품번호 = 'P001';
-- 결과: 18,000원 (스냅샷 생성)

-- 트랜잭션 B: 가격 수정
BEGIN TRANSACTION;
UPDATE 상품 SET 가격 = 20,000 WHERE 상품번호 = 'P001';
COMMIT;  -- 가격 = 20,000원으로 커밋

-- 트랜잭션 A: 같은 쿼리 재실행
SELECT 가격 FROM 상품 WHERE 상품번호 = 'P001';
-- 결과: 18,000원 (같은 결과 - Non-Repeatable Read 방지!)
-- 스냅샷을 사용하여 트랜잭션 시작 시점의 데이터 유지

5. Phantom Read (팬텀 읽기)

Phantom Read의 정의

Phantom Read(팬텀 읽기)는 트랜잭션 중에 새로운 행이 추가되거나 삭제되어 결과 집합이 달라지는 문제입니다.

Phantom Read 발생 예시

시나리오: 주문 개수 조회

-- 초기값: 브랜드 'B001'의 주문 개수 = 5개

-- 트랜잭션 A: 주문 개수 조회
BEGIN TRANSACTION;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT COUNT(*) FROM 주문 WHERE 브랜드코드 = 'B001';
-- 결과: 5

-- 트랜잭션 B: 새 주문 추가
BEGIN TRANSACTION;
INSERT INTO 주문 (주문ID, 브랜드코드, 총금액)
VALUES ('O200', 'B001', 36000);
COMMIT;  -- 주문 추가 완료 (실제 주문 개수 = 6개)

-- 트랜잭션 A: 같은 쿼리 재실행
SELECT COUNT(*) FROM 주문 WHERE 브랜드코드 = 'B001';
-- 결과: 5 (같은 결과 - Non-Repeatable Read는 방지됨)
-- 하지만 실제로는 6개 행이 존재 (Phantom Read!)

-- 문제: 트랜잭션 중에 새로운 행이 추가되었지만 보이지 않음

Phantom Read의 문제점

  • 결과 집합 불일치: 실제 데이터와 조회 결과가 다름
  • 통계 오류: 집계 쿼리의 결과가 부정확함
  • 비즈니스 로직 오류: 존재하지 않는 데이터를 기반으로 의사결정

Phantom Read 해결 방법

SERIALIZABLE 격리 수준 사용

-- 트랜잭션 A: 주문 개수 조회
BEGIN TRANSACTION;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SELECT COUNT(*) FROM 주문 WHERE 브랜드코드 = 'B001';
-- 결과: 5 (범위 락 설정)

-- 트랜잭션 B: 새 주문 추가 시도
BEGIN TRANSACTION;
INSERT INTO 주문 (주문ID, 브랜드코드, 총금액)
VALUES ('O200', 'B001', 36000);
-- 대기 (트랜잭션 A가 커밋할 때까지)

-- 트랜잭션 A
COMMIT;  -- 락 해제

-- 트랜잭션 B 실행 가능
COMMIT;

-- 트랜잭션 A가 다시 조회하면
SELECT COUNT(*) FROM 주문 WHERE 브랜드코드 = 'B001';
-- 결과: 6 (정확한 결과)

6. 락(Lock) 메커니즘

락의 정의

락(Lock)은 동시성 제어를 위해 데이터에 대한 접근을 제한하는 메커니즘입니다.

락의 종류

1. 공유 락 (Shared Lock, S-Lock)

공유 락은 읽기 작업에 사용되는 락으로, 여러 트랜잭션이 동시에 공유 락을 가질 수 있습니다.

-- 트랜잭션 A
BEGIN TRANSACTION;
SELECT * FROM 상품 WHERE 상품번호 = 'P001' LOCK IN SHARE MODE;
-- 공유 락 획득 (다른 트랜잭션도 읽기 가능)

-- 트랜잭션 B
BEGIN TRANSACTION;
SELECT * FROM 상품 WHERE 상품번호 = 'P001' LOCK IN SHARE MODE;
-- 공유 락 획득 가능 (읽기 가능)

-- 트랜잭션 C
BEGIN TRANSACTION;
UPDATE 상품 SET 가격 = 20000 WHERE 상품번호 = 'P001';
-- 대기 (공유 락이 있으면 배타 락 획득 불가)

2. 배타 락 (Exclusive Lock, X-Lock)

배타 락은 쓰기 작업에 사용되는 락으로, 한 트랜잭션만 배타 락을 가질 수 있습니다.

-- 트랜잭션 A
BEGIN TRANSACTION;
UPDATE 상품 SET 가격 = 20000 WHERE 상품번호 = 'P001';
-- 배타 락 획득

-- 트랜잭션 B
BEGIN TRANSACTION;
SELECT * FROM 상품 WHERE 상품번호 = 'P001';
-- 대기 (배타 락이 있으면 공유 락 획득 불가)

-- 트랜잭션 C
BEGIN TRANSACTION;
UPDATE 상품 SET 가격 = 19000 WHERE 상품번호 = 'P001';
-- 대기 (배타 락이 있으면 배타 락 획득 불가)

락 호환성

현재 락 공유 락 요청 배타 락 요청
공유 락 ✅ 허용 ❌ 대기
배타 락 ❌ 대기 ❌ 대기
락 없음 ✅ 허용 ✅ 허용

락의 범위

1. 행 락 (Row Lock)

특정 행에만 락을 설정합니다.

BEGIN TRANSACTION;
UPDATE 상품 SET 가격 = 20000 WHERE 상품번호 = 'P001';
-- 상품번호 'P001' 행에만 락 설정

2. 테이블 락 (Table Lock)

전체 테이블에 락을 설정합니다.

BEGIN TRANSACTION;
LOCK TABLE 상품 IN EXCLUSIVE MODE;
-- 전체 상품 테이블에 락 설정

3. 인덱스 락 (Index Lock)

인덱스 키에 락을 설정합니다.

BEGIN TRANSACTION;
SELECT * FROM 상품 WHERE 상품번호 = 'P001' FOR UPDATE;
-- 인덱스 키에 락 설정

7. 데드락 (Deadlock)

데드락의 정의

데드락(Deadlock)은 두 개 이상의 트랜잭션이 서로가 가진 락을 기다리며 무한 대기하는 상태입니다.

데드락 발생 예시

시나리오: 상호 대기

-- 트랜잭션 A
BEGIN TRANSACTION;
UPDATE 상품 SET 가격 = 20000 WHERE 상품번호 = 'P001';
-- 상품 P001에 배타 락 획득

-- 트랜잭션 B (동시 실행)
BEGIN TRANSACTION;
UPDATE 주문 SET 총금액 = 40000 WHERE 주문ID = 'O100';
-- 주문 O100에 배타 락 획득

-- 트랜잭션 A
UPDATE 주문 SET 총금액 = 50000 WHERE 주문ID = 'O100';
-- 주문 O100의 락을 기다림 (트랜잭션 B가 가지고 있음)

-- 트랜잭션 B
UPDATE 상품 SET 가격 = 19000 WHERE 상품번호 = 'P001';
-- 상품 P001의 락을 기다림 (트랜잭션 A가 가지고 있음)

-- 데드락 발생!
-- A: 주문 O100 락 대기 (B가 가지고 있음)
-- B: 상품 P001 락 대기 (A가 가지고 있음)
-- 둘 다 대기 상태로 무한 대기

데드락의 문제점

  • 시스템 성능 저하: 데드락에 걸린 트랜잭션이 리소스를 점유
  • 사용자 경험 저하: 트랜잭션이 완료되지 않아 응답 없음
  • 리소스 낭비: CPU와 메모리 리소스가 낭비됨

데드락 해결 방법

1. 데드락 탐지 및 복구

대부분의 DBMS는 데드락을 자동으로 탐지하고 한 트랜잭션을 롤백합니다.

-- DBMS가 데드락을 탐지하면
-- 한 트랜잭션을 롤백하고 에러 반환

-- 트랜잭션 A
ERROR: Deadlock detected
ROLLBACK;  -- 자동 롤백

-- 트랜잭션 B
-- 정상적으로 실행 계속
COMMIT;

2. 데드락 예방

락 순서 일관성 유지:

-- ✅ 좋은 예: 항상 같은 순서로 락 획득
-- 항상 상품 → 주문 순서

-- 트랜잭션 A
BEGIN TRANSACTION;
UPDATE 상품 SET 가격 = 20000 WHERE 상품번호 = 'P001';
UPDATE 주문 SET 총금액 = 50000 WHERE 주문ID = 'O100';
COMMIT;

-- 트랜잭션 B
BEGIN TRANSACTION;
UPDATE 상품 SET 가격 = 19000 WHERE 상품번호 = 'P001';
-- 대기 (트랜잭션 A가 상품 락을 가지고 있음)
-- 트랜잭션 A 커밋 후 실행
UPDATE 주문 SET 총금액 = 40000 WHERE 주문ID = 'O100';
COMMIT;

타임아웃 설정:

-- 락 대기 시간 제한
SET LOCK_TIMEOUT 5000;  -- 5초 대기 후 타임아웃

BEGIN TRANSACTION;
UPDATE 상품 SET 가격 = 20000 WHERE 상품번호 = 'P001';
-- 5초 내에 락을 획득하지 못하면 타임아웃 에러

8. 격리 수준과 동시성 이슈

격리 수준별 동시성 이슈

격리 수준 Lost Update Dirty Read Non-Repeatable Read Phantom Read 동시성
READ UNCOMMITTED ❌ 발생 가능 ❌ 발생 가능 ❌ 발생 가능 ❌ 발생 가능 ⭐⭐⭐⭐⭐
READ COMMITTED ❌ 발생 가능 ✅ 방지 ❌ 발생 가능 ❌ 발생 가능 ⭐⭐⭐⭐
REPEATABLE READ ✅ 방지 ✅ 방지 ✅ 방지 ❌ 발생 가능 ⭐⭐⭐
SERIALIZABLE ✅ 방지 ✅ 방지 ✅ 방지 ✅ 방지 ⭐⭐

격리 수준 선택 가이드

READ COMMITTED (대부분의 DBMS 기본값):

  • 일반적인 웹 애플리케이션에 적합
  • Dirty Read 방지
  • 적절한 동시성과 일관성 균형

REPEATABLE READ:

  • 같은 데이터를 여러 번 읽어야 하는 경우
  • Non-Repeatable Read 방지 필요 시

SERIALIZABLE:

  • 최고 수준의 일관성 필요 시
  • 모든 동시성 문제 방지
  • 성능 저하 감수 필요

9. 동시성 이슈 해결 전략

애플리케이션 레벨 해결

1. 낙관적 락 (Optimistic Lock)

버전 컬럼을 사용하여 동시 수정을 감지합니다.

-- 버전 컬럼 추가
ALTER TABLE 상품 ADD COLUMN version INT DEFAULT 0;

-- 트랜잭션 A
BEGIN TRANSACTION;
SELECT 가격, version FROM 상품 WHERE 상품번호 = 'P001';
-- 결과: 가격 = 18000, version = 0

UPDATE 상품 
SET 가격 = 20000, version = version + 1
WHERE 상품번호 = 'P001' AND version = 0;
-- 성공: 1행 업데이트
COMMIT;

-- 트랜잭션 B (동시 실행)
BEGIN TRANSACTION;
SELECT 가격, version FROM 상품 WHERE 상품번호 = 'P001';
-- 결과: 가격 = 18000, version = 0

UPDATE 상품 
SET 가격 = 19000, version = version + 1
WHERE 상품번호 = 'P001' AND version = 0;
-- 실패: 0행 업데이트 (version이 이미 1로 변경됨)
-- 재시도 필요

2. 비관적 락 (Pessimistic Lock)

락을 먼저 획득하여 동시 수정을 방지합니다.

-- 트랜잭션 A
BEGIN TRANSACTION;
SELECT * FROM 상품 WHERE 상품번호 = 'P001' FOR UPDATE;
-- 배타 락 획득

UPDATE 상품 SET 가격 = 20000 WHERE 상품번호 = 'P001';
COMMIT;  -- 락 해제

-- 트랜잭션 B
BEGIN TRANSACTION;
SELECT * FROM 상품 WHERE 상품번호 = 'P001' FOR UPDATE;
-- 대기 (트랜잭션 A가 커밋할 때까지)
-- 트랜잭션 A 커밋 후 실행
UPDATE 상품 SET 가격 = 19000 WHERE 상품번호 = 'P001';
COMMIT;

데이터베이스 레벨 해결

1. 적절한 격리 수준 설정

-- 애플리케이션 요구사항에 맞는 격리 수준 선택
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

2. 인덱스 최적화

인덱스를 적절히 사용하여 락 범위를 최소화합니다.

-- 인덱스가 있으면 특정 행만 락
CREATE INDEX idx_상품번호 ON 상품(상품번호);

UPDATE 상품 SET 가격 = 20000 WHERE 상품번호 = 'P001';
-- 인덱스를 사용하여 특정 행만 락 (테이블 전체 락 아님)

요약

  • 동시성 이슈는 여러 트랜잭션이 동시에 같은 데이터에 접근할 때 발생하는 데이터 일관성 문제입니다. Lost Update, Dirty Read, Non-Repeatable Read, Phantom Read 등의 문제가 발생할 수 있습니다.
  • Lost Update는 두 트랜잭션이 동시에 같은 데이터를 수정할 때 하나의 변경사항이 손실되는 문제입니다. 배타 락이나 낙관적 락을 사용하여 해결할 수 있습니다.
  • Dirty Read는 커밋되지 않은 데이터를 읽는 문제입니다. READ COMMITTED 격리 수준을 사용하여 해결할 수 있습니다.
  • Non-Repeatable Read는 같은 트랜잭션 내에서 같은 쿼리를 반복 실행했을 때 다른 결과가 나오는 문제입니다. REPEATABLE READ 격리 수준을 사용하여 해결할 수 있습니다.
  • Phantom Read는 트랜잭션 중에 새로운 행이 추가되거나 삭제되어 결과 집합이 달라지는 문제입니다. SERIALIZABLE 격리 수준을 사용하여 해결할 수 있습니다.
  • 락 메커니즘은 공유 락(읽기)과 배타 락(쓰기)을 사용하여 동시성 제어를 수행합니다. 락의 범위는 행 락, 테이블 락, 인덱스 락 등이 있습니다.
  • 데드락은 두 트랜잭션이 서로의 락을 기다리며 무한 대기하는 상태입니다. 락 순서를 일관되게 유지하거나 타임아웃을 설정하여 예방할 수 있습니다.
  • 격리 수준은 동시성과 일관성 사이의 트레이드오프를 결정합니다. 애플리케이션의 요구사항에 맞는 적절한 격리 수준을 선택하는 것이 중요합니다.

참고 자료


예상 꼬리질문 정리

1. 동시성 이슈를 완전히 해결할 수 있나?

완전한 해결의 트레이드오프:

동시성 이슈를 완전히 해결하려면 SERIALIZABLE 격리 수준을 사용해야 하지만, 이는 성능 저하를 수반합니다.

SERIALIZABLE의 문제점:

  • 모든 트랜잭션이 순차적으로 실행됨
  • 동시성 크게 저하
  • 데드락 가능성 증가
  • 성능 저하

실무 권장:

  • 대부분의 경우 READ COMMITTED로 충분
  • 필요한 경우에만 REPEATABLE READ 사용
  • SERIALIZABLE은 최후의 수단

2. Lost Update와 Dirty Read의 차이는?

Lost Update:

  • 두 트랜잭션이 동시에 수정할 때 발생
  • 하나의 변경사항이 손실됨
  • UPDATE 작업에서 발생

Dirty Read:

  • 한 트랜잭션이 수정하고 다른 트랜잭션이 읽을 때 발생
  • 커밋되지 않은 데이터를 읽음
  • SELECT 작업에서 발생

핵심 차이:

  • Lost Update: 쓰기-쓰기 충돌
  • Dirty Read: 쓰기-읽기 충돌

3. Non-Repeatable Read와 Phantom Read의 차이는?

Non-Repeatable Read:

  • 기존 행의 값이 변경되어 같은 쿼리 결과가 달라짐
  • 같은 행을 다시 읽었을 때 값이 다름
  • UPDATE 작업으로 인한 문제

Phantom Read:

  • 새로운 행이 추가되거나 기존 행이 삭제되어 결과 집합이 달라짐
  • 행의 개수나 존재 여부가 달라짐
  • INSERT/DELETE 작업으로 인한 문제

예시:

-- Non-Repeatable Read: 같은 행의 값 변경
SELECT 가격 FROM 상품 WHERE 상품번호 = 'P001';  -- 18000
-- 다른 트랜잭션이 가격을 20000으로 변경
SELECT 가격 FROM 상품 WHERE 상품번호 = 'P001';  -- 20000 (다른 값)

-- Phantom Read: 새로운 행 추가
SELECT COUNT(*) FROM 주문 WHERE 브랜드코드 = 'B001';  -- 5
-- 다른 트랜잭션이 새 주문 추가
SELECT COUNT(*) FROM 주문 WHERE 브랜드코드 = 'B001';  -- 6 (새로운 행)

4. 데드락을 어떻게 예방하나?

데드락 예방 전략:

  1. 락 순서 일관성 유지
    • 항상 같은 순서로 락 획득
    • 예: 항상 상품 → 주문 순서
  2. 락 보유 시간 최소화
    • 트랜잭션 범위를 최소화
    • 필요한 작업만 트랜잭션 내에서 수행
  3. 타임아웃 설정
    • 락 대기 시간 제한
    • 타임아웃 시 롤백하고 재시도
  4. 인덱스 최적화
    • 인덱스를 사용하여 락 범위 최소화
    • 불필요한 인덱스 제거

5. 낙관적 락과 비관적 락의 차이는?

낙관적 락 (Optimistic Lock):

  • 락을 먼저 획득하지 않음
  • 버전 컬럼으로 동시 수정 감지
  • 충돌 시 재시도
  • 읽기가 많은 경우에 유리

비관적 락 (Pessimistic Lock):

  • 락을 먼저 획득
  • 다른 트랜잭션의 접근 차단
  • 충돌 방지
  • 쓰기가 많은 경우에 유리

선택 기준:

  • 충돌이 적으면: 낙관적 락
  • 충돌이 많으면: 비관적 락