본문 바로가기
DB

Transaction Isolation Level

by yzzzzun 2021. 8. 1.

Transaction

  Transaction은 DB 수행(Read, Write)을 논리적 단위 하나로 묶은 것으로, Transaction의 시작, 쿼리 수행, Commit or Rollback까지의 단위를 의미합니다.

Transaction ACID

  • Atomicity : All or Nothing, 트랜잭션 단위에 대해 성공 또는 실패를 보장해야 합니다.
  • Consistency : Transaction이 성공하면 언제나 일관된 DB 상태를 유지해야 합니다.
  • Isolation : Transaction 작업은 서로 격리되어 보호되어야 합니다.
  • Durability: Transaction이 완료된 후 데이터는 영원히 반영되어야 합니다.

Transaction Isolation Level

  만약 여러 클라이언트가 DB의 같은 데이터에 동시에 접근하는 상황을 Race Condition이라고 하는데요. 이런 Race Condition상황에서 Transaction이 서로에게 영향을 주게 되면 데이터의 정합성에 문제가 발생하게 됩니다. 그래서 Transaction은 격리(Isolation) 해야 하는데요. 가장 단순한 방법으로 Transaction을 순차적으로 처리해서 문제를 해결하는 방법이 있습니다.

  하지만 대기하는 시간이 길어지고 성능 저하를 불러올 수 있기 때문에 Database는 Isolation Level을 나누어 단계적으로 제공하고 있습니다. 격리를 한다는 건 DB에서 Lock이라는 기법을 사용해서 Transaction이 영향을 주지 못하도록 만듭니다. Isolation Level이 높을수록 Lock을 더 많이 걸어 엄격하게 관리가 들어가는 것이고 그만큼 성능이 저하될 수 있음을 의미합니다.


Lock

  영화관 예매 시스템이 있습니다. 예매할 수 있는 자리가 마지막 한자리가 남아있습니다. 이때 두 명이 그 자리를 예매하는 상황이 발생합니다. Race Condition 상황에서 각 Transaction이 모두 수행된다고 하면 누군가는 예매에 성공했지만 결과는 다른 사람이 예매 완료가 되어있는 불상사가 발생할 겁니다. 이러한 상황을 막기 위해 Transaction의 순서를 보장해야 하는데 Lock을 통해서 순차적으로 이루어지게 만들고 데이터의 일관성을 지키게 됩니다.

Shared Lock / Exclusive Lock

  Mysql InnoDB는 Shared Lock과 Exclusive Lock인 두 가지의 Row-Level-Locking을 제공합니다.

Shared Lock
  • Transaction의 읽기 수행을 위한 Lock 입니다.
  • 한 row에 대해 여러 Transaction이 Shared Lock을 획득 할 수 있습니다.
  • 하지만 Exclusive Lock을 위한 요청은 허용하지 않습니다.
Exclusive Lock
  • Transaction의 업데이트, 삭제를 위한 Lock 입니다.
  • 한 row에 대해 Exclusive Lock이 걸려있으면, 해당 Transaction이 종료될 때 까지 Shared Lock, Exclusive Lock을 허용하지 않습니다.

Intention Lock

 Intention Lock은 Row-Level의 Lock을 걸기 전 어떤 Lock을 걸지에 대해 그 의도를 알려주기위한 Table-Level의 Lock입니다.

  • Intention Shared Lock 을 테이블에 걸고 해당 row에 shared Lock을 걸어줍니다.
  • Intention Exclusive Lock 을 테이블에 걸고 해당 row에 Exclusive Lock을 걸어줍니다.
  Exclusive Lock Intention Exclusive Lock Shared Lock Intention Shared Lock
Exclusive Lock Conflict Conflict Conflict Conflict
Intention Exclusive Lock Conflict Compatible Conflict Compatible
Shared Lock Conflict Conflict Compatible Compatible
Intention Shared Lock Conflict Compatible Compatible Compatible

위의 표는 Transaction이 요청하는 Lock이 기존의 Lock과 호환이 되는지 나타내는 표입니다.

  Intention Lock은 전체 테이블을 잠금 하는 LOCK TABLES... WRITE를 제외하고 블록 하지 않습니다. Intention Lock의 주된 목적은 row가 Locking이거나 테이블 내의 row에 Lock을 걸겠다는걸 나타내기 위함입니다.

Record Lock

  Record Lock은 인덱스 레코드에 대한 Lock 입니다. 예를 들어 SELECT c1 FROM t WHERE c1=10 FOR UPDATE 쿼리는 다른 Transaction의 where c1=10 조건과 일치하는 update, insert, delete를 막습니다.

  테이블에 인덱스가 정의되어 있지 않더라도 Record Lock은 인덱스 레코드 Lock을 의미합니다.

Gap Lock

  Gap Lock은 index 레코드들 사이의 Lock 을 의미합니다. 또는 첫 번째 인덱스 이전 또는 마지막 인덱스 이후의 gap에 대한 Lock을 의미합니다. 예를 들어, SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE 는 t.c1이 15인 column의 insert를 막아버립니다. 이때 10~20까지의 범위를 gap lock으로 잠그기 때문에, 값의 유무와 상관없이 잠금이 걸리게 됩니다.

Gap Lock 은 성능과 동시성 사이의 tradeoff가 있으며, 일부 Isolation Level에서 사용합니다. Gap Lock 은 다른 Transaction이 갭에 값을 삽입하는 것만 막습니다. 다른 Transaction이 동일한 gap에 대해 gap lock을 가지는 것을 허용하고, 서로 충돌하지 않습니다.

Next-Key Lock

  Next-key Lock은인덱스-레코드 잠금과 인덱스 레코드 앞에 있는 갭의 잠금을 더한 것입니다. Gap Lock과 Record Lock 이 복합적으로 적용되는 걸 의미합니다.

  Repeatable Read 레벨에서 사용하며 이를통해 phantom row 발생을 방지합니다.

Insert Intention Lock

  Row insert에 앞서 insert 수행을 위한 gap lock의 한 종류가 Insert Intention Lock입니다. 삽입 의도를 나타내는 Lock 으로 여러 Transaction이 gap 안에서 같은 위치에 데이터를 삽입하는 게 아니라면 같은 gap에 삽입시 block하지 않습니다. 대기할 필요가 없어집니다.

  예를들어 4,7 인덱스의 레코드가 있다고 가정하고, 각 Transaction이 5, 6의 데이터 삽입을 시도합니다. 4~7은 exclusive Lock을 걸기 전 Insert Intention Lock으로 4~7을 Lock을 겁니다. 5, 6 데이터 삽입은 각 Transaction이 추가하고자 하는 위치가 다르기 때문에 서로 block하지 않습니다.

AUTO-INC Lock

  AUTO-INC Lock은 AUTO_INCREMENT 컬럼이 있는 테이블에 값을 삽입하려고 하는 Transaction에 걸리는 특별한 Table-level lock입니다. AUTO_INCREMENT가 있는 테이블에 값을 추가하면 테이블락이 걸리고 다음 추가는 해당 Transaction이 완료될 때까지 대기해야 합니다.

  innodb_autoinc_lock_mode 설정으로 auto-increment 알고리즘을 조정할 수 있으며, 동시성과 순서 사이의 트레이드오프를 선택할 수 있습니다.

 


Isolation Level

  Lock에 대해 정리했으니 이젠 각 Isolation Level에서 어떤 Lock을 통해 격리 수준을 유지하는지 정리해보겠습니다.

이전에 Isolation Level에 대해 설명할 때  Level이 낮을수록 동시성 이슈가 발생한다고 말씀드렸는데요. 어떤 이슈들이 발생하는지 간단하게 정리하고 넘어가겠습니다.

Dirty Read Transaction의 커밋되지 않은 변경사항을 읽는 현상
Non-Repeatable Read Transaction이 select 한 후 다른 Transaction에서 행을 업데이트하고 커밋하는 경우 기존 Transaction이 행을 다시 읽을 때 값이 동일하지 않은 현상
Phantom Read 다른 Transaction이 데이터를 추가, 삭제 한 경우 기존 Transaction에서 Read시 없던 유령 데이터가 생기는 현상

Read UnCommitted

  Lock을 사용하지 않는 level로 거의 사용하지 않습니다. Commit 되지 않은 데이터 변경사항을 읽을 수 있습니다.

발생 이슈 : Dirty Read, Non-Repeatable Read, Phantom Read

Read Committed

  Consistent Read를 수행합니다. 읽기를 시도할 때마다 snapshot을 설정하고 Transaction 내에서 읽기를 시도하면 snapshot을 통해 커밋되지 않은 내용들을 복구해서 read 합니다. Record Lock만 사용하고, Gap Lock을 사용하지 않기 때문에 스캔한 index의 gap에 대해 자유롭게 데이터를 수정할 수 있습니다.

발생 이슈 : Non-Repeatable Read, Phantom Read

Repeatable Read

  Mysql InnoDB의 default Isolation Level입니다. Consistent Read를 수행하는데, Read Committed와는 조금 다르게 최초 read 수행의 snapshot을 설정하고 이를 기준으로 Consistent Read를 수행합니다. 그 결과 반복된 read에 대해 동일한 값을 보장합니다.

  select... for share, select ... for update, Update, Delete 실행 시 Gap Lock 또는 Next-Key Lock을 통해 다른 transaction이 접근하지 못하도록 합니다.

Consistent Read덕분에 MySQL InnoDB 엔진은 Phantom Read 이슈가 발생하지 않습니다.

Serializable

  암시적으로 SELECT를 SELECT... FOR SHARE로 변경해서 실행합니다. 그 외에는 Repeatable Read 레벨과 동일합니다.


Wrap-up

  Transaction 간 영향을 주면 동시성 이슈가 발생하게 되는데 Lock을 통해 Isolation을 보장할 수 있습니다. 각 Isolation Level에 따라 동시성과 성능 사이에서 줄타기를 하게 되는데요. 개발자는 Isolation Level에 정확하게 이해하고 적용하여 최적의 성능을 끌어낼 수 있어야 한다는 생각이 들었습니다.

제가 주로 사용하는 DB가 MySQL이라 MySQL 레퍼런스를 참고해서 정리하게 되었습니다.
reference..
https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html
https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html
https://www.baeldung.com/spring-transactional-propagation-isolation

 

댓글