2024. 1. 29. 23:55ㆍDatabase
InnoDB
InnoDB의 구조는 다음과 같다.
InnoDB는 높은 안정성과 좋은 성능을 가진 스토리지 엔진으로, MySQL 스토리지 엔진 가운데 가장 많이 사용된다.
MySQL의 스토리지 엔진 중 거의 유일하게 레코드 기반 잠금을 제공한다.
☁️ 인메모리 구조
- 버퍼 풀
- 체인지 버퍼
- 어댑티브 해시 인덱스
- 로그 버퍼
☁️ 디스크 구조
- 테이블
- 인덱스
- 테이블스페이스
- Double Write Buffer
- Redo Log
- Undo Log
InnoDB 특징
InnoDB는 다음과 같은 특징을 갖는다.
- PK에 의한 클러스터링
- 외래 키 지원
- MVCC
- 잠금 없는 일관된 읽기
- Deadlock Detection
- 자동화된 장애 복구
✔️ PK에 의한 클러스터링
InnoDB의 모든 테이블은 기본적으로 PK를 기준으로 클러스터링되어 저장된다.
이외 모든 세컨더리 인덱스는 리프 노드에 데이터(레코드)의 주소 대신 PK의 값을 논리적인 주소로 사용한다.
클러스터는 '군집'이라는 뜻을 가진다.
즉 클러스터링은 대상을 군집화 시키는 것을 의미한다.
PK 기준으로 군집화되므로, PK가 비슷한 데이터 끼리 함께 모여 저장된다고 생각할 수 있다.
데이터는 디스크에 페이지 단위(기본 16KB)로 저장되는데,
한 페이지 내의 데이터는 PK 순으로 정렬되어 저장된다.
PK를 설정하지 않으면 다음과 같은 순서로 클러스터링 인덱스를 지정한다.
- NOT NULL로 정의된 첫 번째 UNIQUE 인덱스
- 적절한 UNIQUE 인덱스가 없는 경우 GEN_CLUST_INDEX라는 숨겨진 클러스터링 인덱스를 생성
✔️ 외래 키(Foreign Key) 지원
InnoDB는 데이터 무결성을 위해 외래 키 제약조건을 지원한다.
외래 키를 사용하게 되면 데이터 변경 시 부모 테이블과 자식 테이블에 데이터가 았는지 체크하는 작업이 필요하다.
해당 작업을 수행하면 잠금이 여러 테이블로 전파되고,
이로 인해 데드락이 발생할 수 있어 주의해야 한다.
임시로 외래 키 제약조건 검사를 비활성화 하는 방법은 다음과 같다.
SET foreing_key_checks=OFF;
SET foreing_key_checks=ON;
✔️ MVCC (Multi Version Concurrency Control)
일반적으로 레코드 레벨의 트랜잭션을 지원하는 DBMS에서 제공하는 기능으로,
MVCC의 가장 큰 목적은 잠금을 사용하지 않는 일관된 읽기이다.
MVCC에서 Multi Version은 하나의 레코드에 대해 여러 개의 버전이 동시에 관리되는 것을 의미한다.
InnoDB는 언두 로그(Undo Log)를 이용하여 MVCC를 지원하는데, 아래 예시를 통해 살펴보자.
위 상태에서 UPDATE 쿼리를 통해 데이터를 변경해보자.
UPDATE member SET m_area='경기' WHERE m_id=12;
UPDATE 가 실행되면 버퍼 풀의 데이터는 곧바로 업데이트된다.
언두 로그에는 변경된 값에 대해서만 변경되기 이전의 값이 저장된다.
디스크의 데이터는 체크포인트나 InnoDB의 Write 스레드에 의해 값이 변경되었을 수도, 아닐 수도 있다.
(그러나 InnoDB는 ACID를 보장하므로, 일반적으로 버퍼 풀과 같은 상태라고 생각해도 무방하다.)
현재 상황에서 사용자가 작업 중인 데이터를 조회하면 어떻게 될까?
SELECT * FROM member WHERE m_id=12;
위 쿼리의 결과는 격리 수준(Isolation level)에 따라 달라진다.
- READ_UNCOMMITTED: 버퍼 풀이 현재 가진 데이터, 즉 '경기'를 읽는다.
- READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE: 언두 영역의 데이터, 즉 '서울'을 읽는다.
이렇듯 한 레코드에 대해 여러 버전의 데이터가 관리되는 것을 MVCC라 한다.
이곳에서는 언두 로그가 단 하나만 존재했지만,
하나의 레코드에 대해 여러 개의 언두 로그가 존재하는 것 역시 가능하다.
커밋 시 InnoDB는 이를 영구적인 데이터로 만들지만,
롤백하게 되면 언두 영역의 데이터를 버퍼 풀로 다시 복구하고 언두 영역의 내용을 삭제한다.
언두 영역의 데이터는 커밋이 된다고 해서 바로 삭제되는 것이 아니고,
언두 영역을 필요로 하는 다른 트랜잭션이 없는 경우 삭제된다.
☁️ (참고) - 언두 로그의 위치
공식문서의 InnoDB의 구조 사진을 보면 언두 로그는 디스크에만 존재하는 것 처럼 보인다.
언두로그는 메모리와 디스크에 모두 존재하며,
메모리 영역의 언두 로그는 버퍼의 역할을 수행한다.
디스크에 존재하는 언두 로그는 Undo Tablespace라고 한다.
메모리의 언두 로그 버퍼가 플러시되면
언두 테이블스페이스로 언두 로그 엔트리들이 저장된다.
✔️ 잠금 없는 일관된 읽기 (NON-Locking Consistent Read)
InnoDB는 MVCC를 사용해 잠금(Lock) 없이 읽기 작업을 수행한다.
잠금을 걸지 않으므로 InnoDB의 읽기 작업은 다른 트랜잭션이 가진 잠금을 기다리지 않고 곧바로 실행된다.
(Isolation level이 SERIALIZABLE인 경우에는 잠금을 기다린다.)
따라서 특정 사용자가 데이터를 변경하고 커밋하지 않았더라도,
다른 사용자의 SELECT 작업을 방해하지 않는다.
이를 잠금 없는 일관된 읽기라고 하며,
InnoDB에서는 변경 전의 데이터를 읽기 위해 언두 로그를 사용한다.
언두 로그는 사용하는 트랜잭션이 모두 종료되지 않는 한 제거되지 않으므로,
가능한 트랜잭션을 빨리 완료해야 한다.
그렇지 않으면 언두 로그가 쌓여 MySQL 서버가 느려지거나 오류가 발생할 수 있다.
✔️ Deadlock Detection
데드락(Deadlock)은 교착상태라고도 하며,
서로 다른 트랜잭션이 서로에게 필요한 잠금을 보유하고 있기 때문에 무한히 대기하는 상황이다.
한 트랜잭션이 여러 테이블을 잠궜을 때 다른 트랜잭션에서 반대 순서로 테이블을 잠그는 경우 발생한다.
(테이블은 SELECT ~ FOR UPDATE, UPDATE 등을 수행하면 잠긴다)
# 트랜잭션이 잠금을 기다리고 있는지 여부,
# 트랜잭션이 언제 시작되었는지,
# 트랜잭션이 실행 중인 SQL 문(있는 경우)을 포함하여
# 현재 InnoDB 내에서 실행 중인 모든 트랜잭션에 대한 정보를 제공
# https://dev.mysql.com/doc/refman/8.0/en/information-schema-innodb-trx-table.html
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX\G
# 세션이나 트랜잭션이 대기 중인 잠금,
# 해당 잠금을 보유하고 있는 세션이나 트랜잭션에 대한 정보가 표시된다.
# https://dev.mysql.com/doc/refman/8.0/en/performance-schema-data-lock-waits-table.html
SELECT * FROM performance_schema.data_locks\G
# 락 대기 요청 목록 확인
# https://dev.mysql.com/doc/refman/8.0/en/performance-schema-data-locks-table.html
SELECT * FROM performance_schema.data_lock_waits\G
InnoDB는 잠금이 데드락에 빠지지 않았는지 체크하기 위해 잠금 대기 목록을 그래프(wait-for list) 형태로 관리한다.
데드락 감지는 데드락 감지 스레드에 의해 수행되며,
데드락이 감지된 경우 데드락에 빠진 트랜잭션 중 하나를 강제로 롤백시킨다.
이때 롤백되는 트랜잭션은 일반적으로 크기가 작은 트랜잭션으로,
가진 언두 로그의 양이 적은 트랜잭션이 대상이 된다.
언두 로그의 양이 적다는 것은 롤백했을 때 처리할 데이터가 적다는 것이며, 이는 부하를 적게 유발한다.
innodb_table_locks = 1(기본값), autocommit = 0인 경우,
InnoDB는 테이블 잠금(LOCK TABLES 명령으로 잠긴 테이블)을 인식할 수 있다.
innodb_table_locks을 0으로 설정하면,
테이블 잠금 또는 InnoDB가 아닌 다른 스토리지 엔진에서 설정한 잠금과 관련된 교착 상태를 InnoDB가 감지할 수 없다.
별 다른 이유가 없다면 기본값을 그대로 사용하자.
동시 처리 작업이 매우 많거나 트랜잭션의 가진 잠금의 개수가 많아지는 경우,
데드락 감지 스레드가 느려지고 이로 인해 쿼리를 처리하는 스레드가 영향을 받게 된다.
이런 경우에는 데드락 감지 스레드를 비활성화 하는 것이 권장된다.
데드락 감지 비활성화는 innodb_deadlock_detect 옵션으로 설정할 수 있다.
데드락 감지가 비활성화되면 데드락에 걸렸을 때 innodb_lock_wait_timeout의 시간만큼 대기하게 된다.
해당 설정의 기본 값은 기본 값은 50초이지만,
데드락 감지를 비활성화한 경우 많이 낮춰서 사용하는 것을 권장한다.
✔️ 자동화된 장애 복구
InnoDB에는 장애 시 데이터를 보호하기 위한 여러 장치가 있다.
MySQL 서버 시작 시 완료되지 못한 트랜잭션, 디스크에 일부만 기록된(Partial write) 데이터 페이지 등에 대한 복구 작업을 진행한다.
만약 이러한 복구 작업에 실패하면 MySQL은 곧바로 서버를 종료하게 된다.
이런 경우에는 innodb_force_recovery 옵션을 설정한 뒤, 서버를 실행해야 한다.
이에 대한 자세한 내용은 공식 문서를 참고하자.
참고
- Real MySQL 8.0
- MySQL
- MySQL Undo Log (Undo Tablespace) 알아보기
'Database' 카테고리의 다른 글
InnoDB 알아보기 (In-Memory Structures - Log Buffer) (0) | 2024.01.31 |
---|---|
InnoDB 알아보기 (In-Memory Structures - Adaptive Hash Index) (1) | 2024.01.31 |
InnoDB 알아보기 (In-Memory Structures - Change Buffer) (0) | 2024.01.29 |
InnoDB 알아보기 (In-Memory Structures - Buffer Pool) (0) | 2024.01.29 |
MySQL 아키텍처 알아보기 (0) | 2024.01.29 |