InnoDB는 MySQL에서 사용할 수 있는 스토리지 엔진 중 거의 유일하게 레코드 기반 잠금을 제공하며, 따라서 높은 동시성 처리가 가능하고 안정적 + 성능이 뛰어나다. InnoDB 스토리지 엔진은 다음과 같은 특성을 지닌다.
프라이머리 키에 의한 클러스터링
InnoDB의 모든 테이블은 기본적으로 프라이머리 키를 기준으로 클러스터링되어 저장된다. 클러스터링이란 비슷한 것들끼리 하나로 묶는다는 뜻으로, 프라이머리 키 값의 순서대로 디스크에 저장된다.
Non-locking consistent read
InnoDB 엔진은 MVCC(Multi Version Concurrency Control)을 이용해 락을 걸지 않고 읽기 작업을 수행한다(여기서 MVCC는 매우 중요한 개념이므로 뒤에서 한 번 더 정리하겠다). 그렇기 때문에 다른 트랜잭션이 가지고 있는 락을 기다리지도 않는다!!
예시로 스토리지 엔진이 InnoDB인 상태에서 employees 데이터베이스의 테이블에 락을 걸고 UPDATE 쿼리를 실행시켜 봤다.
mysql> LOCK TABLES employees READ;
mysql> UPDATE employees SET hire_date=NOW() WHERE emp_no=100001;
mysql> UPDATE employees SET birth_date=NOW(), hire_date=NOW() WHERE emp_no=100001;
MyISAM이었다면 State가 Locked가 되었겠지만, InnoDB에서는 starting 인 것을 확인할 수 있다.
SHOW OPEN TABLES FROM employees LIKE 'employees';
또한 해당 테이블의 잠금을 기다리는 클라이언트의 수가 In_use에 나타나지 않기 때문에(=해당 테이블의 잠금 자체를 기다리지 않기 때문에) In_use의 값이 1인 것을 확인할 수 있다.
외래 키 지원
외래 키 지원은 MyISAM, MEMORY 테이블에서는 사용할 수 없다. InnoDB 엔진 레벨에서만 지원하는 기능이다. 하지만 InnoDB에서의 외래 키는 부모/자식 테이블 모두 해당 칼럼에 인덱스 생성이 필수이고 변경 시 데이터가 존재하는지 체크하는 작업이 필요하기 때문에 잠금이 여러 테이블로 전파되어 데드락이 발생할 가능성이 높다.
자동 데드락 감지
InnoDB는 그래프 기반 데드락 체크 방식을 사용하여 데드락이 발생함과 동시에 바로 감지되고, ROLLBACK이 가장 용이한 트랜잭션을 자동적으로 강제 종료한다. 따라서 데드락 때문에 쿼리가 제한시간을 초과하는 경우는 거의 없다.
장애 복구 자동화
MySQL 서버가 시작될 때, 완료되지 못한 트랜잭션 복구 작업이 자동으로 시작된다.
오라클 아키텍처 적용
MVCC, Undo, table space 개념 등 오라클 데이터베이스와 흡사한 부분이 많다.
1. InnoDB 버퍼 풀
버퍼 풀이란, 디스크의 데이터 파일이나 인덱스 정보를 메모리에 캐시해 두는 공간이다. 쓰기 작업을 지연시켜 일괄작업으로 처리하도록 해주는 버퍼 역할도 한다. INSERT나 UPDATE, DELETE로 변경된 데이터를 모아서 처리하면 랜덤I/O 횟수를 줄일 수 있다. MyISAM의 키 캐시가 인덱스의 캐시만을 처리하는 반면, InnoDB의 버퍼 풀은 데이터와 인덱스 모두 캐시하고 또 쓰기 버퍼링의 역할까지 처리하는 것이다.
InnoDB 버퍼 풀은 아직 디스크에 기록되지 않은 변경된 데이터(dirty page)를 가지고 있다. 이러한 더티 페이지는 InnoDB에서 주기적이나 특정 조건이 되면 체크포인트 이벤트가 발생하는데, 이 때 Write 스레드가 필요한 만큼의 더티 페이지만 디스크에 기록한다. 즉, 체크포인트가 발생한다고 버퍼 풀에 있는 모든 더티 페이지를 디스크로 기록하는 것은 아니다.
InnoDB 버퍼 풀의 크기를 설정하는 파라미터는 innodb_buffer_pool_size 이다. 버퍼 풀은 많은 기능을 담당하므로 크기를 결정할 때는 신중하게 설정하는 것이 좋다. 일반적으로 전체 물리 메모리의 50~80%수준이 적당하다.
2. Undo 로그
Undo 영역은 UPDATE나 DELETE와 같이 데이터를 변경했을 때, 변경되기 전의 데이터를 보관하는 곳이다. Undo의 데이터는 크게 두 가지 용도로 사용되는데, 첫 번째 용도는 트랜잭션의 롤백 대비용이다. 예를 들어 아래와 같은 업데이트 문장을 실행했다고 해 보자.
mysql> UPDATE member SET name='awake' WHERE member_id='1';
트랜잭션을 커밋하지 않아도 실제 데이터 파일의 내용은 'awake'로 변경된다. 만약 변경 전 데이터가 'sleep' 였다면, Undo 영역에는 'sleep'가 백업되는 것이다. 이 상태에서 사용자가 COMMIT하면 현 상태가 유지되고, ROLLBACK하면 Undo 영역의 데이터를 다시 데이터 파일로 복구한다.
Undo의 데이터가 쓰이는 다른 용도로는 트랜잭션의 격리 수준을 유지하면서 높은 동시성을 제공하는 데 있다. 트랜잭션의 격리 수준이란 동시에 여러 트랜잭션이 데이터를 변경하거나 조회할 때, "한 트랜잭션의 작업 내용이 다른 트랜잭션에 어떻게 보여질지" 결정하는 기준이다.
3. 인서트 버퍼(Insert Buffer)
레코드가 INSERT/DELETE될 때는 해당 테이블의 인덱스 또한 업데이트해야 한다. 인덱스 업데이트에는 랜덤I/O 작업이 필요하므로 작업이 많아질수록 성능은 저하된다. 따라서 InnoDB는 변경해야 할 인덱스 페이지가 버퍼 풀에 있으면 바로 업데이트를 수행하지만, 디스크로부터 읽어와서 업데이트해야할 경우에는 즉시 실행하지 않고 임시 공간인 인서트 버퍼에 저장해두고 사용자에게 결과를 반환하는 형태로 성능을 향상시킨다.
* Unique Index는 사용자에게 결과를 전달하기 전 반드시 중복 여부를 체크하므로 인서트 버퍼를 사용할 수 없다.
4. MVCC(Multi Version Concurrency Control)
레코드 레벨의 트랜잭션을 지원하는 DBMS가 제공하는 기능이며, 오라클 DBMS에도 존재하는 기능이다. MVCC의 가장 큰 목적은 잠금을 사용하지 않는 일관된 읽기를 제공하는 것인데, InnoDB에서는 이를 Undo 로그를 이용해서 구현한다. UPDATE 쿼리를 실행하면 InnoDB 버퍼 풀은 즉시 새로운 데이터로 변경되며, 기존의 데이터는 Undo로 복사되는데, 아직 COMMIT이나 ROLLBACK이 되지 않은 상태에서 SELECT로 조회를 하면 격리 수준에 따라 데이터를 조회하는 곳이 달라진다.
isolation_level이 READ_UNCOMMITTED인 경우에는 InnoDB 버퍼 풀이나 데이터 파일로부터 아직 변경되지 않은 데이터를 읽어서 반환하므로, 데이터가 커밋됐든 아니든 변경된(UPDATE) 데이터를 반환한다.
반면 isolation_level=READ_COMMITTED나 그 이상의 격리 수준일 경우, 아직 커밋되지 않았기 때문에 변경되기 이전의 내용을 보관하고 있는 Undo 영역의 데이터를 반환한다.
COMMIT이 된다고 Undo 영역의 백업 데이터가 항상 바로 삭제되는 것은 아니다. 이 데이터는 Undo 영역을 필요로 하는 트랜잭션이 더는 없을 때 삭제된다.
'MySQL' 카테고리의 다른 글
05. InnoDB 트랜잭션 처리 및 로그(Undo/Redo) 관리 (0) | 2020.06.23 |
---|---|
04. MySQL에서의 잠금 - (2) MyISAM과 MEMORY 스토리지 엔진 (0) | 2020.06.15 |
04. MySQL에서의 잠금 - (1) MySQL 엔진 (0) | 2020.06.15 |
03. 트랜잭션 (0) | 2020.06.15 |
01. MySQL 아키텍처 (0) | 2020.06.13 |