본문 바로가기
database

🔐 트랜잭션 격리 수준과 세션 관계 – 성능과 일관성의 줄다리기 | READ COMMITTED, SERIALIZABLE | 트랜잭션과 세션/커넥션의 생명 주기 관계

by 죄니안죄니 2025. 4. 6.

✅ 왜 트랜잭션 격리 수준이 중요한가?

트랜잭션은 데이터베이스의 핵심 개념입니다. 트랜잭션의 경리수준은 **“같은 데이터를 동시에 여러 사용자가 다룰 때, 어떤 현상을 허용할 것인가?”**에 대한 설정입니다. 트랜잭션의 격리 수준(Isolation Level) 이 높을 수록 데이터 무결성은 올라가지만, 성능을 떨어집니다. 

  • 즉, 읽는 데이터의 정확도 크고, 락이 걸리는 범위 크고, 세션과 커넥션의 유지 시간이 클수록 성능이 떨어지게 됩니다.

특히 실무에서는 성능데이터 무결성 사이의 균형을 어떻게 맞추느냐가 핵심입니다.


🔍 격리 수준(Isolation Level) 한눈에 보기


 

격리 수준 Dirty Read
잘못된 정보를 볼수있는가
Non-repeatable Read

Phantom Read
READ UNCOMMITTED
수정중인 상황에도 접근 가능
실무에서는 거의 안 씀
O O O
READ COMMITTED
완료된 데이터만 접근 가능
(속도, 안정성 괜찮아 웹서비스 많이 사용 )
❌  O 최신정보지만 시간마다 보는내용 다를 수 있음. 즉, 내가 두 번 읽으면 다른 직원이 그 사이 바꿔서 다른값일 수 있음
단일행에 대한 값도 변경될수있음
O
REPEATABLE READ
처음과 같은 데이터만 계속 봄
(MySQL 기본값. SELECT 안정성 강화. 통계처리)
O 행 내용은 고정되지만 새 행은 못막음. 내가 조회한 "기존 행"에만 락을 거는 것. ( "가상의 행"까지 포함X ==insert가능)
팬텀리드 가능이라는 것은 두 번의 조회시 행 수가 변경될 수 있다는 것. 자연스러운 현상. 
SERIALIZABLE 
누군가 접근한 데이터는 잠시 차단함. 내가보면 다른사람은 못 봄
가장 안전하지만 동시성 낮음. ==락경합이 심함.
(결제, 금융, 회계, 인벤토리(입고,재고) 시스템 등에서 부분 사용)
❌행 넘어선 조회조건 범위락(Predicate lock 내가 볼수 있는 것까지) 또는 테이블 단위까지 포함하는 락. INSERT 자체가 block됨

💬 내가 데이터를 읽는 중인데, 누가 그걸 바꾸는 경우 난 다시 바뀐 그걸 읽을 수 있다? 그렇다면 나는 최소 READ COMMITED 상태인 것. (READ COMMITED / READ UNCOMMITTED)

💬 내가 읽는동안은 다른 사람이 테이블을 못 건드린다면 ? SERIALIZABLE 격리수준인 것.

* Serializable (자바 클래스)는 전혀 다른 개념. 객체를 파일이나 네트워크로 전송 가능하도록 직렬화하는 기능. 

Oracle의 기본 설정: READ COMMITTED

  • Dirty Read는 방지하지만, 나머지는 허용 (속도와 일관성 균형)
  • 동일 쿼리라도 실행 시점마다 다른 결과 가능
  • 인덱스가 적절하지 않으면 락 경합이나 언두 부하 발생 가능
  • 대부분의 시스템에서 사용함. 

🧩 격리 수준에 따른 세션 및 락 영향


 

격리 수준 특징 세션 락 경향 실무 영향
READ COMMITTED 대부분의 실무 시스템 기본값 공유락(Shared Lock) 짧게 유지 일반적인 읽기/쓰기 시 무난
SERIALIZABLE Phantom Read 방지, 엄격한 일관성 범위 락 (Predicate Lock) 증가 읽기만 해도 락 발생 → 동시성 저하
READ ONLY 트랜잭션 읽기 전용 보장 락 거의 없음 BI/리포팅용으로 이상적
보고서/대시보드 등 읽기만 필요한 곳에 효과적

주의: SERIALIZABLE은 SELECT 문에서도 충돌이 발생할 수 있습니다.
READ ONLY 트랜잭션만 변경의지가 없다는 선언이기 때문에 락을 회피할 수 있고, SERIALIZABLE 는 SELECT문조차 내가 지금 SELECT한 범위에 대한 다른 트랜잭션이 INSERT/UPDATE를 못하도록 막습니다.
READ COMMITTED 도 SELECT 에서는 락 최소한으로 잡히게 됨(명시적인 READ ONLY가 없기 때문에 DB는 수정할지도 모른다고 판단)

-- read only 트랜잭션 (MySQL/Oracle)
START TRANSACTION READ ONLY;
SELECT * FROM orders WHERE ...;
COMMIT;

🔦 실습 예시: 격리 수준 변경 & 세션 확인

-- 세션 격리 수준 변경
ALTER SESSION SET ISOLATION_LEVEL = SERIALIZABLE;

-- 현재 세션의 격리 수준 확인
SELECT SYS_CONTEXT('USERENV','ISOLATION_LEVEL') FROM DUAL;
-- 현재 락 상태 확인
SELECT s.sid, s.username, l.type, l.mode_held, l.mode_requested
FROM v$session s
JOIN v$lock l ON s.sid = l.sid;

🔁 트랜잭션, 세션, 커넥션의 생명 주기 관계

 Oracle 기준으로, 아래와 같은 구조로 작동합니다:

[HikariCP 커넥션 풀] → getConnection()
     ↓
[커넥션(Connection)]  ← JDBC 객체 DB 연결(설정개수만큼). DB와 연결된 통로 (JDBC 커넥션 등). 보통 커넥션 풀로 생성하기 때문에 애플리케이션 구동 초기화 할 때 히카리, 커넥션(세션)까지 생성
     ↓
     └── [DB 세션(Session)] ← DB에서 연결 수립 시 생성됨. 접속상태와 세션ID를 갖는 사용자 단위의 DB 접속 상태정보 (사용자별 작업 영역) 커넥션과 세션 1:1관계
                ↓
                └── [트랜잭션(Transaction)] ← 명시적/자동으로 BEGIN됨. DB 작업의 묶음단위 (BEGIN ~ COMMIT / ROLLBACK 사이)
                        ↓
                   작업 종료
                        ↓
                    → COMMIT or ROLLBACK
                        ↓
                    → Connection.close() 호출
                        ↓
                    → 커넥션 풀로 커넥션(세션)을 반환
  • 한 커넥션 안에서 세션이 유지되고, 세션 안에서 트랜잭션이 실행됨
  • 세션이 끊기면 트랜잭션은 무조건 롤백됩니다.
  • 커넥션이 끊기면 세션도 종료되며, 트랜잭션도 같이 끝남
  • 반대로, 커넥션이 유지된 상태라면 트랜잭션을 명시적으로 커밋하기 전까지 DB는 락을 유지합니다.

 

✅ Spring 환경 예시 (트랜잭션 어노테이션)

@Transactional(isolation = Isolation.SERIALIZABLE)
public void processOrder() {
    // 하나의 트랜잭션으로 처리됨
    orderRepository.save(...);
    stockRepository.update(...);
}

 

  • 이 메서드가 끝날 때까지 DB 세션은 커밋되지 않으며,
  • 격리 수준이 높으면 읽기조차도 락 경합이 발생할 수 있음

✅ JPA

JPA는 트랜잭션 자체는 Spring이 관리하지만, javax.persistence.LockModeType을 통해 잠금 레벨 설정 가능:

em.find(Entity.class, id, LockModeType.PESSIMISTIC_WRITE); // 강제 락

✅ MyBatis

트랜잭션 팩토리 생성 시 설정 (XML/JavaConfig 모두 가능)

<transactionManager type="JDBC"/>
<dataSource ... defaultTransactionIsolationLevel="SERIALIZABLE" />

자바 설정 시:

Environment env = new Environment("dev", new JdbcTransactionFactory(), dataSource);
 

 


🧷 정리 – 실무 적용 팁

  • 기본 격리 수준인 READ COMMITTED는 성능과 일관성의 균형점
  • SERIALIZABLE은 신중히 사용, 장시간 락을 유발할 수 있음
  • 트랜잭션이 커넥션을 점유한 채 오랫동안 유지되지 않도록 주의
    • → 비즈니스 로직은 짧게, 쿼리와 트랜잭션은 명확하게

📘 정리 예시


 

목적 추천 격리 수준 이유
일반 웹 서비스 (로그인 후 마이페이지 조회) READ COMMITTED 최신 상태면 충분, 일관성보다 속도
회계/결제 등 정합성 최우선 (결제 처리, 포인트 적립) SERIALIZABLE + 제한적 사용(SERIALIZABLE or SELECT FOR UPDATE) 데이터 충돌 방지 필수
리포트/대시보드 READ ONLY 트랜잭션
(READ ONLY 트랜잭션 + SNAPSHOT VIEW)
읽기 일관성 보장, 락 없음
재고 차감, 중복 주문 방지 REPEATABLE READ or 비관적 락 Phantom Read 방지 필요

SELECT ... FOR UPDATE : 읽는 동시에 쓸거라고 미리 락을 거는 쿼리문. 이 쿼리를 실행하면 해당 행에 쓰기 잠금이 걸려서 다른 트랜잭션이 해당행을 UPDATE하거나 DELETE할 수 없게 됨. 그래서 재고 차감이나 결제 중복 주문을 방지하는 데 상요함. 정합성있게 일고 쓰기가 동시 가능해 짐. 

1. A가 잔액 조회(10000)하면서 FOR UPDATE 를 검, 그리고 잔액에서 3000원을 빼기 요청. 커밋 하기까지 10초가 걸림

2. B가 A의 커밋 전에 A의 잔액을 조회 하면서 FOR UPDATE 를 검, 그리고 5000원을 빼기 요청 <- A의 커밋이 없어서 대기 BLOCK

3. A가 커밋을 하고 나서야 최종 잔액이 2000으로 나옴. (트랜잭션 충돌하면 B는 엉뚱한 잔액을 볼수도 있음 )

(select ... for update는 행기반 잠금이라서 팬텀 리드를 방지하지는 않습니다. 회계의 정합성이 중요한 분야에서는 serializable 격리수준을 사용하거나 명시적으로 합계용 보조 테이블, 순번 테이블, 테이블 락 등을 사용하는 전략이 필요합니다. 행 기준으로만 잔액이 관리되는 경우 for update로 되겠지만, 전체 거래 기록을 기준으로 누적 금액을 계산하는 회계시스템인 경우 반드시 팬텀 리드까지 방어하는 전략이 필요합니다.)

 


👉 다음 글에서는 읽기 전용 DB 분산 구조 (Read Replication) 를 구조, 라우팅, 실무 이슈 중심으로 정리합니다.

 

격리수준 정리표

 

댓글