테마
MVCC와 복구
MVCC(Multi-Version Concurrency Control, 다중 버전 동시성 제어)는 InnoDB가 잠금 없이 읽는 비결이다. 행의 과거 버전을 언두 로그(undo log)에 쌓아 두고, 각 트랜잭션(transaction)이 볼 수 있는 버전만 보여 준다. 데드락(deadlock) 감지와 장애 복구도 같은 장에 묶는다.
MVCC: 버전으로 읽기
InnoDB는 행마다 사용자가 만들지 않은 숨은 필드를 붙인다. DB_TRX_ID는 이 행을 마지막으로 바꾼 트랜잭션의 번호이고, DB_ROLL_PTR는 바로 이전 버전이 적힌 언두 레코드를 가리킨다. 옛 버전들은 이 포인터로 줄줄이 이어져 언두 체인을 이룬다. 읽기 뷰(read view)는 이 정보와 언두 체인을 따라 현재 트랜잭션에 보이는 버전을 고른다.
어느 시점의 데이터를 보여 줄지는 격리 수준(isolation level)에 따라 다르다.
- REPEATABLE READ: 트랜잭션의 첫 일관된 읽기(consistent read) 때 그 시점의 데이터 모습인 스냅샷(snapshot)을 만들고 끝까지 유지한다. 그래서 같은 행을 다시 조회하면 항상 같은 값이 나온다.
- READ COMMITTED: 문장마다 새 스냅샷을 만든다. 그래서 다음 SELECT에는 그사이 다른 트랜잭션이 커밋(commit)한 변경이 보인다.
- 과거 버전은 언두 로그에 체인으로 연결된다. 긴 트랜잭션이 옛 버전을 붙잡고 있으면 퍼지(purge)가 그 버전들을 치우지 못해 공간이 계속 불어난다.
잠금 없는 일관된 읽기란 이 원리 그 자체다. 일반 SELECT는 잠금을 걸지 않고 읽기 뷰에 맞는 버전을 고르므로, 읽는 쪽과 쓰는 쪽이 서로를 막지 않는다. 단 FOR UPDATE가 붙은 SELECT나 UPDATE는 읽기 뷰가 아니라 현재 읽기(current read)로 현재 버전을 읽고 잠근다.
자동 데드락 감지
트랜잭션끼리 누가 누구의 잠금을 기다리는지 그린 것을 대기 그래프(wait-for graph)라 한다. 데드락은 이 그래프에 사이클이 생긴 상태다. InnoDB는 사이클을 감지하면 희생자(victim) 하나를 골라 롤백(rollback), 즉 그 트랜잭션의 변경을 모두 취소해 나머지를 풀어 준다. 희생자는 보통 변경량이 적은 쪽이다. 데드락으로 롤백됐다는 에러 1213을 받은 쪽은 트랜잭션을 재시도하는 게 정석이다.
대기가 폭증하는 환경에서는 감지 자체의 비용이 커질 수 있다. 이때는 innodb_deadlock_detect로 감지를 끄고, 잠금 대기 제한 시간인 innodb_lock_wait_timeout으로 버티는 선택지도 있다.
자동화된 장애 복구
MySQL 서버 프로그램인 mysqld가 비정상 종료됐다가 다시 뜨면 다음 과정을 자동으로 한다.
- 리두 로그(redo log) 재생: 로그와 데이터 페이지(page)의 LSN(Log Sequence Number, 로그 순번)을 비교해 필요한 변경을 다시 적용한다. 이 단계에는 이후 롤백될 미완료 트랜잭션의 변경도 포함될 수 있다. 쓰다 만 채로 깨진 페이지가 있으면, 리두 로그를 적용하기 전에 더블라이트 버퍼(doublewrite buffer)에 남아 있는 사본으로 먼저 바로잡는다. 리두 로그는 온전한 페이지 위에서만 다시 적용할 수 있기 때문이다.
- 언두 로그 롤백: 리두 로그 적용 후 커밋되지 않은 트랜잭션을 언두 로그로 되돌린다.
DBA(Database Administrator, 데이터베이스 관리자)가 손으로 복구하는 게 아니라 서버가 뜨는 과정에 내장돼 있다. 이것이 크래시 복구(crash recovery)다. 백업과 바이너리 로그(binary log)로 원하는 시각까지 되돌리는 시점 복구(point-in-time recovery)와는 층위가 다르다. 시점 복구는 재해 복구용이다.
면접 포인트
- "MVCC가 뭐예요?" → 버전 체인 + 스냅샷 가시성. "잠금 없이 읽는 원리"로 답한다.
- "긴 트랜잭션이 왜 위험해요?" → 언두 로그 퍼지 지연 + 공간 팽창 + 스냅샷 유지 비용.
- "데드락 나면 누가 풀어요?" → 자동 감지·희생자 롤백. 앱은 재시도.
