InnoDB의 모든 테이블은 Primary Key를 기준으로 Clustering되어 저장한다. 세컨더리 인덱스는 레코드의 주소 대신 프라이머리 키의 논리적인 주소로 사용된다. MyISAM의 경우는 클러스터링 키를 지원하지 않아 Primary Key에 세컨더리 인덱스는 NotNull 여부 외에는 같다.
InnoDB 스토리지 엔진에서만 지원한다. MyISAM, MEMORY 테이블 지원 안함. InnoDB의 외래키는 인덱스를 생성하고, 변경 시에 부모와 자식 테이블 데이터 체크를 하기 때문에 잠금이 여러 테이블로 전파될 수 있다. 이는 데드락을 발생 시킬 수 있기 때문에 따라서 주의해야한다.
기본적으로 레코드 레벨의 트랜잭션을 지원하는 DBMS가 지원하는 기능이다.
MVCC의 가장 큰 목적은 잠금을 사용하지 않는 일관된 읽기이다. InnoDB는 언두 로그를 통해 이를 구현한다.
m_area을 업데이트 시키고 COMMIT하지 않은 트랜잭션이 존재하는 경우, m_area를 SELECT하게 되면, 트랜잭션 격리 수준에 따라 읽어오는 값이 바뀌게 된다.
MVCC 덕분에 잠금없이 읽기가 가능해진다. 격리 수준이 SERIALIZABLE이 아닌 경우는 SELECT 작업이 잠금 대기를 하지 않고 바로 실행된다. 오랜시간 활성상태인 트랜잭션이 있는 경우 언두 로그에 지속적으로 데이터가 유지되기 때문일 수 있다. 따라서 트랜잭션을 빠르게 종료하는 것이 좋다.
InnoDB는 데드락 감지를 위해 잠금 대기 목록 그래프를 항상 갖게 되고, 데드락 감지 쓰레드를 통해 주기적으로 잠금 대기 목록 그래프를 검사해 교착 상태에 빠진 트랜잭션을 종료시킨다. 동시 처리 쓰레드가 많은 경우는 데드락 감지 쓰레드가 느려지기 때문에 innodb_deadlock_detect
를 Off하는 것도 방법이다. 이 경우 innodb_lock_wait_timeout
시스템 변수를 활성하하여 일정 시간 락을 획득하지 않는 경우 쿼리를 실패하도록 하자.
InnoDB에는 손실이나 장애로부터 데이터를 보호하기 위한 여러가지 메커니즘이 있다. 메커니즘을 통해 MySQL이 다시 시작할때 트랜잭션이나 디스크에 일부만 기록된 데이터 페이지를 복구한다. 하지만, 디스크나 서버 하드웨어에 문제가 생기는 경우는 innodb_force_recovery
시스템 변수를 사용해서 MySQL을 실행해 해결하자. 기본적으로 시스템을 실행시키고 mysqldump를 이용하여 백업하는 것이 좋다.
InnoDB의 핵심 요소이며, 디스크의 데이터 파일과 인덱스 정보를 메모리에 올리는 공간이다.
버퍼 풀은 일반적으로 전체 메모리의 80%를 설정하는게 좋다고는 하는데, 운영체제와 클라이언트 쓰레드 메모리를 고려하는 것이 좋다. 커넥션이 많은 서비스라면 클라이언트 쓰레드 메모리가 많이 필요할 것이다.
MySQL 5.7 InnoDB 버퍼 풀의 크기를 동적으로 처리할 수 있다. 버퍼 풀의 크기는 전체 메모리의 50%로 하고, 점점 올려가면서 모니터링하여 최적점을 찾도록 하자. InnoDB 버퍼 풀은 내부적으로 128MB 청크 단위로 쪼개어져 관리 되기 때문에, 버퍼 풀의 크기를 늘리거나 줄일 떄 128MB 단위로 처리된다. 버퍼 풀의 크기를 설정하는 경우는 MySQL 매뉴얼을 숙지하자.
LRU의 목적은 디스크에서 읽어온 데이터를 최대한 오랫동안 유지시키는 것.
플러시 리스트는 디스크로 동기화되지 않은 데이터 (더티 페이지)의 목록을 기록해놓는 리스트이다.
프리 리스트는 버퍼 풀에서 실제 데이터로 채워지지 않은 페이지의 목록이고, 새롭게 디스크에서 데이터를 읽어오는 경우 사용된다.
InnoDB는 버퍼 풀이 2가지 역할을 하는데, 첫번째로 데이터를 메모리에 캐시, 두번째로 쓰기 버퍼링을 한다. 하지만 버퍼 풀의 크기만 늘리는 것은 데이터 캐시 기능만 향상 시키는 것이다. 따라서 InnoDB의 쓰기 버퍼링 기능을 향상시키려면 버퍼 풀과 리두 로그의 관계를 알아야한다.
먼저 2번에서 말했듯 버퍼 풀은 더티 페이지와 클린 페이지 모두 갖게 된다. 더티 페이지는 언젠가 디스크에 기록되어야하지만, 더티페이지가 버퍼 풀에 무한정 있을 수는 없다.
데이터 변경이 발생하면 InnoDB 스토리지 엔진에서는 리두 로그 파일에 기록 하게 되고 어느 순간 새로운 리두 로그 엔트리에 의해서 덮어 쓰이게 된다.
전체 리두 로그 파일에서 재사용 가능 공간과 불가능한 공간이 구분되며, 재사용 불가능한 공간을 활성 리두 로그(Active Redo Log) 라고 한다.
InnoDB 스토리지 엔진은 주기적으로 체크 포인트를 발생시켜서 리두 로그와 버퍼 풀의 더티 페이지를 디스크로 동기화. 이렇게 발생한 체크포인트 이벤트 중 가장 최근 체크포인트 지점의 LSN 이 활성 리두 공간의 시작점
최근의 체크포인트의 LSN 과 마지막 리두 로그 엔트리의 LSN의 차이를 체크 포인트 에이지(Checkpoint Age) 라고 한다. 즉 활성 리두 로그 공간의 크기를 의미
InnoDB의 버퍼 풀을 잘 이용하려면, 리두 로그의 크기를 키우는 것이 좋다. 하지만, 너무 키우는 것은 더티 페이지를 한번에 디스크에 기록해야하므로 문제가 생길 수 있어 조심해야 한다.
Inno DB 스토리지 엔진은 버퍼 풀에서 아직 디스크에 입력되지 않은 더티 페이지들을 성능 상의 악영향 없이 디스크에 쓰기 위해 2가지의 플러시를 백그라운드에서 지원한다.
플러시 리스트 플러시
오래된 리두 공간을 지우려면, 반드시 InnoDB 버퍼 풀의 더티 페이지로 동기화가 되어야한다. 따라서 이를 위해 InnoDB 스토리지 엔진은 주기적으로 플러시 리스트 플러시 함수를 호출하여 디스크에 동기화 시켜준다. 이 때 언제부터 얼마나 많은 더티 페이지를 한 번에 디스크에 기록하느냐가 사용자의 쿼리 처리가 악영향을 받지 않으면서 부드럽게 처리되는지를 결정한다. 따라서 InnoDB 스토리지 엔진은 이와 관련된 다양한 시스템 변수를 제공한다.
LRU 리스트 플러시
LRU에서 사용빈도가 낮은 데이터 페이지를 제거하고 새로운 공간을 만들어 데이터 페이지를 넣어야 하는데, 이를 LRU 리스트 플러시 함수를 사용한다.
버퍼 풀에 사용할 데이터가 없다면, 디스크에서 데이터를 읽어와야 하기 때문에 성능상 하락이 있을 것이다. 따라서 버퍼 풀에는 사용할 데이터를 준비해야 한다. 이렇게 디스크의 데이터가 버퍼 풀에 잘 적재되어 있는 상태를 워밍업이라고 한다.
5.5 버전에는 주요 테이블 인덱스를 풀스캔 했지만, 5.6 버전부터는 버퍼 풀 덤프 및 적재 기능이 생겨 innodb_buffer_pool_dump_now
시스템 변수를 통해 현재 InnoDB의 버퍼 풀 상태를 백업할 수 있고, innodb_buffer_pool_load_now
시스템 변수를 통해 버퍼 풀 상태를 복구할 수 있다. 이러한 수동 방식이 쉽지 않아 MySQL 서버는 자동으로 백업된 버퍼 풀의 상태를 복구할 수 있는 기능을 제공한다. innodb_buffer_pool_dump_at_shutdown
, innodb_buffer_pool_load_at_startup
을 설정 파일에 넣으면 된다.
MySQL 8.0에 information_schema 데이터베이스에 innodb_cached_indexes 테이블이 추가되어 이 테이블을 이용하여 테이블의 인덱스별로 데이터 페이지가 얼마나 InnoDB 버퍼 풀에 적재되어있는지 확인이 가능하다.
InnoDB에서 디스크로 플러시할때 하드웨어에 문제가 생기는 등에 사고가 발생하면, 이를 복구해야한다. 이 현상을 파셜 페이지 or 톤 페이지라고 한다.
A-E를 입력하는 경우를 예시로 생각해보면, A-E 처럼 더티 페이지를 우선적으로 묶고 DoubleWrite에 기록한 후, 데이터 페이지를 개별로 파일로 기록한다. 이 경우 'C'를 기록하다가 비정상적인 종료가 일어나는 경우, 문제를 인지하고 DoubleWrite 버퍼의 내용과 데이터 파일의 페이지를 비교해 다른 내용을 담고 있다면 DoubleWrite 버퍼의 내용을 데이터 파일의 페이지로 복사한다.
언두 로그는 트랜잭션 보장, 격리 수준 보장을 위해 DML 쿼리 이전의 데이터를 별도로 백업해 놓는다.
언두 로그의 데이터는 2가지 용도로 구분된다.
1. 트랜잭션의 롤백을 위한 용도
2. 트랜잭션의 격리수준을 유지하면서 높은 동시성 제공
언두 로그는 100GB의 데이터를 DELETE 하는 경우나, 트랜잭션이 오랜 시간 실행 될 때에 언두 로그의 공간이 폭증할 수 있다. MySQL 5.5까지는 언두 로그의 공간이 한 번 늘어나면 서버를 새로 구축하는 것이 아니라면 줄일 수 없었다. 하지만 8.0이 되면서 사용 공간을 자동으로 줄여준다.
SELECT count
FROM information_schema.innodb_metrics
WHERE SUBSYSTEM='transaction' AND NAME='trx_rseg_history_len';
위 명령어를 통해 언두 로그를 모니터링할 수 있다.
하나의 언두 테이블스페이스는 1-128개의 롤백 세그먼트를 가지고, 롤백 세그먼트는 1개 이상의 언두 슬롯을 가진다.
최대 동시 트랜잭션 수 : (InnoDB 페이지 크기) / 16 * (롤백 세그먼트 개수) * (언두 테이블스페이스 개수)
가장 일반적인 페이지 16KB InnoDB에서 기본 설정을 사용하면 대략 131072개의 트랜잭션이 동시에 가능하다.
RDBMS에서는 UPDATE나 INSERT가 되는 경우, INDEX를 업데이트 해줘야한다. 인덱스 정보가 버퍼 풀에 올라와 있는 경우는 바로 업데이트하지만, 그렇지 않은 경우는 이를 즉시 실행하지 않고 임시 공간에 저장해두고 값을 반환하는 형태를 띄는데, 이 임시 공간을 체인지 버퍼라고 한다.
체인지 버퍼에서 저장된 임시의 인덱스 조각은 나중에 백그라운드 쓰레드에 의해 병합 되는데, 이 쓰레드를 체인지 버퍼 쓰레드라고 한다.
리두 로그 : 리두 로그는 데이터 블록, 인덱스 블록 및 실행 취소 블록에 대한 변경 사항을 포함하여 데이터베이스에 대한 변경 사항에 대한 정보를 포함하는 레코드 집합. 리두 로그의 목적은 장애 발생 시 데이터베이스 복구를 허용.
리두 로그는 ACID의 D(Durable)에 해당하는 영속성과 가장 밀접하게 연관돼 있다. 리두 로그는 서버가 비정상적으로 종료됐을 때 데이터가 기록되지 못하는 경우를 막기 위한 안전장치이다.
거의 대부분의 DBMS는 데이터의 변경을 기록하기 전에 로그를 먼저 기록한다.
데이터베이스 서버는 ACID도 중요하지만 성능도 중요하기 때문에 데이터 파일 뿐만 아니라 리두 로그를 버퍼링 할 수 있는 InnoDB 버퍼 풀이나 리두 로그를 버퍼링할 수 있는 로그 버퍼와 같은 자료 구조도 가지고 있다.
MySQL에서 비정상 종료가 일어나는 경우, 2가지의 일관되지 않은 데이터가 발생한다.
1번의 경우는 리두 로그의 데이터를 저장하면 된다. 2번의 경우는 리두 로그의 트랙잭션 실행 중인지 커밋인지 롤백인지 확인하고, 언두 로그를 확인하여 처리한다.
리두 로그 아카이빙은 오래된 리두 로그를 백업이나 다른 목적을 위해 다른 저장위치나 장치로 이동 시키는 프로레스이다. 시간이 지남에 따라 리두 로그가 커지기 때문에 아카이브 해야 할 수도 있다.
MySQL 8.0부터 리두 로그를 아카이빙하는 기능이 추가되었다. 이를 활성화/비활성화 할 수 있다.
B-Tree의 검색 시간을 줄여주기 위해 도입된 기능. InnoDB 스토리지 엔진은 자주 읽히는 데이터 페이지의 키 값을 이용해 해시 인덱스를 만들고, 필요할 때마다 어댑티브 해시 인덱스를 검색해서 레코드에 저장된 페이지를 즉시 찾아갈 수 있다.
출저 : Real MySQL 8.0, https://hoing.io/archives/1641?hilite=tomcat