Spring ‐ 트랜잭션 - thought-corner/Backend-PlayGround GitHub Wiki

트랜잭션 개념

  • 트랜잭션 ACID
    • 원자성(Atomicity) : 트랜잭션 내에서 실행한 작업들은 마치 하나의 작업인 것처럼 모두 성공하거나 모두 실패해야 한다.
    • 일관성(Consistency) : 모든 트랜잭션은 일관성 있는 데이터베이스 상태를 유지해야 한다.
    • 격리성(Isolation) : 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리해야 한다.
    • 지속성(Durability) : 트랜잭션을 성공적으로 끝내면 그 결과가 항상 기록되어야 한다.
  • 트랜잭션의 격리 수준(Isolation Level)
    • READ UNCOMMITED(커밋되지 않은 읽기)
    • READ COMMITED(커밋된 읽기)
    • REPEATABLE READ(반복 가능한 읽기)
    • SERIALIZABLE(직렬화 가능)

📚Transaction

1. ACID 원칙의 심화 이해

  • 원자성 : All or Nothing. DB는 커밋(Commit)과 롤백(Rollback) 메커니즘을 사용하며, 중간에 오류가 발생하면 이전 상태로 되돌리는 작업을 보장한다.
  • 일관성 : 데이터 무결성 제약 조건을 지키는 것이다.
  • 격리성 : 성능과 데이터 안정성 사이의 트레이드오프 관계이다. 격리 수준을 높이면 데이터는 안전하지만 동시 처리 성능이 떨어진다.
  • 지속성 : 시스템 장애가 발생해도 로그 등을 통해 성공한 트랜잭션의 결과를 복구해내는 능력이다.

2. 트랜잭션 격리 수준(Isolation Level)과 발생 문제

격리 수준 (Isolation Level) Dirty Read Non-repeatable Read Phantom Read 특징 및 주요 DB 기본값
READ UNCOMMITTED 허용 허용 허용 성능은 가장 좋으나 데이터 부정합 위험이 매우 커 거의 사용 안 함
READ COMMITTED 방지 허용 허용 Oracle, PostgreSQL, SQL Server 등 대부분의 RDB 기본값
REPEATABLE READ 방지 방지 허용 MySQL (InnoDB) 기본값 (MVCC를 통해 Phantom Read 일부 방지)
SERIALIZABLE 방지 방지 방지 가장 엄격하지만 동시 처리 성능이 급격히 떨어져 실무 사용 드묾

3. 주요 부정합 문제의 정석 정의

  • Dirty Read : 트랜잭션이 커밋되지 않은 데이터를 다른 트랜잭션이 읽는 현상이다. 만약 A 트랜잭션이 롤백되면 B 트랜잭션은 존재하지 않는 데이터를 읽는 셈이 된다.
  • Non-Repeatable Read : 한 트랜잭션 내에서 똑같은 SELECT를 2번 실행했을때, 그 사이 다른 트랜잭션이 데이터를 수정/삭제해 결과가 달라지는 현상이다.
  • Phantom Read : 한 트랜잭션 내에서 똑같은 범위 조회를 2번 실행했을때, 그 사이 다른 트랜잭션이 데이터를 삽입해 유령처럼 행이 늘어나는 현상이다.

4. 실무 기준

  • 격리 수준 선택 : 대부분의 애플리케이션은 READ_COMMITTED만으로 충분하다. 금융 서비스처럼 잔액 계산이 정밀해야 하는 경우엔 REPEATABLE_READ 이상을 고려하거나 비관적 락을 명시하기도 한다.
  • MySQL 특수성 : MySQL InnoDB 엔진은 MVCC(Multi-Version Concurrency Control) 기술을 통해 REPEATABLE_READ에서 어느 정도 Phantom Read를 방지하는 강력한 기능을 제공한다.
  • 트랜잭션 범위 : 트랜잭션은 최대한 짧게 유지해야 한다. DB 커넥션 점유 시간이 늘어나면 HikariCP의 풀이 고갈되어 시스템 전체 장애로 이어질 수 있다.

데이터베이스 연결 구조와 DB 세션

  • DB 서버는 내부에 세션을 만들고 앞으로 해당 커넥션을 통한 모든 요청을 해당 세션을 통해 실행한다.
  • 개발자가 클라이언트(WAS나 DB 접근 툴)를 통해 SQL을 전달하면 현재 커넥션에 연결된 세션이 SQL을 실행한다.
  • 세션은 트랜잭션을 시작하고 커밋 또는 롤백을 통해 트랜잭션을 종료할 수 있으며, 이후에 새로운 트랜잭션을 다시 시작할 수도 있다.
  • 자동 커밋(Auto Commit)
    • 각각의 SQL 쿼리가 실행될 때마다 DB가 즉시 COMMIT을 호출하는 모드이다.
    • 쿼리가 성공하면 물리적인 DB에 반영되고 실패하면 해당 쿼리만 무효화된다.
    • 별도의 커밋 명령어를 내릴 필요가 없어 매우 편리하며, 단순 조회나 단발성 수정에 유리하다.
    • 여러 쿼리를 하나의 작업 단위로 묶을 수 없다.
    • 기본값 : 대부분의 DB 클라이언트와 라이브러리의 기본 설정이다.
  • 수동 커밋(Manual Commit)
    • 개발자가 직접 COMMIT이나 ROLLBACK을 호출하기 전까지는 데이터가 임시 상태로 유지되는 모드이다.
    • 특징 : set autocommit false를 설정하는 순간부터 트랜잭션이 시작된다.
    • 트랜잭션의 원자성을 보장할 수 있다. 여러 작업을 수행한 뒤, 모든 결과가 완벽할 때만 한꺼번에 Commit하거나 문제가 생기면 통째로 Rollback을 할 수 있다.
    • 커밋을 잊어버리면 트랜잭션이 열린 상태가 되고 결국 데이터가 반영되지 않는다. 커넥션을 오래 붙잡고 있을 경우 다른 트랜잭션이 해당 데이터에 접근하지 못하는 락 대기 현상이 길어질 수 있다.

DB 락

📚DB Lock

1. 락(Lock)의 본질과 필요성

  • 트랜잭션의 격리성과 원자성을 물리적으로 보장하기 위한 장치이다.
  • 수정 중 보호 : 세션이 데이터를 수정할 때, COMMIT 또는 ROLLBACK 전까지 다른 세션이 해당 데이터를 수정하지 못하게 차단하여 Lost Update(업데이트 분실)을 방지한다.
  • 조회 시 보호 : 일반적인 조회는 락을 사용하지 않지만, 비즈니스 로직상 조회 시점 데이터가 끝까지 유지되어야 하는 경우 락을 획득한다.
종류 SQL 예시 특징 및 용도
배타 락 (X) SELECT ... FOR UPDATE 조회 시점에 락을 걸고, 트랜잭션 종료까지 다른 곳의 수정/조회 락 획득을 차단 (가장 강력함)
공유 락 (S) SELECT ... FOR SHARE 다른 세션에서 읽기는 가능하지만, 수정은 못 하게 막음 (데이터 정합성 유지용)

2. 락의 범위(Lock Granularity)

  • Record Lock : 특정 Row 하나에만 락을 거는 방식. 가장 정밀하여 동시성이 높다.
  • Page/Block Lock : 특정 데이터 페이지 전체에 락을 건다.
  • Table Lock : 테이블 전체에 락을 건다. 데이터 구조 변경 시 발생하며, 운영에서는 절대로 해선 안 된다.
  • Database Lock : 데이터베이스 전체에 락을 건다. 업데이트나 마이그레이션시 제한적으로 사용한다.

3. 조회 시점의 락(Locking Read)

  • 기본적으로 MVCC룰 사용하는 현대 DB에서는 조회가 쓰기를 막지 않지만 필요한 경우에 따라 명시적으로 락을 걸 수 있다.
  • SELECT FOR UPDATE : 조회한 행에 대해 다른 세션이 수정하거나 락을 거는 것을 방지한다.
  • SELECT FOR SHARE : 다른 세션에서 읽기는 가능하지만 수정은 못하게 막는다.

4. 락 사용의 정석 가이드(Best Practices)

  • 트랜잭션 범위 최소화 : 락은 트랜잭션이 유지되는 동안 계속 잡히게 된다. 트랜잭션을 짧게 가져가야 락 경합을 줄이고 시스템 전체 성능을 높일 수 있다.
  • 교착 상태 방지 : 여러 테이블을 수정할 때, 항상 정해진 순서로 데이터를 수정하는 규칙을 세워야 한다.
  • 락 타임아웃 설정 : 락을 얻기 위해 무한정 대기하면 전체 서비스가 마비될 수 있으니 innodb_lock_wait_timeout과 같은 설정을 통해 적절한 시점에 에러를 내고 재시도하게 해야 한다.