brunch

You can make anything
by writing

C.S.Lewis

by 최진영 Oct 11. 2022

MySQL InnoDB Isolation level

InnoDB가 가지는 특별한 격리 수준

 Database를 공부하다 보면 항상 나오는 중요한 질문이 있습니다.

Database Isolation Level에 대해서 아시나요?

 기본적으로 SQL 표준에 정의된 격리 수준을 이야기한다면 아래와 같은 표의 형식으로 이야기하게 될 것이라고 생각해요.

SQL 표준에 정의된 격리 수준

 SQL 표준이라면 맞는 말입니다. 다만 제가 사용을 하는 MySQL InnoDB 기준에서는 조금 틀린 말이에요. Phantom Read가 MySQL InnoDB에서는 발생하지 않거든요.


 그래서 왜 MySQL InnoDB에서는 발생하지 않는지, 각 Isolation Level을 어떻게 이해해야 하는지에 대해서 제가 공부했던 내용을 이야기하려 합니다.




 MySQL InnoDB의 Isolation Level에 대해서 알아보기 앞서 MySQL InnoDB에서 알아야 하는 개념들이 있습니다. InnoDB 스토리지 엔진의 Lock 방법과 Consistent Read인데요. 이 두 개를 이해해야 Isolation Level이 다른 RDBMS와 어떤 다른 점이 있는지 알 수 있기 때문에 먼저 두 개념을 이야기하고 Isolation Level을 이야기하려고 합니다.



1. InnoDB 스토리지 엔진에서의 Lock 방법

 분명 Isolation Level에 대한 글인데 Lock에 대한 이야기가 나와서 뜬금없다고 느낄 수 있어요. InnoDB에서는 일반 DBMS와는 다르게 제공하는 Lock방법을 Isolation Level 각각을 풀어나가는데 이용하기 때문에 Lock이 어떤 것이 있는지 간단하게 살펴보고 넘어가려고 합니다.

(Isolation Level 설명에 사용될 Lock들에 대한 이야기하는 점 참고 부탁드립니다.)


1.1. Record Lock

 레코드 자체만을 잠구는 Record Lock이에요. 보통은 하나의 인덱스 레코드를 잠그게 되는데, 인덱스가 하나도 없는 테이블이라더라도 내부적으로는 자동 생성된 클러스터 인덱스를 사용하여 잠금을 설정합니다.

 현재 글에서는 "레코드 자체를 잠근다"로 간단하게 이해하고 넘어가셔도 좋을 것 같아요.


1.2. Gap Lock

 일반 DBMS와 다르게 제공한다는 Lock입니다. 레코드 자체만을 잠그고 있던 Record Lock과는 다르게 인접한, 즉 특정 레코드와 레코드 사이에 생기는 그 간격을 잠그는 Lock입니다. 특정 간격에서 무언가 삽입되거나 제거되는 등의 작업을 막을 수 있습니다.


1.3. Next-Key Lock

 위에서 말한 Record Lock과 Gap Lock을 모두 합쳐놓은 형태의 Lock입니다.



2. Consistent Read


A read operation that uses snapshot information to present query results based on a point in time, regardless of changes performed by other transactions running at the same time. If queried data has been changed by another transaction, the original data is reconstructed based on the contents of the undo log. 
https://dev.mysql.com/doc/refman/8.0/en/glossary.html#glos_consistent_read


 Consistent Read는 select문을 실행할 때, 동시에 다른 트랜잭션에서 데이터를 변경하더라도 undo log에 존재하는 특정 시점의 snapshot을 사용하여 동일한 결과를 반환하기 위한 방법입니다. MySQL에서는 이런 Consistent Read를 통해서 동시성 제어를 특정 lock 없이도 할 수 있게끔 도와주는 것이죠. (물론 write에 대한 lock을 아예 쓰지 않는다는 의미는 아닙니다.)

 MVCC란 Multi-Version Concurrenty로 동시 접근을 사용하는 데이터베이스에서 동시성을 제어하기 위해 사용하는 방법입니다.




Read Uncommitted

 자 그럼 Lock에 대해서 알아보았으니 Read Uncommited에 대해서 이야기해볼까 합니다. 사실 Read Uncommited는 다른 RDBMS랑 별다른 차이가 없어요.

 MySQL에서는 Read Uncommited에 대해서 MVCC를 사용하지 않거든요. 따라서 이전 버전에 대한 Undo Log가 따로 없어서 아래와 같은 문제가 발생합니다.

Read Uncommitted에서 발생하는 Dirty Read

 commit을 하기 전에 읽는 값이 아직 commit하지 않은 값임에도 불구하고 읽어오는 모습입니다. 근데 roll back을 하면 해당 값이 사라지므로 그 이후에 읽으면 값이 없다는 결과가 나오죠.

 이러한 문제 때문에 Read Uncommitted에서는 Dirty Read의 문제가 발생하며, SQL 표준에서는 최소한 Read Committed 이상의 격리 수준을 권장합니다.



Read Committed

 여기서부터 MVCC인 Consistent Read로 해결합니다. 바로 상황을 살펴보면,

와 같은 방식으로 다른 Transaction이 조회를 하더라도 Snapshot을 남겨두어 다른 Transaction이 변경한 데이터에 의해서 영향을 받지 않습니다.

 Read Committed는 commit 된 데이터에 대해서는 정합성을 유지한다라는 판단을 하기 때문에 commit 된다면 Snapshot을 해당 commit 된 데이터로 다시 덮어쓰게됩니다.

 따라서 Read Committed는 Consistent Read를 Snapshot을 계속 만드는 방식으로 진행되며, 이로 인해 None-Repeatable Read가 발생합니다. 또한 앞서 Lock방법에서 Record Lock만을 사용하기 때문에 데이터가 중간에 삽입, 삭제가 되어서 발생하는 phantom Read를 발견할 수 있습니다.


Repeatable Read

 Reapeatable Read는 말 그대로 반복적으로 읽기 작업이 발생해도 읽는 값이 변화하지 않는 isolation level입니다. 다르게 이야기하면 Transaction이 시작하기 전에 발생한 commit 된 데이터에 대해서 정합성을 보장하는 방법입니다.


 Read Committed와 동일하게 Snapshot을 통해서 Consistent Read를 보장합니다만, Transaction이 시작하기 전 발생한 commit 된 데이터에 대해서 읽기를 수행하기 때문에 Transaction마다 별도의 Snapshot을 관리하며 다른 Transaction이 commit 하더라도 Snapshot을 변경하지는 않습니다.


 이때 SQL 표준에서는 Repeatable Read가 phantom Read가 발생한다라고 흔히 알고 있는데, MySQL InnoDB기준으로는 사실 Repeatable Read가 phantom Read가 발생하지 않습니다. 이는 Repeatable Read에서는 Record Lock만이 아닌 Next-key Lock을 사용하기 때문인데요.


  특정 레코드에 대해서만 Lock을 걸지 않고 검색하고자 하는 범위 내에서 Gap Lock까지 걸어버리기 때문에 중간에 특정 데이터가 추가 혹은 삭제되어 발생하는 phantom Read는 Repeatable Read에서는 발생하지 않습니다.



Serializable

 지금까지의 이야기를 정리하자면 아래와 같습니다.


Read Committed

- Consistent Read를 보장하지만, Snapshot이 commit 될 때마다 새로 만들어진다.

- Record Lock만 건다. 그래서 phantom Read가 발생한다.

Repeatable Read

- Consistent Read를 보장하며, Snapshot이 처음 생성된 이후로 변화하지 않는다.

- Next-Key Lock을 걸기 때문에 phantom Read가 발생하지 않는다.


 이때 lock은 write시에 발생하는 x-lock이 걸리며 읽기 시에는 consistent read를 제공하기 때문에 비 잠금 read를 쓴다는 것입니다.


 다만 Serializable은 Consistent Read를 사용하지 않고, Read에 대해서 S-Lock을 걸어서 isolation을 보장하게 된다라는 큰 차이가 있습니다.




회고

 해당 글에서는 MySQL의 InnoDB에서 보장하는 Isolation Level에 대해서 알아보았습니다.

 물론 SQL 표준상에서는 다른 부분들도 있겠지만 MySQL을 쓰는 프로젝트라면 한 번쯤 생각하면 좋은 부분이라고 생각합니다. 예를 들면 단순히 phantom Read가 SQL 표준에서 Repeatable Read에 문제가 발생한다고 MySQL에서 그를 위해 Serializable로 변경하는 문제는 없어야 하니까요.


 직접 사용하는 SQL의 스펙에서 한 번쯤 알아보고 넘어가면 좋은 기회가 죌 것 같습니다.



참고

https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html

keyword
작가의 이전글 테스트 격리 라이브러리 개발기
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari