목차
- InnoDB 스토리지 엔진의 특징
- 프라이머리 키(PK)에 의한 클러스터링
- MVCC (Multi Version Concurrency Control)
- 잠금없는 일관된 읽기
- 자동으로 데드락 감지
- 자동화된 장애 복구
InnoDB는 MySQL에서 사용할 수 있는 스토리지 엔진 중 거의 유일하게 레코드 기반의 잠금을 제공하고, 그 때문에 동시성 처리가 가능하며 안정적이고 성능이 뛰어납니다. InnoDB의 구조는 다음과 같습니다. 우선 InnoDB의 구조를 기반으로 InnoDB의 특징부터 살펴보도록 하겠습니다.
InnoDB의 모든 테이블은 기본적으로 PK를 기준으로 클러스터링되어 저장됩니다. 즉, PK 값 순서대로 디스크에 저장된다는 뜻이며, 모든 세컨더리 인덱스는 레코드의 주소 대신 PK의 값을 논리적인 주소로 사용합니다.
PK가 클러스터링 인덱스이기 때문에, PK를 이용한 레인지 스캔은 상당히 빠르고, 쿼리 실행계획에서도 PK는 다른 보조 인덱스에 비해 비중이 높게 설정되어 있습니다. 왜? 디스크에 순서대로 저장할 때 사용된 클러스터링 인덱스를 사용하는 것이 세컨더리 인덱스에 비해 훨씬 효율적이기 때문입니다.
일반적으로 레코드 레벨의 트랜잭션을 지원하는 DBMS가 제공하는 기능이며, MVCC
는 여러 버전의 데이터를 동시에 유지하면서 다른 트랜잭션의 작업이 해당 데이터에 영향을 미치지 않도록 제어하는 방식을 의미합니다.
또한, MVCC
의 가장 큰 목적은 잠금을 사용하지 않는 일관된 읽기를 제공하는 것입니다.
잠금을 사용하지 않는 일관된 읽기?
일반적인 잠금 방식
의 경우, 트랜잭션이 데이터를 읽거나 쓸 때 해당 데이터에 대한 잠금을 걸어 다른 트랜잭션이 해당 데이터를 수정하는 것을 막습니다. 그러나 이 방식은 다른 트랜잭션의 작업에 대해 잠금을 요청하기 때문에 일부 트랜잭션은 대기할 수 있으며, 이로 인해 성능 저하가 발생할 수 있습니다.
반면MVCC 방식
은 일반적으로 새로운 버전의 데이터를 생성하여 기존 데이터와 함께 유지합니다. 이렇게 생성된 새로운 버전의 데이터는 다른 트랜잭션이 기존 버전의 데이터를 읽거나 쓸 수 있도록 제공하면서도, 동시에 현재 작업 중인 트랜잭션이 보는 데이터는 일관성 있게 보여집니다.
MVCC
에서는 각 트랜잭션의 격리 레벨과 시작 시간, 커밋 시간 등을 비교하여 해당 트랜잭션이 보는 데이터 버전을 결정합니다.
InnoDB는 언두 로그(Undo log)를 활용해 이 기능을 구현합니다. 멀티버전
이라 함은, 하나의 레코드에 대해 여러 개의 버전이 동시에 관리된다는 의미입니다. 아래 그림을 통해 이해해보겠습니다.
왼쪽 그림은 INSERT 문을 통해 홍길동 member에 대한 데이터가 삽입된 상태를 의미합니다. 그리고 오른쪽 그림은 UPDATE 문을 통해, 홍길동 유저의 데이터를 수정한 상태입니다.
UPDATE member SET m_area='경기' WHERE m_id=12;
그런데 UPDATE 문장이 실행되면 커밋 실행 여부와 상관없이 InnoDB의 버퍼 풀
은 새로운 값인 '경기'로 업데이트됩니다.
아직 COMMIT 이나 ROLLBACK이 되지 않은 상태에서 다른 사용자가 레코드를 조회하면 어떻게 될까요? 답은, 격리 수준(Isolation level)
에 따라 다릅니다.
READ_UNCOMMITED
: InnoDB 버퍼 풀이 현재 가지고 있는 변경된 데이터를 읽어서 반환READ_COMMITED
: 변경되기 이전의 내용을 보관하고 있는 언두 영역의 데이터를 반환REPEATABLE_READ
: (상동)SERIALIZABLE
: (상동)즉, 하나의 레코드에 대해 2개의 버전이 유지되고, 필요에 따라 어느 데이터가 보여지는지 여러 가지 상황에 따라 달라지는 구조입니다.
지금까지 쿼리가 실행되고, COMMIT이나 ROLLBACK이 실행되기 이전의 상황을 가정해보았습니다. 만약 COMMIT
명령이 실행된다면 InnoDB는 버퍼 풀의 상태를 영구적인 데이터로 만들어버립니다. 단, ROLLBACK
을 실행하면 언두 영역에 있는 백업된 데이터를 InnoDB 버퍼풀로 다시 복구하고, 언두 영역의 내용을 삭제합니다.
일반적인 잠금 방식
의 경우, 트랜잭션이 데이터를 읽거나 쓸 때 해당 데이터에 대한 잠금을 걸어 다른 트랜잭션이 해당 데이터를 수정하는 것을 막습니다. 그러나 이 방식은 다른 트랜잭션의 작업에 대해 잠금을 요청하기 때문에 일부 트랜잭션은 대기할 수 있으며, 이로 인해 성능 저하가 발생할 수 있습니다.
반면 InnoDB의 MVCC 방식
은 일반적으로 새로운 버전의 데이터를 생성하여 기존 데이터와 함께 유지합니다. 이렇게 생성된 새로운 버전의 데이터는 다른 트랜잭션이 기존 버전의 데이터를 읽거나 쓸 수 있도록 제공하면서도, 동시에 현재 작업 중인 트랜잭션이 보는 데이터는 일관성 있게 보여집니다.
MVCC
에서는 각 트랜잭션의 격리수준과 시작 시간, 커밋 시간 등을 비교하여 해당 트랜잭션이 보는 데이터 버전을 결정합니다. 격리수준
에 따라서 어떻게 MVCC가 동작하는지 이해해보겠습니다.
만약 격리수준이 SERIALIZABLE
이 아닌 READ_UNCOMMITED
, READ_COMMITED
, REPEATABLE_READ
이라면 INSERT와 연결되지 않은 순수한 읽기 작업은 다른 트랜잭션의 변경 작업과 상관없이 항상 잠금을 대기하지 않고 실행됩니다.
즉, 특정 트랜잭션이 레코드를 변경하고 아직 커밋을 수행하지 않았더라도, 이 변경 트랜잭션이 다른 사용자의 읽기 작업을 방해하지 않습니다. 이를 잠금 없는 일관된 읽기
라고 합니다.
가끔 오랜 시간 활성 상태인 트랜잭션으로 인해 MySQL 서버가 느려지거나 문제가 발생할 때가 있는데, 바로 이러한 일관된 읽기를 위해 언두 로그를 삭제하지 못하고 계속 유지해야 하기 때문에 발생하는 문제입니다.
따라서 트랜잭션이 시작됐다면 가능한한 빨리 COMMIT
이나 ROLLBACK
을통해 트랜잭션을 완료하는 것이 데이터베이스 서버의 성능에 좋습니다.
InnoDB는 내부적으로 잠금이 교착상태에 빠지지 않았는지 체크하기 위해, 잠금 대기 목록을 그래프(Wait-for-List) 형태로 관리합니다.
또한, 데드락 감지 스레드가 있어서, 데드락 감지 스레드가 주기적으로 잠금 대기 그래프를 검사해 교착상태에 빠진 트랜잭션들을 찾아서 그 중 하나를 강제종료시킵니다.
구글에서는 PK 기반의 조회 및 변경이 아주 높은 빈도로 실행되는 서비스가 많았는데, 이런 서비스는 매우 많은 트랜잭션을 동시에 실행하기 때문에 데드락 감지 스레드가 상당히 성능을 저하시킨다는 것을 알아냈습니다.
그래서 MySQL에서는 데드락 감지 스레드 활성화 여부를innodb_deadlock_detect
옵션으로 정할 수 있게 수정되었습니다.
InnoDB에는 손실이나 장애로부터 데이터를 보호하기 위한 몇 가지 메커니즘이 탑재되어 있습니다. 그러한 메커니즘을 이용해 MySQL 서버가 시작될 때, 완료되지 못한 트랜잭션이나 디스크에 일부만 기록된 (Partial Write) 데이터 페이지 등에 대한 일련의 복구 작업이 자동으로 진행됩니다. InnoDB 스토리지 엔진은 매우 견고해서 데이터 파일이 손상돼 문제가 생기는 경우는 거의 없습니다.
InnoDB 데이터파일은 기본적으로 MySQL 서버가 시작될 때 항상 자동 복구를 수행합니다.
다만, MySQL 서버와 무관하게 디스크나 서버 하드웨어의 이슈로 InnoDB 엔진이 자동으로 복구를 못하는 경우도 있는데 이는 복구하기가 쉽지 않습니다. 그래서 innodb_force_recovery
시스템 변수를 이용해서 여러 단계를 적용해봄으로써 데이터베이스 서버를 복구해야합니다.
innodb_force_recovery
값을 키울수록 심각한 상황임을 의미하고 데이터 손실 가능성이 커지고 복구 가능성이 줄어듭니다.참고로, innodb_force_recovery
가 0이 아닌 복구모드에선 select
이외의 insert, delete, update
쿼리는 사용불가능합니다.
innodb_force_recovery
값에 따른 단계별 복구절차1(SRV_FORCE_IGNORE_CORRUPT)
2(SRV_FORCE_NO_BACKGROUND)
3(SRV_FORCE_NO_TRX_UNDO)
mysqldump
를 이용해 데이터를 백업해서 다시 데이터베이스를 구축하는 것이 좋다.4(SRV_FORCE_NO_IBUF_MERGE)
insert buffer
에 저장해두고 나중에 처리할 수도 있다. 언제 insert buffer
의 내용이 merge
될지는 알 수 없다.merge
되지 않을 수 있는데 만약 MySQL 재시작 시에 insert buffer
의 손상을 감지하면 에러 발생5(SRV_FORCE_NO_UNDO_LOG_SCAN)
mysqldump
를 이용해 데이터를 백업하고 데이터베이스를 새로 구축해야한다.6(SRV_FORCE_NO_LOG_REDO)
mysqldump
를 이용해 데이터를 모두 백업하고 서버를 새로 구축하는 것이 좋다.Real MySQL 8.0.1