현실과 마찬가지로, 컴퓨터 과학 세계도 상충관계로 가득 차 있습니다. 관계형 데이터베이스도 예외는 아닙니다. 관계형 데이터베이스와 상호작용할 때, 우리는 데이터 일관성과 트랜잭션 동시성 사이의 딜레마에 직면합니다. 전자는 데이터의 신뢰성을 보장하고, 후자는 관계형 데이터베이스가 신속하게 거래를 처리할 수 있도록 보장합니다. 두 가지 모두 관계형 데이터베이스의 바람직한 특성이지만, 동시에 최대한 달성할 수는 없습니다. 오늘은 격리 수준이 이 문제에 대한 의사결정 구조를 어떻게 도울 수 있는지 논의하겠습니다.
불합리함을 읽어보세요
격리 수준에 대해 논의하기 전에, 관계형 데이터베이스에서 흔히 발생하는 세 가지 읽기 불일치, 즉 더티 리드, 비반복 리드, 그리고 팬텀 리드를 소개하겠습니다. 이들은 동시 트랜잭션에 의해 발생하는 데이터 불일치입니다. 격리 수준을 이해하는 데 매우 중요합니다. 격리 수준에 존재하는지 부재하느냐가 데이터 일관성을 결정하기 때문입니다.
더티 리드
더티 리드는 본질적으로 커밋되지 않은 데이터를 읽는 것입니다. 트랜잭션이 다른 트랜잭션에 의해 수정되었지만 아직 커밋되지 않은 데이터를 읽을 때 발생합니다.
예를 들어보겠습니다: 두 개의 거래가 있다고 가정해 봅시다: T1과 T2. 두 거래는 모두 "BankAccount"라는 동일한 테이블과 상호작용합니다. 이 테이블에는 다음 행이 포함되어 있습니다.

T1은 은행계좌 테이블에서 위 행을 읽고, T2는 이를 수정합니다.
'''SQL -- T1의 SQL 문장: 은행 계좌에서 잔액을 선택해 account_number = 88;
'''SQL
-- T2의 SQL 문장:
업데이트 은행 계좌 잔액 설정 = 800 여기서 account_number = 88;
지금까지 전체 과정은 일련의 무해한 데이터베이스 작업처럼 들립니다. 하지만 문제는 T1이 해당 행을 읽을 때 T2가 이미 수정했지만 아직 커밋하지 않았기 때문에, T1이 더티 데이터를 읽게 된다는 점입니다. T1의 더티 데이터를 사용해 T2가 수정한 부분을 일부 실패로 인해 롤백할 때 더 심각한 문제가 발생할 수 있습니다.
반복 불가 독서
비반복 읽기는 트랜잭션 내에서 같은 행을 두 번 읽었을 때 서로 다른 결과가 나올 때 발생합니다. 그 이유는 다른 커밋된 트랜잭션이 첫 번째와 두 번째 읽기 이전에 해당 행을 수정했기 때문입니다.
예를 들어보겠습니다: 두 트랜잭션이 있다고 가정해 봅시다: T1과 T2. 두 트랜잭션 모두 "Book"이라는 동일한 테이블과 상호작용합니다. 이 테이블에는 다음 행이 포함되어 있습니다.

T1은 위 행을 책 테이블에서 두 번 읽고, T2는 이를 수정합니다.
'''SQL -- T1의 SQL 문장: 책에서 가격 선택 위치 ID = 1; 책에서 가격 선택 위치 ID = 1;
'''SQL
-- T2의 SQL 문장:
업데이트 도서 세트 가격 = 20 여기서 ID = 1;
T1의 첫 번째 읽기는 『위대한 개츠비』의 원래 가격(15)을 성공적으로 가져옵니다. 그 후 T2는 행을 업데이트하고 가격을 수정한 후 T1의 두 번째 리드가 시작됩니다. 결국 T1의 두 번째 리드는 최신 가격(20)을 얻어 T1의 같은 행에 대한 두 번의 리드 간에 불일치가 발생합니다.
팬텀 리드
비반복 읽기와 유사하게, 팬텀 리드는 동일한 트랜잭션 내에서 발생하는 읽기 불일치의 한 유형이기도 합니다. 동일한 트랜잭션 내에서 두 번의 동일한 읽기가 서로 다른 행 수를 반환할 때 발생합니다. 그 이유는 다른 커밋된 트랜잭션이 이전 트랜잭션의 첫 번째 읽기 이후와 두 번째 읽기 이전에 일부 행을 삽입하거나 삭제했기 때문입니다.
예를 들어보겠습니다: 두 트랜잭션이 있다고 가정해 봅시다: T1과 T2. 두 트랜잭션 모두 "Book"이라는 동일한 테이블과 상호작용합니다. 이 테이블에는 다음 행이 포함되어 있습니다.

T1은 Book 테이블의 모든 행을 두 번 읽고, T2는 Book 테이블에 새로운 행을 삽입합니다.
'''SQL -- T1의 SQL 문장: 책에서 선택 * 책에서 선택 *
'''SQL
-- T2의 SQL 문장:
책에 삽입 (ID, 제목, 가격) 가치 (2, '오만과 편견', 20)
T1의 첫 번째 읽기는 책 테이블에서 유일한 행(위대한 개츠비)을 가져옵니다. 그 후 T2는 T1의 두 번째 읽기 시작 전에 새로운 행(오만과 편견)을 책 테이블에 삽입합니다. 결국 T1의 두 번째 읽기 결과는 두 행(위대한 개츠비와 오만과 편견)으로 구성되며, T2가 새로 삽입한 행도 포함됩니다.
격리 단계

다음으로, SQL 트랜잭션의 네 가지 격리 수준에 대해 살펴보겠습니다. 가장 낮은 격리 수준부터 시작해 나머지는 오름차순으로 살펴보겠습니다.
읽기 미공개
Read Uncommitted는 가장 낮은 격리 수준입니다. 또한 가장 높은 수준의 트랜잭션 동시성에 해당하며, 동시 트랜잭션이 데이터베이스에 접근하고 수정하는 데 아무런 제한이 없습니다. 위에서 언급한 모든 리드 불일치(더티 리드, 반복 불가능한 리드, 팬텀 리드)는 모두 가능합니다.
커밋드 읽기
Read Committed는 두 번째로 낮은 격리 수준입니다. 트랜잭션이 커밋되지 않은 데이터를 읽지 않는 한 동시 트랜잭션을 허용합니다. 이 제한 때문에 Read Committed는 동시성을 희생하면서도 더티 리드 가능성을 제거합니다. 그럼에도 불구하고 반복 불가능한 리드와 팬텀 리드가 발생할 수 있습니다.
반복 읽기 가능
반복 읽기는 두 번째로 높은 격리 수준입니다. Read Committed에 따른 제한 외에도, Repeatable Read는 같은 트랜잭션 내에서 동일한 행에 대한 두 번의 읽기가 서로 다른 결과를 내지 않도록 금지합니다. 이 제한은 트랜잭션 내 각 문에서 읽는 모든 데이터에 공유 락을 걸어 달성합니다. 데이터베이스는 해당 트랜잭션이 완료될 때까지 공유 락을 해제하지 않습니다. 그 결과, 다른 트랜잭션들은 잠긴 데이터에 변경을 할 수 없습니다. 이 추가 제한은 트랜잭션 동시성 수준을 더욱 낮춥니다. 이 제한 덕분에 Repeatable Read는 반복 불가능한 읽기를 불가능하게 만듭니다. 하지만 Repeatable Read는 여전히 팬텀 리드 위험에 직면해 있습니다.
연속 가능
직렬화 가능한 것은 가장 높은 격리 수준입니다. 이름에서 알 수 있듯이, 트랜잭션이 생성되는 결과가 직렬로 실행될 때와 동일할 때만 동시 트랜잭션이 발생할 수 있도록 허용합니다. 앞서 두 격리 레벨이 가한 제한 외에도, 동일한 트랜잭션 내에서 동일한 두 번의 읽기가 서로 다른 행 수를 반환하는 것을 금지합니다. 이 제한은 트랜잭션 내 모든 문장의 검색 조건에 일치하는 범위 락을 데이터 범위에 설정함으로써 달성합니다. 데이터베이스는 해당 트랜잭션이 완료되기 전에 모든 범위 락을 보유합니다. 이 제한은 트랜잭션 동시성을 가장 낮은 수준으로 떨어뜨리지만, Serializable이 최고 수준의 데이터 일관성을 달성하는 데 도움을 줍니다. 세 가지 읽기 불일치(더티 리드, 비반복 리드, 팬텀 리드)는 모두 불가능합니다.
어떤 격리 층을 선택해야 할지에 대한 팁
이제 네 가지 격리 수준을 다뤘으니, 여러분 중 일부는 "그럼 관계형 데이터베이스에서 어떤 격리 수준을 선택해야 할까요?"라고 물을 수도 있습니다. 이 질문은 본질적으로 데이터 일관성을 위해 얼마나 많은 동시성을 포기할 의향이 있느냐로 귀결됩니다.
읽기 미공개
경험상 Read Uncommitted는 사용하지 않는 것이 좋습니다. 더티 리드는 가장 바람직하지 않은 데이터 불일치 유형이기 때문입니다. 유일한 예외는 성능 면이 데이터 일관성보다 훨씬 중요할 때입니다.
커밋드 읽기
Read Committed는 대부분의 데이터베이스에서 기본 격리 수준입니다. 동시성과 일관성 사이의 균형을 잘 제공합니다. 어떤 격리를 선택해야 할지 확신이 없다면, Read Committed를 사용해 보는 것을 안전하게 시작할 수 있습니다.
반복 읽기 가능
반복 읽기는 동시성을 희생하면서도 뛰어난 데이터 일관성을 제공합니다. 이 격리 수준을 괴롭히는 유일한 읽기 불일치는 팬텀 리드입니다. 시스템 성능보다 데이터 일관성을 중요시할 때는 반복 읽기를 선택해야 합니다.
연속 가능
직렬화 가능한 기능은 가장 높은 수준의 데이터 일관성을 자랑합니다. 그 결과, 트랜잭션 동시성이 가장 낮아 성능이 최악입니다. 직렬화 가능한 옵션은 데이터 불일치에 대해 무관용 정책을 가지고 있을 때 적합한 선택이지만, 그로 인해 시스템 성능이 저하될 수 있다는 점을 기억해 주세요.
이 블로그에서는 세 가지 유형의 읽기 불일치(더티 리드, 비반복 읽기, 팬텀 리드)와 네 가지 격리 수준(Read Uncommitted, Read Committed, Repeatable Read, Serializable)을 다루었습니다. 각 격리 수준은 일부 읽기 불일치를 허용하고 다른 불일치는 금지합니다. 이 격리 수준 중 선택하는 것은 본질적으로 동시성과 일관성 사이의 절충입니다. 이 글이 여러분께 도움이 되길 바랍니다.