본문 바로가기
정리

[DB] 트랜잭션 정리

by baau 2023. 6. 17.

트랜잭션?

  • 데이터베이스의 상태를 변화시키는 하나의 논리적인 작업 단위
  • DB에서 데이터를 다룰 때 장애가 일어난 경우 데이터를 복구하는 작업의 단위
  • 트랜잭션은 전체가 수행되거나 또는 전체가 수행되지 않아야 한다. (ALL or Nothing)
  • ex) 데이터베이스에 삽입, 수정, 삭제 등의 작업을 할 때, 여러 개의 작업들을 하나의 트랜잭션으로 묶습니다.

 

트랜잭션 연산

  • Commit : 트랜잭션이 성공하여 트랜잭션 결과를 영구적으로 반영하는 연산
  • Rollback : 트랜잭션 도중 실패하여 트랜잭션 실행을 취소하여 원래의 상태로 원상 복구시키는 연산
  • 자동 커밋 : 자동 커밋으로 설정하면 각 쿼리는 실행 직후 자동으로 commit이 호출되어 결과가 반영된다.
    • 따라서 commit이나 rollback을 따로 호출하지 않아도 되는 편리함이 있지만,
    • 쿼리가 실행 직후 결과가 반영되기 때문에 트랜잭션 기능은 제대로 사용할 수 없다.
    • set autocommit true;
  • 수동 커밋 : 수동 커밋으로 설정하면 쿼리 실행 이후 꼭 commit이나 rollback을 호출해주어야 한다.
    • 따라서 수동 커밋을 설정하는 것은 트랜잭션을 시작한다고 표현한다.
    • set autocommit false;

 

트랜잭션과 락

  • 트랜잭션과 Lock은 비슷한 목적과 비슷한 기능을 수행하는 것 같지만,
  • Lock
    • 동시성을 제어하기 위한 기능
    • 여러 커넥션에서 동시에 동일한 자원을 요청할 경우 순서대로 한 시점에는 하나의 커넥션만 변경할 수 있게 해주는 역할
  • 트랜잭션
    • 데이터의 정합성을 보장하기 위한 기능
    • 꼭 여러 개의 변경 작업을 수행하는 쿼리가 조합되었을 때만 의미 있는 개념은 아니다.
    • 하나의 논리적인 작업 셋 중 하나의 쿼리가 있든 두 개 이상의 쿼리가 있든 관계없이 논리적인 작업 셋 자체가 100% 적용되거나 아무것도 적용되지 않아야 함을 보장하는 것

 

트랜잭션의 특성 (ACID)

  • Atomicity (원자성)
    • 트랜잭션이 원자처럼 더 이상 쪼개지지 않는 하나의 프로그램 단위로 동작해야 한다는 의미
    • 트랜잭션에 포함된 작업은 전부 수행되거나 전부 수행되지 않아야 한다.
  • Consistency (일관성)
    • 트랜잭션을 수행하기 전이나 후나 데이터베이스는 항상 일관된 상태를 유지해야 한다.
    • ex) 어떤 테이블의 기본키와 같은 속성은 유지되어야 한다는 것 또는 A에서 B로 돈 이체를 할 때 A와 B계좌의 돈의 총합은 같아야 한다는 것 등이 있습니다.
  • Isolation (고립성)
    • 동시에 실행되는 둘 이상의 트랜잭션이 서로에게 영향을 미치지 않도록 격리한다.
    • 수행 중인 트랜잭션이 완료될 때까지 다른 트랜잭션의 연산이 끼어들 수 없다.
    • Isolation Level 중요
  • Durability (영구성)
    • 수행을 성공적으로 완료한 트랜잭션은 변경한 데이터를 영구히 저장해야 한다.
    • 트랜잭션을 성공적으로 완료하면 그 결과는 항상 기록되어, 중간에 시스템에 문제가 발생하더라도 데이터베이스 로그 등을 사용해서 성공한 트랜잭션 내용을 복구해야 한다.

 

트랜잭션 격리 수준

1) Read Uncommitted

  • 트랜잭션에서 처리한 작업이 완료되지 않았음에도 불구하고 다른 트랜잭션에서 볼 수 있다.
  • 커밋하지 않는 내용을 다른 트랜잭션이 접근가능
  • Dirty Read 발생
    • 데이터가 나타났다가 사라졌다 하는 현상

 

2) Read Committed

  • 커밋이 완료된 트랜잭션의 변경사항만 다른 트랜잭션에서 조회 가능하다.
  • 어떤 트랜잭션에서 변경한 내용이 커밋되기 전까지는 다른 트랜잭션에서 그러한 변경 내역을 조회할 수 없다.
  • 오라클 DBMS의 디폴트 격리 수준
  • Dirty Read 해결
    • 다른 트랜잭션이 수정한 필드를 가져오는 경우 UNDO 영역에서 백업된 레코드를 가져온다.
  • Non-Repeatable Read 발생
    • 하나의 트랜잭션 내에서 동일한 SELECT 쿼리를 실행하였을 대 항상 같은 결과를 보장해야 하는 Repeatable read 정합성에 어긋난다.

 

3) Repeatable Read

  • 트랜잭션 내에서 한번 조회한 데이터를 반복해서 조회해도 결과는 동일하다.
  • Mysql의 InnoDB 스토리지 엔진에서 기본적으로 사용되는 격리 수준
  • Non-Repeatable Read 해결
    • 모든 InnoDB의 트랜잭션은 고유한 트랜잭션 번호(순차적으로 증가하는 값)를 가지며, 언두 영역에 백업된 모든 레코드에는 변경을 발생시킨 트랜잭션의 번호가 포함되어 있다.
    • 자신보다 트랜잭션 ID가 작은 트랜잭션 번호에서 변경한 것만 보게 한다.
  • Phantom Read 발생
    • 다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다가 안보였다가 하는 현상
    • SELECT .. FOR UPDATE 쿼리는 SELECT 하는 레코드에 쓰기 잠금을 걸어야 하는데, 언두 레코드에는 잠금을 걸 수 없다.
    • 따라서 위와 같은 쿼리는 언두 영역의 변경 전 데이터를 가져오는 것이 아니라 현재 레코드의 값을 가져오게 된다.
  • InnoDB에서는 독특한 특성 때문에 REPEATABLE READ 격리 수준에서도 PHANTOM READ가 발생하지 않는다.
    • InnoDB의 다중 버전 동시성 제어(MVCC, Multi-Version Concurrency Control) 메커니즘
      • MVCC는 각 트랜잭션에 대해 데이터의 이전 버전을 유지하고, 각 트랜잭션에게 일관된 스냅샷을 제공하는 방식
      • 트랜잭션의 시작 시점에서 스냅샷이 생성된다. 이 스냅샷은 해당 트랜잭션이 읽은 데이터의 일관성을 유지하기 위해 사용된다. InnoDB는 새로운 데이터를 삽입하거나 기존 데이터를 변경할 때 해당 데이터의 이전 버전을 유지하면서 새로운 버전을 생성하며, 새로운 버전은 트랜잭션이 커밋될 때까지 다른 트랜잭션에게 보이지 않는다.
      • REPEATABLE READ 격리 수준에서 InnoDB는 쿼리를 실행할 때 트랜잭션의 스냅샷을 사용하므로, 다른 트랜잭션에 의해 새로운 행이 삽입되어도 해당 트랜잭션이 그 행을 볼 수 없게 된다.
    • InnoDB 스토리지 엔진은 레코드 락과 갭 락을 합친 넥스트 키 락을 사용

 

4) Serializable

  • 읽기 작업도 공유 잠금을 획득해야 하며, 동시에 다른 트랜잭션은 그러한 레코드를 변경할 수 없다.
  • 트랜잭션에서 읽고 쓰는 레코드를 다른 트랜잭션에서는 절대 접근할 수 없다.

 

격리 수준이 높을수록 데이터 일관성은 강화되지만 동시성과 성능은 약화된다.

반대로 격리 수준이 낮을수록 동시성과 성능은 향상되지만 데이터 일관성에 대한 보장은 약화될 수 있다.

Isolation Level Dirty Read Non-Repeatable Read Phantom Read
Read Uncommitted O O O
Read Committed   O O
Repeatable Read     O
Serializable      

 

트랜잭션 상태

  • Active(활동) : 트랜잭션이 실행 중인 상태
  • Failed(장애) : 트랜잭션이 실행에 오류가 발생하여 중단한 상태
  • Aborted(철회) : 트랜잭션이 비정상적으로 종료되어 Rollback 수행하는 상태
  • Partially committed(부분 완료) : 트랜잭션이 마지막 연산까지 실행했지만, Commit 연산이 실행되기 직전인 상태
  • Committed(완료) : 트랜잭션이 성공적으로 종료되어 Commit 연산을 실행한 후의 상태
  • Partially committed와 Committed의 차이점
    • Partially committed은 Commit 요청이 들어왔을 때
    • Commited은 Commit을 정상적으로 완료하였을 때

 

트랜잭션의 주의점

  • 트랜잭션은 꼭 필요한 최소의 코드에만 적용하는 것이 좋다.
    • 트랜잭션 범위를 최소화하는 것이 중요
    • 일반적으로 데이터베이스 커넥션 개수는 제한이라, 커넥션을 소유하는 시간이 길어지면 사용 가능한 여유 커넥션의 개수도 줄어들게 된다.
    • 커넥션이 부족하면 커넥션을 기다리는 요청이 많아진다.
    • 따라서 교착상태가 발생하지 않도록 유의해야 한다.
  • 교착 상태의 빈도를 낮추는 법
    • 트랜잭션을 자주 커밋한다. → 트랜잭션 범위를 최소화
    • 읽기 잠금 획득 (SELECT ~ FOR UPDATE)의 사용을 피한다.