데이터베이스 트랜잭션을 잘 알고싶다.

데이터베이스 트랜잭션을 잘 알고싶다.

Git을 처음 배우던 시절 Git에 대한 오해들이 좀 있었는데, 그 중 하나는 git commitgit push를 혼동하던 것이었다.

커밋만 하면 원격 저장소에 반영되었다고 생각한 적이 있었다. 이런 오해는 대형 참사를 불러왔다. 특히 노트북이 없어 학교 전산실에서 작업하고 깃에 올려놓고 집에서 또 작업하고 하던 시기였기 때문에 앗차 하고 컴퓨터를 다시 켜 봐도 데이터는 이미 롤백된 후였다.

시간이 흘러 데이터베이스 공부를 시작하는 시점이 됐다. 별 생각 없이 사용하던 INSERT INTO가 사실은 깃의 커밋에 불과한 일이란 것을 알게 됐다.

그치만 데이터가 다행히도 전부 살아있었던 것은 MySQL이 기본값으로 오토 커밋을 지원하기 때문 이라는 소름돋는 사실을 알게됐다.

트랜잭션과 커밋

트랜잭션은 데이터베이스에 변경 사항을 반영하는 행위, 혹은 행위의 집합을 일컫는다(실제 정의는 다를 수 있겠다. 하지만 나는 이렇게 이해해보려고 한다).

데이터베이스의 트랜잭션은 아래의 네 가지 속성을 만족시켜야 한다.

  • A - Atomicity(원자성)

    • 각 트랜잭션은 시작부터 끝까지 결과를 보장해야 하며 중도에 간섭이나 장애가 발생해서는 안된다.
    • e.g. 은행 거래 - 입금 트랜잭션이 발생하려면 내 계좌에서 돈을 까고 상대 계좌에 잔액을 더하는 일련의 과정이 한번에 성공해야 한다.
    • 입금 트랜잭션에 문제가 발생할 것 같으면 내 계좌에 있는 돈을 차라리 까지 말고 모든 거래 과정이 모두 실패해야만 한다.
  • C - Consistency(일관성)

    • 데이터베이스가 원래 갖고 있는 제약 조건과 트랜잭션은 일관되게 유지돼야 한다.
    • 잔액이 음수가 될 수 없다는 제약 조건이 있다면, 잔고보다 많은 송금 요청이 들어온다면 그 트랜잭션은 무효 처리 돼야 할 것이다.
  • I - Isolation(고립성)

    • 트랜잭션은 다른 트랜잭션에 종속적이거나 영향을 받으면 아니 된다.
  • D - Durability(내구성)

    • 트랜잭션은 한 번 데이터베이스에 반영되면 그 내용이 손실되어서는 안 된다.

위 조건을 충족하는 건강한 트랜잭션이 시작됐다면 해당 트랜잭션은 커밋되거나 롤백 돼야 한다.

  • COMMIT : 데이터베이스에 해당 트랜잭션을 반영함.
  • ROLLBACK: 반영하지 아니함.

고립 수준

thread-safe 문제와 비슷한 상황이 데이터베이스에서도 연출될 수 있다. 데이터베이스는 당연히 다중 접근 시나리오를 상정하고 설계해야 한다. 내가 만들어놓고 나만 쓰고 읽을 거라면 그냥 일기장에 쓰지 굳이 데이터베이스를 운영할 이유가 있을까? 데이터베이스는 많은 사용자가 한번에 쓰고 한번에 읽을 것을 계산에 넣고 만들었기 때문에 한 시점에 한 데이터를 읽거나 쓰는 동작에 대한 대처 가 당연히 설정돼있어야 한다.

트랜잭션이라는 개념이 생긴 것도 그런 까닭이다. 데이터베이스에 접근하는 시도가 모두 트랜잭션의 기준에 맞을 리도 없고, 또 데이터를 쓰는 중에 오류나 손실이 발생할 수도 있기 때문에 트랜잭션 커밋이라는 안전장치를 둬서 네가 반영한 변경 사항이 정말 의도와 맞는지를 체크하게 만들어 놨다.

문제는 트랜잭션이 커밋, 혹은 롤백 되기 이전에 같은 데이터를 읽는 동작에 대한 처리 일 것인데 이를 고립 수준 설정으로 해결하고 있다.

또 이 설정에 따라 세 가지의 현상이 발생할 수 있는데 이것을 dirty read, non-repeatable read, phantom read라고 칭한다. 이에 대한 각각의 설명은 후술하기로 하고 일단 이것들을 D, R, P로 칭하겠다.

  • READ UNCOMMITTED - 커밋되지 않은 변경 읽음

    • 커밋하지 않은 상태여도 변경사항을 읽어들일 수 있다.
    • 언뜻 보면 투명하니 좋아보이지만 만약 상대가 트랜잭션을 롤백한다면 잘못된 데이터를 읽어들인 셈이 된다.
    • D, R, P
  • READ COMMITTED - 커밋되면 읽음

    • 커밋하지 않은 트랜잭션은 가리고, 커밋한 트랜잭션만 읽어들인다.
    • 반영되지 않을 트랜잭션을 읽어들이지 않는다는 장점은 있지만, 데이터를 삭제하는 커밋이 발생할 경우, 처음 조회된 데이터를 영영 찾을 수 없을 것이다.
    • R, P
  • Repeatable Read - 데이터 반복 조회

    • 같은 트랜잭션 내에서 한번 더 조회가 발생해도 원래 있던 데이터를 읽어들인다.
    • P
  • Serializable

Dirty Read, Non-Repeatable Read, Phantom Read

격리 수준과 그에 따른 현상들을 설명해본다.

  • Dirty Read - 말 그대로다. 더러운 것을 읽는다.

Read Uncommitted 상태에서만 발생한다. 커밋되지 않은 트랜잭션을 들여다볼 수 있기 때문에 반영되지 않을 데이터가 조회되는 문제점이 생기는 것이다. 데이터를 갖다 쓰는데 실제로 반영되지 않을 데이터였다면 문제가 발생할 것이다.

이를 방지하기 위해 대부분의 DBMS에서는 Read Committed를 기본값으로 둔다.

  • Non-Repeatable Read - 돌아올 수 없는 다리

한 번 읽어들인 데이터가 다른 트랜잭션 세션에서의 DROP으로 인해 unreachable 상태로 접어드는 것을 말한다.

커밋된 트랜잭션만 읽어들인다 해도 데이터 자체가 날라가버리면 이 현상을 피할 길이 없다.

  • Phantom Read - 데이터베이스의 유령

트랜잭션이 끝나지 않은 상태에서 조회된 데이터에 대해, 다른 세션의 트랜잭션을 통해 데이터가 추가된다면, 다음 질의에서는 그 데이터가 나타나게 된다.

이 상황에서 작업을 잘 하다가 만약 다른 세션의 트랜잭션이 롤백된다면 데이터는 망한다.