트랜잭션 격리 수준을 공부하다가 문득 궁금증이 생겼습니다.
Oracle, PostgreSQL의 경우에는 기본 격리 수준이 Read Committed인데, 왜 MySQL은 Repeatable Read가 기본 격리 수준일까요?
과거 MySQL의 복제 작업
과거 MySQL은 Master -> Replica 복제 작업 시 Statement-Based Replication(SBR) 방식을 사용했습니다.
SBR 방식이 무엇일까요? 그림을 보면서 이해해봅시다.

SBR 방식은 쿼리문을 그대로 복제 후 실행하는 방식을 의미합니다. 동작방식에 대해 조금 더 구체적으로 알아볼까요?
1) Insert, Update 등 쿼리문이 날아오면, 해당 쿼리 실행 후 MasterDB의 Binary Log에 실행 쿼리가 기록됩니다
2) Binary Log에 담긴 쿼리문을 네트워크를 타고 Replica에게 전달합니다. (구체적으로는 Binlog Dump Thread를 생성 후, 해당 스레드가 Replica로 전송합니다)
3) Replica의 I/O 스레드가 Master로부터 받은 로그 데이터를 읽어와, Relay Log에 저장합니다
4) Replica 내부에 있는 SQL Thread가 Relay Log에 있는 쿼리문을 읽고 실행합니다. (여기서 복제 발생)
[참고 - 4) 작업에서는 쿼리를 읽고 실행하는 것이기 때문에 옵티마이저가 실행계획을 세우는 작업부터 진행합니다]
SBR 방식에서는 꼭 알아야 할 부분이 있는데요, 바로 트랜잭션 격리 수준을 Repeatable Read 이상으로 설정해줘야 합니다.
Read Committed 수준에서는 트랜잭션 도중에 다른 데이터가 끼어들 수 있습니다. 이 경우, Master에서 여러 트랜잭션이 섞여 실행된 결과와, Replica에서 쿼리들을 순서대로 실행한 결과가 달라질 수 있습니다. 구체적인 예시를 확인해 볼까요?
트랜잭션 격리 수준을 Read Committed로 설정했다고 가정해 봅시다. 그리고 커밋 순서는 B -> A 순입니다.

유저 A, B의 쿼리를 실행하면 테이블의 결괏값은 위의 사진처럼 {id : 1, status : 'Done'}, {id : 2, status : 'Ready'}가 됩니다.
복제는 어떻게 진행될까요?

commit을 한 순서대로 Binary Log에 기록이 쌓이고, 해당 로그가 Relay Log에 전달된 후, Insert -> Update 순으로 쿼리문이 실행되었습니다. 쿼리 실행 결과는 {id : 1, status : 'Done'}, {id : 1, status : 'Done}이라는 결과가 나왔네요.
그림을 보면 알 수 있듯이, Read Committed의 트랜잭션 격리 수준으로 SBR 복제를 진행하면, 복제 시 데이터 정합성 오류가 발생합니다. 그렇기에 격리 수준을 Repeatable Read 이상으로 설정해줘야 하는 것이죠.
현대 MySQL의 복제 작업 (MySQL 5.7.7 이상)
MySQL5.7.7 이상부터는 Master -> Replica 복제 작업 시 Row-Based Replication(RBR) 방식을 사용합니다.

RBR 방식이 무엇일까요? 마찬가지로 그림을 보면서 이해해 봅시다.

SBR 방식과 비슷해 보이지만 큰 차이점이 있습니다. 바로 Binary Log에 실행한 쿼리문이 저장되는 것이 아니라, 변경된 행의 실제 값이 기록됩니다. 이후에 Relay Log에서 해당 값들을 직접 스토리지 엔진에 저장/변경 요청을 하는 것이죠.
RBR 방식에서는 Read Committed 격리 수준으로 인한 복제 정합성 문제가 발생하지 않습니다. 쿼리 순서에 의존하지 않고, 변경된 값만 사용하기 때문이죠. 즉 RBR 방식에서는 Repeatable Read 이상의 격리 수준을 사용하지 않아도 됩니다.
그렇다면 RBR 방식은 무적일까요?? 그건 아닙니다. 모든 기술의 선택에는 트레이드오프 지점이 존재하죠.
UPDATE reservation SET status = 'ACTIVE' WHERE status = 'WAITING';
예를 들어, 해당 쿼리문의 결과가 1,000만 개의 데이터를 변경한다고 가정해 보죠.
이 경우 SBR 방식에서는 하나의 쿼리문을 실행하면 끝입니다. 그에 비해 RBR 방식에서는 Master -> Replica로 갈 때 1,000만 개의 데이터가 네트워크를 타고 이동해야 합니다. 또한 1,000만개의 변경 데이터를 저장해야 하기 때문에 바이너리 로그의 크기가 커집니다.
대신, 쿼리를 실행하지 않고 데이터 변경 요청을 하면 되기에 옵티마이저가 실행계획을 세울 필요도 없어 CPU 자원을 아낄 수 있고, 삽입/변경 요청만 하면 돼서 SBR 방식보다 속도가 빠릅니다.
정리
글을 읽으면 유추할 수 있듯이, MySQL이 Repeatable Read를 기본 격리 수준으로 설정한 이유는 Master - Replica의 복제 전략 문제라고 생각합니다. 과거 (MySQL 5.7.7 버전 미만)에는 SBR 방식이 기본 복제 전략이었고, SBR 방식의 복제를 사용하기 위해서는 Repeatable Read가 강제되었기 때문이죠.
현시점 (MySQL 5.7.7 이상)에서 기본 격리 수준을 변경할 수도 있지만, 호환성을 위해 그대로 유지하고 있지 않나 싶네요.
그래서, 격리 수준을 뭘로 설정해야 할까?
프로젝트 요구사항마다 다를 것 같습니다.
0.01%의 오차도 없어야 하는 정산/결제 등등의 도메인에서는 Repeatable Read 격리 수준을 선택할 것 같습니다. 결국 해당 격리 수준을 사용함으로써 Phantom Read와 Non-Repeatable Read가 발생하지 않으니까요.
하지만 속도가 중요하고, 쓰기 작업이 많으며(데드락 발생 확률이 올라가기 때문), 데이터 정합성이 완전히 보장되지 않아도 된다면,
Read Committed 격리 수준을 사용할 것 같습니다. 다른 데이터베이스들(ex. PostgreSQL, Oracle)의 격리 수준 또한 Read Committed이기도 하고요.
단, 기존 프로젝트가 꽤 많이 진행되었다면 격리 수준을 변경하는 건 정말 신중하게 결정할 것 같습니다. 개발자가 일일이 체크해야 하는 부분도 많고, 체크한다고 해도 빼먹을 수 있는 부분이 분명히 생기기 때문이죠. Read Committed는 심지어 데드락이 발생하지도 않아, 버그가 있으면 터지지도 않고 뒤에서 조용히 데이터를 바꿔버립니다ㅎ 그래서 정말 신중히 고려할 것 같네요.
신규 프로젝트 or 시작한 지 얼마 안 된 프로젝트 + 데이터 정합성에 살짝 오차가 있어도 된다면, Read Committed 격리 수준은 좋은 선택이 될 수 있겠다는 생각이 드네요. (넥스트 키 락으로 인한 대기도 없어지고, 데드락 발생 확률이 줄어드니...!)
참고자료
https://dev.mysql.com/doc/refman/8.4/en/innodb-transaction-isolation-levels.html
https://dev.mysql.com/doc/refman/8.4/en/glossary.html#glos_consistent_read
https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-7.html
https://dev.mysql.com/doc/refman/8.4/en/replication-sbr-rbr.html
https://dev.mysql.com/doc/refman/8.0/en/replication-formats.html
https://dev.mysql.com/doc/refman/8.0/en/binary-log-setting.html
'DB' 카테고리의 다른 글
| 커버링 인덱스 vs 클러스터링 인덱스 (0) | 2025.11.17 |
|---|