Q. InnoDB가 MySQL 에서 사용할 수 있는 스토리지 엔진과 비교해서 유일한 기능은 무엇인가요?
InnoDB는 MySQL에서 사용할 수 있는 스토리지 엔진 중 거의 유일하게 레코드 기반의 잠금을 제공합니다
이 때문에 높은 동시성 처리가 가능하고 안정적이며 성능이 뛰어납니다.
프라이머리 키에 의한 클러스터링
Q. InnoDB 테이블에 프라이머리 키를 설정하면 어떻게 되나요?
InnoDB의 모든 테이블은 프라이머리 키를 기준으로 클러스터링되어 저장됩니다. 즉, 프라이머리 키 값의 순서대로 디스크에 저장됩니다. 또 세컨더리 인덱스는 레코드의 주소 대신 프라이머리 키의 값을 논리적인 주소로 사용합니다.
외래 키 지원
Q. MyISAM 스토리지 엔진은 외래 키를 사용할 수 있나요?
외래 키에 대한 지원은 InnoDB 스토리지 엔진 레벨에서 지원하는 기능으로 MyISAM이나 MEMORY 테이블에서는 사용할 수 없습니다.
Q. InnoDB에 외래 키로 인해 발생하는 일들에는 어떤 것이 있나요?
- InnoDB에서 외래 키는 부모 테이블과 자식 테이블 모두 해당 칼럼에 인덱스 생성이 필요
- 변경 시에 반드시 부모 테이블이나 자식 테이블에 데이터가 있는지 체크하는 작업이 필요하므로 잠금이 여러 테이블로 전파됨
- 그로 인해 데드락이 발생할 때가 많음. 개발할 때 외래 키 존재에 주의
MVCC(Multi Version Concurrency Control)
Q. MVCC는 어떤 기능을 지원하는 DBMS가 제공하는 기능이며 어떤 목적으로 사용하는가요?
MVCC는 일반적으로 레코드 레벨의 트랜잭션을 지원하는 DBMS가 제공하는 기능입니다.
MVCC는 잠금을 사용하지 않는 일관된 읽기를 위해 사용됩니다.
Q. InnoDB는 무엇을 이용해 MVCC를 구현하나요?
InnoDB는 MVCC를 언두 로그를 이용해 이 기능을 구현합니다.
Q. MVCC의 멀티 버전은 어떤 것을 의미하나요?
멀티 버전은 하나의 레코드에 대해 여러 개의 버전이 동시에 관리된다는 것을 의미합니다.
Q. 언두 로그란 무엇인가?
트랜잭션 중 변경된 데이터를 트랜잭션 이전 상태로 복구할 수 있도록 기록하는 로그
Q. 격리 수준(isolation level)에 따라 MVCC가 대략 어떻게 동작하나요?
메모리에는 InnoDB 버퍼 풀(InnoDB 스토리지 엔진의 백그라운드 스레드로 디스크에서 읽은 데이터) 과 언두 로그가 있습니다.
만일 격리 수준이 READ_UNCOMMITED인 경우에는 InnoDB 버퍼 풀이 현재 가지고 있는 변경된 데이터 반환합니다.
격리 순준이 READ_COMMITED인 경우에는 언두 로그에 있는 데이터를 반환합니다.
Q. 언두 로그를 이용한 MVCC에서 발생할 수 있는 문제점은 무엇이 있나요?
트랜잭션이 길어지면 언두에서 관리하는 예전 데이터가 삭제되지 못하고 오랫동안 관리돼야 합니다. 이는 자연히 언두 영역이 저장되는 시스템 테이블스페이스의 공간이 많이 늘어나는 상황이 발생할 수 있습니다.
잠금 없는 일관된 읽기
Q. InnoDB 스토리지 엔진의 MVCC 기술을 이용한 잠금 없는 일관된 읽기란 무엇인가요?
InnoDB 스토리지 엔진은 MVCC 기술을 이용해 잠금을 걸지 않고 읽기 작업을 수행합니다. 잠금을 걸지 않기 때문에 InnoDB에서 읽기 작업은 다른 트랜잭션이 가지고 있는 잠금을 기다리지 않고 읽기 작업이 가능합니다.
격리 수준이 SERIALIZABLE이 아닌 READ_UNCOMMITED나 READ_COMMITED, REPEATABLE_READ 수준인 경우 INSERT와 연결되지 않은 순수한 읽기(SELECT) 작업은 다른 트랜잭션의 변경 작업과 관계없이 항상 잠금을 대기하지 않고 바로 실행된다.
자동 데드락 감지
Q. InnoDB 스토리지 엔진이 데드락을 어떻게 관리하나요?
InnoDB 스토리지 엔진은 내부적으로 잠금이 교착 상태에 빠지지 않았는지 체크하기 위해 잠금 대기 목록을 그래프 형태로 관리합니다.
InnoDB 스토리지 엔진은 데드락 감지 스레드를 가지고 있어서 주기적으로 잠금 대기 그래프를 검사해 교착 상태에 빠진 트랜잭션들을 찾아서 그 중 하나를 강제 종료합니다.
Q. 트랜잭션 간 데드락을 감지했을 때 InnoDB는 어떤 트랜잭션을 먼제 강제 종료할까요?
우선 강제 종료 판단 기준은 트랜잭션의 언두 로그 양이며, 언두 로그 레코드를 더 적게 가진 트랜잭션이 일반적으로 롤백 대상이 됩니다.
언두 로그 레코드를 적게 가진 것을 롤백할 때 언두 처리를 해야 할 내용이 적으며, 트랜잭션 강제 롤백으로 인한 MySQL 서버의 부하도 덜 유발하기 때문
InnoDB 버퍼 풀
Q. InnoDB 버퍼 풀은 어떤 역할을 하나요?
InnoDB 스토리지 엔진에서 가장 핵심적인 부분으로, 디스크의 데이터 파일이나 인덱스 정보를 메모리에 캐시해 두는 공간입니다. 또 쓰기 작업을 지연시켜 일괄 작업으로 처리할 수 있게 해주는 버퍼 역할도 합니다.
데이터 변경 쿼리는 데이터 파일 의 랜덤한 위치에 레코드를 변경하기 때문에 랜덤한 디스크 작업을 발생시킵니다. 하지만 버퍼 풀이 이러한 변경된 데이터를 모아서 처리하면 랜덤한 디스크 작업의 횟수를 줄일 수 있습니다.
Q. 버퍼 풀 공간은 어떤 구조로 관리되나요?
InnoDB 스토리지 엔진은 버퍼 풀이라는 거대한 메모리 공간을 페이지 크기(시스템 변수에 설정된)의 조각으로 쪼개어 InnoDB 스토리지 엔진이 데이터를 필요로 할 때 해당 데이터 페이지를 읽어서 각 조각에 저장합니다.
버퍼 풀의 페이지 크기 조각을 관리하기 위해 InnoDB 스토리지 엔진은 크게 LRU(Least Recently Used) 리스트, 플러시 리스트, 프리 리스트라는 3개의 자료 구조를 관리합니다.
Q. 버퍼 풀을 관리하기 위한 3가지 자료 구조의 각 목적은 무엇인가요?
- LRU 리스트 : 디스크로부터 한 번 읽어온 페이지를 최대한 오랫동안 InnoDB 버퍼 풀의 메모리에 유지해서 디스크 읽기를 최소화하기 위해
- 프리 리스트 : 버퍼 풀에서 실제 사용자 데이터로 채워지지 않은 비어 있는 페이지 목록, 사용자의 쿼리가 새롭게 디스크의 데이터 페이지를 읽어와야 하는 경우 사용함
- 플러시 리스트 : 디스크로 동기화 되지 않은 데이터를 가진 데이터 페이지(이를 더티 페이지라고 함)의 변경 시점 기준의 페이지 목록을 관림
Q. InnoDB 버퍼 풀에서 원하는 페이지가 존재하는지 어떻게 알아내나요?
InnoDB 버퍼 풀에서 원하는 페이지가 존재하는지 확인하는 방식은 "버퍼 풀 내부의 해시 테이블" 을 통해 빠르게 찾습니다.
페이지를 찾을 때 InnoDB는 그 페이지를 식별하는 (tablespace ID, page number) 쌍을 사용합니다.
(각 테이블스페이스 파일 안의 특정 페이지를 유일하게 지정하는 ID입니다.)
Q. 리두 로그란 무엇인가?
리두 로그는 트랜잭션 중 변경된 데이터를 장애 발생 시 디스크에 복구할 수 있도록 기록하는 로그입니다.
Q. 플러시 리스트와 리두 로그는 어떤 관계가 있나요?
데이터가 변경되면 InnoDB는 변경 내용을 리두 로그에 기록하고 버퍼 풀의 데이터 페이지에도 변경 내용을 반영합니다.
그래서 리두 로그의 각 엔트리는 특정 데이터 페이지와 연결됩니다.
Q. 버퍼 풀과 리두 로그는 어떤 관계가 있나요?
InnoDB 버퍼 풀은 데이터베이스 서버의 성능 향상을 위해 데이터 캐시와 쓰기 버퍼링이라는 두 가지 용도가 있습니다.
버퍼 풀의 메모리 공간만 단순히 늘리는 것은 데이터 캐시 기능만 향상시키는 것입니다.
버퍼 풀의 쓰기 버퍼링 기능까지 향상시키려면 버퍼 풀과 리두 로그와 관계를 이해해야 합니다.
InnoDB 버퍼 풀의 더티 페이지는 특정 리두 로그 엔트리와 관계를 가지고, 체크포인트(더티 페이지를 디스크로 동기화하는 작업)가 발생하면 리두 로그 엔트리와 관련된 더티 페이지 모두 디스크로 동기화돼야 합니다.
따라서, 버퍼 풀의 크기가 매우 크더라도 사용가능 한 리두 로그 파일의 크기가 작으면 허용 가능한 더티 페이지 크기가 줄어듭니다.
Q. 기본적으로 InnoDB 스토리지 엔진은 전체 버퍼 풀이 가진 페이지의 몇 퍼센트까지 더티 페이지를 가질 수 있나요?
기본적으로 90%까지 더티 페이지를 가질 수 있지만 이 값이 너무 높을 수도 있기 때문에 시스템 변수로 비율을 조정할 수 있습니다.
버퍼 풀이 더티 페이지를 많이 가지고 있을 수록 디스크 쓰기 작업을 버퍼링함으로써 여러 번의 디스크 쓰기를 한 번으로 줄이는 효과를 극대화 할 수 있습니다.
하지만 디스크로 기록되는 더티 페이지 개수보다 더 많은 더티 페이지가 발생하면 더티 페이지가 계속 증가하게 되고, 어느 순간 90%을 넘어가면 InnoDB 스토리지 엔진은 급작스럽게 더티 페이지를 디스크로 기록해야 한다고 판단하고 디스크 쓰기 폭발 현상이 발생할 수 있습니다.
이런 문제를 완하하기 위해 일정 비율 이상의 더티 페이지 발생 시 조금씩 디스크로 기록하게 하고 있습니다.
Q. 버퍼 풀 플러시란 무엇인가요?
InnoDB 스토리지 엔진은 버퍼 풀에서 아직 디스크로 기록되지 않은 더티 페이지들을 성능상 악영향 없이 디스크에 동기화하기 위해 다음과 같이 2개의 플러기 기능을 백그라운드로 실행합니다.
- 플러시 리스트 플러시
- LRU 리스트 플러시 : LRU 리스트에서 사용 빈도 낮은 데이터 페이지를 제거해서 새로운 페이지를 위한 공간을 만들기 위한 작업 중 제거하는 데이터 페이지가 더티 페이지면 디스크에 동기화하게 하는 것
Q. 버퍼 풀 상태 백업이 필요한 이유는 무엇인가요?
버퍼 풀은 쿼리의 성능에 매우 밀접하게 연결돼 있습니다. 만일 MySQL 서버를 셧다운 후 다시 서비스를 시작하면 쿼리의 성능이 평상시 보다 많이 떨어집니다. 디스크의 데이터가 버퍼 풀에 적재돼 있는 상태를 워밍업이라고 표현하는데, 버퍼 풀을 백업 후 서비스 재시작 시 복구하여 빠르게 워밍업 시키기 위함입니다.
Double Write Buffer
Q. Double-Write 기법이 나오게 된 이유는 무엇인가요?
스토리지 엔진에서 더티 페이지를 디스크 파일로 플러시할 때 일부만 기록되는 문제가 발생하면 그 페이지의 내용을 복구할 수 없을 수도 있습니다. 이렇게 페이지 일부만 기록되는 현상을 파셜 페이지라고 합니다.
이러한 문제를 막기 위해 Double-Write 기법을 이용합니다.
Q. Double-Write 기법은 무엇인가요?
각각의 더티 페이지를 데이터 파일의 적당한 위치에 하나씩 랜덤으로 쓰기 전에 더티 페이지를 묶어서 한 번의 디스크 쓰기로 시스템 테이블스페이스의 DoubleWrite 버퍼에 기록합니다.
Q. 저장 매체에 따른 DoubleWrite 기법 문제점은 무엇인가요?
DoubleWrite 버퍼는 더티 페이지를 묶어 한 번의 디스크 쓰기이기 때문에 순차 디스크 쓰기입니다.
HDD인 경우 순차 디스크 쓰기가 부담스럽지 않지만 SSD인 경우 랜던 IO와 순차IO의 비용이 비슷하기 때문에 부담스럽습니다. 그래서 비활성하는 옵션도 존재합니다.
MySQL은 DoubleWrite 기법 외에도 InnoDB의 리두 로그와 복제를 위한 바이너리 로그 등 무결성을 위한 장치들이 많습니다.
언두 로그
Q. 언두 로그는 어디에 사용되나요?
- 트랜잭션 보장
트랜잭션 롤백 시 데이터 복구에 사용됨 - 격리 수준 보장
MVCC를 통해 격리 수준에 맞게 데이터를 읽을 때 사용됨
Q. 장시간 활성회된 트랜잭션이 언두 로그에 어떤 영향을 주나요?
InnoDB 스토리지 엔진의 언두 로그는 해당 트랜잭션 이후 생성된 언두 로그를 계속 보존하게 된다.
누적된 언두 로그로 인해 디스크의 사용량이 증가하고 그동안 빈번하게 변경된 레코드를 조회하는 쿼리가 실행되면 언두 로그의 이력을 필요한 만큼 스캔해야만 필요한 레코드를 찾을 수 있기 때문에 쿼리의 성능이 전반적으로 떨어집니다.
체인지 버퍼
Q. 체인지 버퍼는 무엇인가요?
데이터 변경 쿼리 실행 시 데이터 파일을 변경하는 작업뿐 아니라 해당 테이블에 포함된 인덱스를 업데이트하는 작업도 필요합니다.
인덱스를 업데이트하는 작업은 랜덤하게 디스크를 읽는 작업이 필요하므로 테이블에 인덱스가 많으면 이 작업은 많은 자원을 소모하게 됩니다.
그래서 InnoDB는 변경해야 할 인덱스 페이지가 버퍼 풀에 있다면 바로 업데이트를 수행하지만 디스크로부터 읽어와야 한다면 즉시 실행하지 않고 임시 공간에 저장해 두고 바로 사용자에게 결과를 반환하는 형태로 성능을 향상시킵니다.
이때 사용하는 임시 메모리 공간을 체인지 버퍼라고 합니다.
사용자에게 결과를 전달하기 전에 반드시 중복 여부를 체크해야 하는 유니크 인덱스는 체인지 버퍼를 사용할 수 없습니다.
어댑티브 해시 인덱스
Q. 어댑티브 해시 인덱스란 무엇인가요?
InnoDB 스토리지 엔진에서 사용자가 자주 요청하는 데이터에 대해 자동으로 생성하는 인덱스입니다.
Q. 어댑티브 해시 인덱스는 왜 필요한가요?
B-Tree 인덱스에서 특정 값을 찾기 위해서는 여러 노드를 거쳐야 원하는 레코드를 찾을 수 있습니다.
이런 작업을 동시에 몇천 개의 스레드로 실행하면 컴퓨터의 CPU는 엄청난 프로세스 스케줄링으로 쿼리의 성능이 떨어집니다.
어댑티브 해시 인덱스는 이러한 B-Tree 검색 시간을 줄여주기 위해 도입된 기능입니다. B-Tree를 루트 노드부터 리프 노드까지 찾아가는 비용을 없애 CPU 작업을 줄이고 쿼리의 성능을 높입니다. 또 동시에 더 많은 쿼리를 동시에 처리할 수 있습니다.
Q. 어댑티브 해시 인덱스의 키-값 쌍은 어떤 정보로 구성되나요?
어댑티브 해시 인덱스는 '인덱스 키 값'과 해당 인덱스 키 값이 저장된 '데이터 페이지의 주소'의 쌍으로 관리됩니다.
인덱스 키 값은 'B-Tree 인덱스의 고유 번호와 B-Tree 인덱스의 실제 키 값' 조합으로 생성됩니다. B-Tree 인덱스의 고유 번호를 포함하는 이유는 InnoDB 스토리지 엔진에서 어댑티브 해시 인덱스는 하나만 존재(파티션되는 기능도 있지만)하기 때문입니다. 즉, 모든 B-Tree에 대한 어댑티브 해시 인덱스가 하나의 해시 인덱스에 저장되기 때문입니다.
데이터 페이지의 주소는 InnoDB 버퍼 풀에 로딩된 페이지의 주소를 의미합니다.
Q. 어댑티브 해시 인덱스에 파티션 기능이 필요한 이유가 무엇인가요?
예전 버전까지는 어댑티브 해시 인덱스는 하나의 메모리 객체인 이유로 어댑티브 해시 인덱스에 대한 경합이 상당히 심했습니다.
그래서 MySQL 8.0부터는 내부 잠금 경합을 줄이기 위해 어댑티브 해시 인덱스이 파티션 기능을 제공합니다. 기본값은 8개이며 시스템 변수를 이용해 개수를 변경할 수 있습니다.
Q. 어댑티브 해시 인덱스로 인해 발생할 수 있는 문제점이 있나요?
어댑티브 해시 인덱스 또한 저장 공간인 메모리를 사용하며, 때로는 상당히 큰 메모리 공간을 사용할 수도 있습니다.
어댑티브 해시 인덱스가 활성화되면 InnoDB 스토리지 엔진은 그 키 값이 해시 인덱스에 있든 없든 검색해봐야 한다는 것입니다. 즉, 해시 인덱스의 효율이 없는 경우에도 InnoDB는 계속 해서 인덱스를 사용할 것입니다.
어댑티브 해시 인덱스는 테이블의 삭제, 변경 작업 시 InnoDB 스토리지 엔진은 이 테이블이 가진 모든 데이터 페이지의 내용을 어댑티브 해시 인덱스에서 제거되야 합니다.
InnoDB와 다른 스토리지 엔진 비교
Q. MySQL 버전별 InnoDB와 다른 스토리지 엔진 간 차이가 있나요?
MySQL 5.5부터 InnoDB 스토리지 엔진이 기본 스토리지 엔진으로 채택됐지만 시스템 테이블에 사용되거나 전문 검색, 공간 좌표 기능은 MyISAM 테이블만 지원되었습니다. 하지만 8.0으로 업그레이드 되면서 모두 InnoDB 스토리지 엔진으로 대체되었습니다.
8.0에서는MySQL의 모든 기능이 InnoDB 스토리지 엔진 기반으로 재편됐고, MyISAM 스토리지 엔진만이 가지는 장점이 없는 상태다.
'데이터베이스' 카테고리의 다른 글
[도서] Real MySQL : 인덱스 - 질문지 (0) | 2025.05.06 |
---|---|
MySQL 스레드 풀 적용 및 성능 비교 (0) | 2025.05.04 |
존재하지 않는 레코드에 비관적 락으로 동시성 제어가 가능할까 (0) | 2025.05.02 |
MySQL에서 GROUP BY 처리는 어떻게 할까? (0) | 2025.04.30 |
[도서] Real MySQL : MySQL 아키텍처 - 질문지 (0) | 2025.04.25 |