- 레코드 기반의 잠금을 제공
- 높은 동시성 처리가 가능하고 안정적이며 성능이 뛰어나다
프라이머리 키 클러스터링 인덱스
- InnoDB의 모든 테이블은 프라이머리 키를 기준으로 클러스터링되어 저장된다.
- 클러스터링 인덱스: 데이터가 물리적으로 인덱스 키 값 순서대로 저장되는 구조
- 모든 세컨더리 인덱스는 레코드의 주소 대신 프라이머리 키의 값을 논리적인 주소로 사용한다.
- 세컨더리 인덱스: 프라이머리 키가 아닌 다른 컬럼에 생성하는 인덱스
- 프라이머리 키가 클러스터링 인덱스이기 때문에 범위 검색, 정렬이 빠르다.
- 쿼리 옵티마이저는 실행 계획 수립 시 다른 보조 인덱스보다 프라이머리 키를 우선적으로 고려한다.
외래 키 지원
- 외래 키 지원은 InnoDB 스토리지 엔진 레벨에서 지원하는 기능
- 변경 시 부모 테이블이나 자식 테이블에 데이터가 있는지 체크하는 작업이 필요
→ 잠금이 여러 테이블로 전파 된다.
→ 데드락 발생하는 경우가 많다.
- 수동으로 데이터를 다루는 경우 주의
- foreign_key_checks 변수를 off로 설정하면 외래 키 관계에 대한 체크 작업을 일시적으로 멈출 수 있다.
→ off 로 설정하면 cascade 옵션도 무시
MVCC(Multi Version Concurrency Control)
- 레코드 레벨의 트랜색션을 지원하는 DBMS가 제공하는 기능
- 잠금을 사용하지 않는 일관된 읽기를 제공
- 언두 로그를 이용해 기능을 구현
- READ_UNCOMMITTED
- READ_COMMITTED 이상의 격리 수준 (REPEATABLE_READ, SERIALIZABLE)
- MVCC: 하나의 레코드에 대해 2개의 버전이 유지되고, 상황에 따라 다른 데이터를 반환한다.
- 트랜잭션이 길어지면 언두에서 관리하는 데이터가 삭제되지 못한다.
- 커밋을 실행하면 데이터의 현재 상태를 영구 데이터로 만든다
- 언두 영영의 데이터가 바로 삭제되진 않고, 언두 영역을 필요로 하는 트랜잭션이 없을 때 삭제된다.
- 롤백을 실행하면 언두 영역에 있는 데이터를 버퍼 풀로 복구하고 언두 영역의 내용이 삭제된다.
언두 로그
- 트랜잭션과 격리 수준을 보장하기 위해 DML로 변경되기 이전의 데이터를 별도로 백업한 데이터
- 트랜잭션 보장
- 트랜잭션이 롤백되면 데이터를 변경 전 데이터로 복구한다.
- 격리 수준 보장
- 특정 커넥션에서 데이터를 변경하는 도중 다른 커넥션에서 데이터를 조회하면 트랜잭션 격리 수준에 맞게 변경 중인 레코드를 읽지 않고 언두 로그에 백업해둔 데이터를 읽어서 반환
잠금(lock)
사용자나 애플리케이션이 동시에 데이터베이스에 접근할 때 데이터 일관성과 무결성을 보호하기 위한 메커니즘
- 동시성 제어: 여러 트랜잭션이 동시에 같은 데이터에 접근할 때 충돌을 방지
- 배타적 접근: 특정 데이터를 한 트랜잭션이 수정하는 동안 다른 트랜잭션의 접근을 제한
- 공유 잠금(Shared Lock)
- 다른 트랜잭션의 읽기는 허용, 쓰기는 차단
- 여러 트랜잭션이 동시에 같은 데이터의 공유 잠금을 획득할 수 있지만, 배타적 잠금은 획득할 수 없다.
- 배타적 잠금(Exclusive Lock)
- 트랜잭션이 데이터에 대해 배타적 잠금을 획득하면 다른 트랜잭션은 해당 데이터에 대한 어떤 유형의 잠금도 획득할 수 없다.
잠금 없는 일관된 읽기
- 격리 수준이 SERIALIZABLE 이 아닌 경우 순수한 읽기 작업은 다른 트랜잭션의 변경 작업과 관계없이 항상 잠금을 대기하지 않고 바로 실행된다.
- 변경 전의 데이터를 읽기 위해 언두 로그를 사용
- 트랜잭션이 오랜 시간 활성 상태로 있는 경우 서버가 느려지거나 문제가 발생할 수 있다
- 일관된 읽기를 위해 언두 로그를 삭제하지 못하고 유지하는 경우 발생
- 트랜잭션은 가능한 빨리 롤백 또는 커밋으로 완료하는 것이 좋다.
자동 데드락 감지
- 데드락: 두 개 이상의 트랜잭션이 서로 보유한 잠금(lock)을 기다리며 무한정 대기하는 상태
- 잠금이 교착 상태에 빠지지 않았는지 체크하기 위해 잠금 대기 목록을 그래프 형태로 관리한다.
- 데드락 감지 스레드를 가지고 있어 주기적으로 잠금 대기 그래프를 검사해 교착 상태에 빠진 트랜잭션들을 찾아서 하나를 강제 종료한다.
- 강제 종료(롤백)의 판단 기준은 언두 로그 레코드를 더 적게 가진 트랜잭션을 우선으로 삭제한다.
- 테이블 잠금은 스토리지 엔진의 상위 레이어인 MySQL 엔진에서 관리한다.
- innodb_table_locks 변수를 활성화해 테이블 레벨의 잠금도 감지할 수 있다. (권장)
- 동시 처리가 스레드나 트랜잭션이 가진 잠금의 개수가 많아지면 데드락 감지 스레드가 느려진다. → 서비스 쿼리를 처리 중인 스레드는 작업을 진행하지 못하고 대기 → innodb_deadlock_detect 변수를 OFF 로 설정하면 데드락 감지 스레드 작동 중지할 수 있다.
- 2개 이상의 트랜잭션이 상대방이 가진 잠금을 요구하는 경우 데드락 상태가 된다.
- innodb_lock_wait_timeout (기본 값, 50초) 변수를 활성화 해서 데드락 상황에서 일정 시간이 지나면 자동으로 요청이 실패하도록 할 수 있다.
- innodb_deadlock_detect를 OFF 하는 경우 기본값 보다 훨씬 낮은 시간으로 변경하는 것을 권장
자동화된 장애 복구
- InnoDB 데이터 파일은 기본적으로 MySQL 서버가 시작될 때 항상 자동 복구를 수행한다.
- 자동 복구될 수 없는 손상이 있다면 MySQL 서버는 종료된다.
- innodb_force_recovery 변수를 설정해서 MySQL 서버를 구동 시켜 보면서 복구한다.
- 1 ~ 6, 값이 커질 수록 심각한 상황
InnoDB 버퍼 풀
- 데이터베이스가 디스크 I/O 작업을 최소화하기 위해 사용하는 메모리 영역
- 캐싱: 디스크의 데이터 파일이나 인덱스 정보를 메모리에 임시 저장
- 쓰기 버퍼링: 쓰기 작업을 지연시켜 일괄 작업으로 처리
- innodb_buffer_pool_size: 버퍼 풀의 크기를 설정하는 변수
- 동적으로 버퍼 풀의 크기 확장 가능
- 크기 변경 작업은 한가한 시점에, 크기를 줄이는 작업은 지양
- 내부적으로 128MB 청크 단위로 쪼개어 관리 → 버퍼 풀의 크기를 줄이거나 늘리기 위한 단위 크기로 사용
버퍼 풀 구조
- 페이지 크기의 조각으로 쪼개 데이터를 필요로 할 때 해당 데이터 페이지를 읽어서 각 조각에 저장한다.
- 페이지 크기 조각을 관리하기 위해 3개의 자료 구조를 관리한다.
- LRU(Least Recently Used) 리스트
- LRU와 MRU(More Recently Used) 리스트가 결합된 형태
- 한 번 읽어온 페이지를 최대한 오래 버퍼풀의 메모리에 유지해 디스크 읽기를 최소화
- 사용 빈도가 낮은 데이터 페이지들을 제거해서 새로운 페이지들을 읽어올 공간을 만드는 작업을 수행
- 플러시 리스트
- 디스크로 동기화되지 않은 데이터를 가진 페이지(더티 페이지)의 목록을 관리
- 오래전에 변경된 데이터 페이지 순서대로 디스크에 동기화 하는 작업을 수행
- 데이터의 변경 내용을 리두 로그에 기록하고 버퍼 풀의 데이터 페이지에도 반영한다.
- 리두 로그의 각 엔트리는 특정 데이터 페이지와 연결된다.
- 리두 공간이 지워지려면 InnoDB 버퍼 풀의 더티 페이지가 먼저 디스크로 동기화 돼야 함
- 스토리지 엔진이 주기적으로 호출
- 프리 리스트
- InnoDB 버퍼 풀에서 실제 사용자 데이터로 채워지지 않은 비어 있는 페이지들의 목록
- 쿼리가 새롭게 디스크의 데이터 페이지를 읽어와야 하는 경우 사용
- 클린 페이지: 디스크에서 읽어온 상태의 페이지, 변경되지 않은 상태
- 더티 페이지: 변경된 데이터를 가진 페이지, 디스크에 기록되어야 한다.
버퍼 풀과 리두 로그
- 리두 로그는 영속성과 밀접한 연관관계
- 서버가 비정상적으로 종료됐을 때 기록되지 못한 데이터를 잃지 않게 해주는 안전장치
- 데이터 변경 내용을 로그에 먼저 기록
- 대부분의 DBMS는 쓰기 보다 읽기 성능을 고려한 자료 구조를 가지고 있음
- 쓰기는 디스크의 랜덤 액세스가 필요 → 상대적으로 큰 비용 발생 → 쓰기 비용이 낮은 리두 로그를 사용
- 여러 개의 고정 크기 파일로 구성
- 파일을 순차적으로 순회하면서 사용한다.
[redo01.log] → [redo02.log] → [redo03.log] → [redo04.log]
↑ |
└──────────────────────────────────────────────┘
- 활성 리두 공간: 디스크에 반영되지 않은 변경사항을 담고 있는 공간(당장 재사용이 불가능)
- LSN(Log Sequence Number)
- 각 로그 엔트리에 부여되는 고유 일련번호
- 변경 사항의 순서를 추적
- 주기적으로 체크포인트 이벤트를 발생시켜 리두 로그와 버퍼 풀의 더티 페이지를 디스크로 동기화한다.
- 가장 최근 체크포인트 지점의 LSN이 활성 리두 공간의 시작점이된다.
- 체크포인트 에이지: 가장 최근 체크포인트의 LSN과 마지막 리두 로그 엔트리의 LSN의 차이
→ 활성 리두 공간의 크기
상태 백업 및 복구
- 워밍업: 데이터가 버퍼 풀에 적재돼 있는 상태 → 쿼리 성능이 빨라짐(캐싱)
- 5.6 버전부터 버퍼 풀 덤프 및 적재 기능 도입으로 버퍼 풀의 상태를 백업할 수 있게 됨
- 백업은 메타 정보만 저장하기 때문에 매우 빠르다.
- 적재 과정은 InnoDB 버퍼 풀의 크기에 따라 시간이 다소 소요될 수 있다.
체인지 버퍼
- 인덱스를 업데이트 해야하는 경우 사용하는 임시 메모리 공간
어댑티드 해시 인덱스
- 사용자가 자주 요청하는 데이터에 대해 자동으로 생성하는 인덱스
- B-Tree의 검색 시간을 줄이기 위해 도입되었다.
- 자주 읽히는 데이터 페이지의 키 값을 이용해 해시 인덱스를 만든다.
- 키: 인덱스 ID
- 값: 데이터 페이지 주소, 키에 해당하는 데이터가 저장된 버퍼 풀 내 메모리 페이지 위치
- 버퍼 풀에 로딩된 데이터 페이지에 대해서만 적용된다.