[Database] Transaction

Posted by RoadtoS7 on November 18, 2021 · 4 mins read

정의

  1. 작업이 완전하게 실행되는 것을 보장해준다.
  2. 트랜잭션으로 정의된 작업 세트는 완전히 실행되거나 아예 실행되지 않거나 둘중에 하나만으로 처리된다.
  3. 만약 일부분의 작업 세트만 처리한 상황에서 더이상 처리할 수 없는 상황이라면 원 상태로 복구하여 작업의 일부만 적용된 상황이 발생하지 않도록 한다.
  4. 사용자 입장: 트랜잭션이 작업의 논리적 단위로 이해될 수 있다.
  5. 컴퓨터 입장: 데이터를 접근 및 변경하는 프로그램의 단위가 된다.


트랜잭션과 Lock

(둘이 비슷한 개념 같아 보이지만)

  1. Lock: 동시성을 제어하기 위해서 사용
    • 여러 커넥션에서 동일한 자원에 대한 수정 요청이 들어올 경우, 한번에 한 커넥션에서만 자원에 접근하여 수정할 수 있도록 한다.
  2. 트랜잭션: 논리적인 작업 세트 중 일부만 적용되지 않도록 한다. 즉, 작업 세트가 완전히 적용되거나 적용되지 않는 것을 보장한다.
    • HW 에러 or SW에러로 인해, 논리적인 작업 세트가 다 실행되지 못하는 경우에 일부 작업만 실행되면 문제가 될 때, 사용된다.


트랜잭션의 특성

  • 트랜잭션이 만족해야 하는 4가지 특성 = ACID
    1. 원자성(Atomicity)
      트랜잭션의 작업은 데이터베이스에 모두 반영되든지 아니면 전혀 반영되지 않아야 한다. 만약 트랜잭션 수행 중 에러가 발생한다면, 트랜잭션의 작업 중 트랜잭션의 전부가 취소되어야 한다.
  1. 일관성(Consistency)
    트랜잭션을 실행 완료 후에도 트랜잭션 실행 전과 같이 일관성 있는 데이터베이스 상태를 유지한다. (여기서 말하는 데이터: 시스템이 가지고 있는 고정 요소)

  2. 고립성(Isolation)
    각 트랜잭션은 다른 트랜잭션의 간섭없이 독립적으로 수행되어야 한다.
    한 트랜잭션이 처리 중인 데이터를 다른 트랜잭션이 접근할 수 없다.

  3. 지속성(Durability)
    트랜잭션이 정상적으로 완료된 다음에는 데이터베이스에 트랜잭션 작업 결과가 영구적으로 저장되어야 한다.


트랜잭션의 상태

  1. Active
    트랜잭션이 수행 중인 상태

  2. Partially Committed
    트랜잭션의 마지막 연산까지 수행하였고, Commit 연산 수행 직전의 상태

  3. Committed 트랜잭션을 성공적으로 완료한 상태, Commit 연산을 실행한 이후의 상태

  4. Failed
    트랜잭션이 실행 중에 오류가 발생하여, 실행 중단된 상태

  5. Aborted
    트랜잭션이 취소된 상태, rollback을 수행하여 트랜잭션 실행 이전 데이터로 돌아간 상태

💡 Partially Committed 와 Committed 의 차이점

  • Commit 요청이 들어오면 상태는 Partial Commited 상태가 된다.
  • 이후 Commit을 문제없이 수행할 수 있으면 Committed 상태로 전이되고, 만약 오류가 발생하면 Failed 상태가 된다.
  • 즉, Partial Commited는 Commit 요청이 들어왔을때를 말하며, Commited는 Commit을 정상적으로 완료한 상태를 말한다.


트랜잭션 사용 시 주의점

  • 트랜잭션은 꼭 필요한 경우에 최소한만 사용하는 것이 좋다.
  • 일반적으로 데이터베이스 커넥션은 개수가 제한적
  • 트랜잭션이 많아질 수록, 한 트랜잭션이 실행 완료될 때까지 다른 트랜잭션이 대기할 가능성도 많아진다.
  • 그럼 프로그램이 커넥션을 소유하는 시간도 늘어난다.
  • 사용가능한 커넥션의 개수가 줄어든다.
  • 어느 순간, 이용가능한 커넥션이 없어서, 프로그램이 커넥션을 대기해야 하는 상황이 발생할 수 있다.


교착상태

  • 두개의 트랜잭션이 서로가 처리 중인 데이터를 대기하여, 영원히 대기하게 되고 작업이 진행되지 않는 상태

  • 교착상태 예시

    • MySQL은 MVCC에 따른 특성으로 인해 갱신 연산 (UPDATE, DELETE, CREATE)를 실행하면 잠금을 획득한다.
    • 기본적으로 행단위 잠금을 획득한다.
    1. 트랜잭션 1이 테이블 A의 1번째 행에 대한 잠금을 얻는다.
    2. 트랜잭션 2가 테이블의 B의 1번째 행에 대한 잠금을 얻는다.
    3. 각 트랜잭션을 commit하지 않은채, 트랜잭션 2가 테이블 A의 1번째 행에 대한 잠금 획득을 시도, 트랜잭션 1이 테이블 B의 1번째 행에 대한 잠금을 시도하면 Deadlock이 발생한다.


교착상태 빈도를 낮추는 방법

  1. 트랜잭션을 자주 커밋한다.
    커밋을 자주하면, 다른 트랜잭션에서 데이터를 대기하는 시간이 줄어든다.

  2. 정해진 순서로 테이블에 접근한다.
    앞선 예시에서 트랜잭션 1은 테이블 A -> 테이블 B 순으로 접근했고,
    트랜잭션 2는 테이블 B -> 테이블 A순으로 접근했다.
    트랜잭션들이 테이블을 동일한 순서로 접근하도록 한다.

  3. 읽기 잠금 획득 (SELECT ~ FOR UPDATE)의 사용을 피한다.
    읽기 작업은 잠금을 하지 않더라도, 동시 접근했을 때 크게 문제가 발생하지 앟는다. (정확한 데이터를 얻어야 하는 것이 아니라면)

  4. 여러 커넥션에서 한 테이블의 여러 행을 순서없이 갱신하면 교착상태가 발생하기 쉽다.
    이런 경우 테이블 단위 잠금을 하여, 갱신 작업을 직렬화하면 동시성을 떨어지지만 교착상태를 피할 수 있다.