✅ 왜 트랜잭션 격리 수준이 중요한가?
트랜잭션은 데이터베이스의 핵심 개념입니다. 트랜잭션의 경리수준은 **“같은 데이터를 동시에 여러 사용자가 다룰 때, 어떤 현상을 허용할 것인가?”**에 대한 설정입니다. 트랜잭션의 격리 수준(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) 를 구조, 라우팅, 실무 이슈 중심으로 정리합니다.

'database' 카테고리의 다른 글
📘 읽기전용 DB분산구조는 왜필요한가(Read Replication) – Primary / Replica 구조 | 읽기 부하 분산 | 실시간 복제 이슈 (0) | 2025.04.07 |
---|---|
🩺 오라클 기준 DB 세션 모니터링 방법( v$session, v$process, v$sql ) | 세션정보 확인 추적 -> 문제세션 찾는 방법 -> 자동 모니터링 알람 설정 (0) | 2025.04.06 |
🔧 커넥션 풀 튜닝 전략 – 성능 병목을 잡는 핵심 포인트 (0) | 2025.04.06 |
데이터베이스_index (0) | 2025.04.06 |
[DB] HikariCP 커넥션 풀 왜쓰는거야? | 작동 흐름 | 커넥션 풀 없는 구조 vs 있는 구조 비교 | 세션모니터링 (0) | 2025.04.06 |
댓글