개발/DB

트랜잭션(Transaction)이란??

개발자 박현준 2023. 10. 30. 18:01
728x90

트랜잭션

트랜잭션은 데이터베이스에서 수행되는 여러 작업을 하나의 논리적 단위로 수행하는 것.
# 예시)사용자 A가 사용자 B한테 100,000을 입금한다.

# 1. 사용자 A의 계좌에서 100,000원을 차감한다.
UPDATE accounts SET money = money - 100000 WHERE user = "A";

# 2. 사용자 B의 계좌에 100,000원을 추가한다.
UPDATE accounts SET money = money + 100000 WHERE user = "B";

DB에서는 트랜잭션이라는 선언을 통해 위 예시 과정을 한 가지 작업으로 묶을 수 있다.

이때 만약 오류 없이 모든 작업이 성공적으로 마치게 되면 DB에 반영하게 되는데,

이것을 트랜잭션 연산에서는 COMMIT이라고 한다.

 

하지만 위 예시 과정 중간에 B의 과정에서 오류가 나게 된다면 어떻게 될까?

 

B에서 오류가 발생하게 되면 입금을 하지 않은 상황이 되므로 B의 계좌에는 0원이 되지만

A에서는 이미 100,000원을 출금한 상태이므로 0원이 된다.

이렇게 되면 위 과정이 제대로 동작하지 않았기 때문에 다시 A는 100,000원을 B는 0원을 가진 상태로 되돌아가야 한다.

이것을 트랜잭션 연산에서는 ROLLBACK이라고 한다.

 

그래서 트랜잭션의 연산에는 크게 COMMIT, ROLLBACK 두 가지로 나눠져 있다.

 

트랜잭션 특징

  • 트랜잭션은 작업의 완전성을 보장해 준다.
  • 즉, 작업들을 모두 처리하거나 처리하지 못할 경우 이전 상태로 복구하여 작업의 일부만 적용되는 현상이 발생하지 않게 만들어주는 기능이다.
  • 하나의 트랜잭션은 Commit(작업 완료)되거나 Rollback(취소)된다.

 

트랜잭션의 특성(ACID)

ACID란?

 

트랜잭션이 안전하게 수행된다는 것을 보장하기 위한 성질이다.

 

  1. 원자성(Atomicity)
  2. 일관성(Consistency)
  3. 독립성(Isolation)
  4. 영속성(Durability)

각각 네 가지 특성에서 첫 번째 글자를 따와서 ACID라고 한다.

 

1.  원자성(Atomicity)

  • 원자성은 특정 트랜잭션의 작업이 모두 반영되던지 아니면 전혀 반영되지 않아야 한다.
  • 유명한 영어 문장으로는 All Or Nothing라는 게 있다.

 

2.  일관성(Consistency)

  • 일관성은 실행이 완료되면 언제나 일관성 있는 상태로 유지해야 한다.
  • 즉 데이터베이스 데이터 무결성 제약조건에 맞춰야 한다는 것

 

3.  독립성(Isolation)

  • 둘 이상 트랜잭션이 동시에 실행될 경우 서로의 연산에 끼어들 수 없다.
  • 즉 어떤 트랜잭션에서 연산이 수행될 때 다른 트랜잭션에 영향을 주거나 영향을 받아선 안된다.

 

4.  영속성(Durability)

  • 트랜잭션 연산이 성공적으로 수행되면 완료된 결과는 영구적으로 데이터베이스에 반영되어야 한다.

 

트랜잭션의 격리 수준(Transaction Isolation Level)

트랜잭션의 특징 ACID 중 Isolation에 따라, 트랜잭션 간에 독립성을 완전히 보장해야 합니다.

하지만 그럴 경우를 완전히 보장하기 위해서는 모든 트랜잭션을 순차적으로 처리해야 하고, 이는 현실적으로 성능상 단점이 된다.

트랜잭션 격리 수준은, 동시에 여러 트랜잭션이 처리될 때 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있도록 허용할지 말지를 결정하는 것입니다.

 

READ-UNCOMMITED

READ-COMMITTED

REPEATABLE-READ

SERIALIZABLE

 

위로 올라갈수록 속도는 빨라지지만 데이터의 일관성을 보장하지 못하고,

밑으로 갈수록 SERIALIZABLE은 트랜잭션을 순차적으로 처리하므로 속도는 느리지만 데이터의 일관성을 보장할 수 있다.

 

1.  READ-UNCOMMITED

  • 아직 커밋되지 않은 데이터를 읽을 수 있다.
    • ex) 사용자 A의 트랜잭션에서 INSERT를 통해 데이터를 추가했다고 가정해 보면. 아직 커밋 또는 롤백이 되지 않은 상태임에도 불구하고 READ UNCOMMITTED은 변경된 데이터에 접근할 수 있다. 이를 그림으로 표현하면 다음과 같다.

  • 정합성에 문제가 많은 격리 수준이기 때문에 사용하지 않는 것을 권장한다.
  • DIRTY READ 문제가 발생한다.
    • DIRTY READ : 트랜잭션이 작업이 완료되지 않았는데도 다른 트랜잭션에서 볼 수 있게 되는 현상
    • ex) 사용자 A가 커밋이 아닌 롤백을 수행한다면 사용자 B의 트랜잭션은 id = 51인 데이터를 처리하고 있을 텐데 다시 데이터를 조회해 보니 존재하지 않는 상황이 발생. 그림으로 표현하면 다음과 같다.

 

 

 

2.  READ-COMMITED

  • 커밋된 데이터만 읽을 수 있다.
  • RDB에서 대부분 기본적으로 사용되고 있는 격리 수준이다.
  • Dirty Read의 문제는 발생하지 않지만 NON-REPEATABLE READ 문제가 발생한다.
    • NON-REPEATABLE READ : 하나의 트랜잭션 내에서 동일한 SELECT 쿼리를 실행했을 때 항상 같은 결과를 보장해야 한다는 REPEATABLE READ 정합성에 어긋나는 것이다.
    • ex) 사용자 A의 트랜잭션에서 UPDATE를 통해 데이터를 수정했다고 가정해 보면. 수정된 내용이 사용자 B의 트랜잭션에서는 바로 보이지는 않는다. 만약 사용자 A가 수정된 데이터를 커밋을 했는데, 사용자 B의 트랜잭션에서는 아직 완료되지 않은 경우, 사용자 B에서 다시 한번 조회를 하게 된다면 사용자 A가 UPDATE한 데이터가 조회가 되므로 같은 조회(select)문인데도 결과가 다르기 때문에 정합성에 어긋난다.
  • 실제 테이블 값을 가져오는 것이 아니라 Undo 영역에 백업된 레코드에서 값을 가져온다.
    • Undo 영역 : 간단하게 데이터를 저장하는 버퍼 기능을 한다고 보면 되고, 트랜잭션에서 연산(커밋 롤백)이 이루어지지 않아도 질의문에 의해 수정이 생기면 수정되기 이전 값이 저장되는 영역이다.
    • ex) 만약 사용자 A가 id = 50인 데이터를 MangKyu -> MinKyu로 수정하고 커밋을 하지 않았을 때 Undo 영역에는 사용자 A가 id = 50인 데이터를 수정하기 전인 MangKyu를 저장해 둔다. 만약 사용자 B가 조회를 하면 Undo 영역에 있는 데이터를 찾아 반환하게 된다.

 

3.  REPEATABLE-READ

  • 특정 데이터를 반복 조회 시 같은 값을 반환한다.
  • MySQL과 MariaDB가 기본으로 사용하는 격리 수준이다.
  • MySQL에서는 트랜잭션마다 트랜잭션 ID를 부여하여 트랜잭션 ID보다 작은 트랜잭션 번호에서 변경한 것만 읽게 된다.
  • REPEATABLE READ는 새로운 레코드의 추가까지는 막지 않는다. 따라서 SELECT로 조회한 경우 트랜잭션이 끝나기 전에 다른 트랜잭션에 의해 추가된 레코드가 발견될 수 있는데, 이를 유령 읽기(Phantom Read)라고 한다. 하지만 MVCC 덕분에 일반적인 조회에서 유령 읽기(Phantom Read)는 발생하지 않는다. 왜냐하면 자신보다 나중에 실행된 트랜잭션이 추가한 레코드는 무시하면 되기 때문이다.
    • Phantom-Read : NON-REPEATABLE READ의 한 종류이고. 새로운 데이터가 생기거나, 기존의 데이터가 사라지는 문제를 뜻한다.
    • MySQL에는 갭 락이 존재하기 때문에 일반적으로 MySQL의 REAPEATABLE READ에서는 Phantom Read가 발생하지 않는다.
      • ex) 사용자 B가 SELECT FOR UPDATE로 데이터를 조회한 경우에 MySQL은 id가 50인 레코드에는 레코드 락, id가 50보다 큰 범위에는 갭 락으로 넥스트 키 락을 건다. 따라서 사용자 A가 id가 51인 member를 INSERT 시도한다면, B의 트랜잭션이 종료(커밋 또는 롤백)될 때까지 기다리다가, 대기를 지나치게 오래 하면 락 타임아웃이 발생하게 된다.

 

4.  SERIALIZABLE

  • 트랜잭션이 순차적으로 실행된다.
  • 가장 단순한 격리 수준이지만 가장 엄격한 격리 수준이다.
  • 데이터를 접근할 때, 항상 Lock을 걸고 데이터를 조회한다.
  • SERIALIZABLE에서는 PHANTOM READ가 발생하지 않는다.
  • 여러 트랜잭션이 동일한 레코드에 동시 접근할 수 없다.
  • 트랜잭션이 순차적으로 처리되어야 하므로 동시 처리 성능이 매우 떨어지기 때문에 극단적으로 안전한 작업이 필요한 경우가 아니라면 사용해서는 안된다.

 

트랜잭션의 격리 수준(Transaction Isolation Level) 요약

READ UNCOMMITTED는 부정합 문제가 지나치게 발생하고, SERIALIZABLE은 동시성이 상당히 떨어지므로 READ COMMITTED 또는 REPEATABLE READ를 사용하면 된다. 참고로 오라클에서는 READ COMMITTED를 기본으로 사용하며, MySQL에서는 REPEATABLE READ를 기본으로 사용한다. 

  Dirty Read Non-Repeatable Read Phantom Read
READ UNCOMMITED 발생 발생 발생
READ COMMITTED 없음 발생 발생
REPEATABLE READ 없음 없음 발생(MySQL은 거의 없음)
SERIALIZABLE 없음 없음 없음

격리 수준이 높아질수록 MySQL 서버의 처리 성능이 많이 떨어질 것으로 생각하는데, 사실 SERIALIZABLE이 아니라면 크게 성능 개선 및 저하는 발생하지 않는다. 그 이유는 결국 언두 로그를 통해 레코드를 참조하는 과정이 거의 동일하기 때문이다. 따라서 MySQL은 갭 락을 통해 Phantom Read까지 거의 발생하지 않고, READ COMMITTED보다는 동시 처리 성능은 뛰어난 REPEATABLE READ를 사용한다.

 

 

참고

https://www.youtube.com/watch?v=taUeIi6a6hk&t=24s

 

https://mangkyu.tistory.com/299

 

https://velog.io/@yujiniii/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EA%B2%A9%EB%A6%AC-%EC%88%98%EC%A4%80

728x90