InnoDB는 MySQL에서 사용할 수 있는 스토리지 엔진 중 거의 유일하게 레코드 기반의 잠금을 제공하며, 그 때문에 높은 동시성 처리가 가능하고 안정적아며 성능이 뛰어나다.
※ 레코드 기반 잠금이란?
레코드 기반 잠금은 데이터베이스의 특정 레코드(행)에 대한 잠금을 수행하는 방식이다. InnoDB에서는 각 레코드를 잠그는 대신, 데이터 페이지(page)를 잠그는 페이지 수준 잠금(page-level locking)과는 다른 방식을 사용한다.
레코드 기반 잠금은 다음과 같은 특징을 갖는다.
- 세밀한 잠금 : InnoDB는 트랜잭션 내에서 필요한 레코드만을 잠그므로 다른 트랜잭션은 동일한 테이블에서 다른 레코드를 조작할 수 있다. 이는 동시성을 향상시키고 락 경합(lock contention)을 감소시킨다.
- 다중 버전 동시성 제어(MVCC) : 레코드 기반 잠금은 InnoDB의 다중 버전 동시성 제어(MVCC) 메커니즘과 연결된다. MVCC는 트랜잭션 간에 일관된 일기(read consistency)를 제공하고 동시성을 높이기 위해 각 트랜잭션에서 보는 데이터의 버전을 추적한다.
- 잠금의 범위 : 레코드 기반 잠금은 특정 행 또는 인덱스 레코드에 대해 세밀한 잠금을 제공한다. 이를 통해 다른 트랜잭션 간의 간섭을 최소화하고 동시성을 높일 수 있다.
아래에서부터는 InnoDB 스토리지 엔진의 주요 특징들을 하나 씩 살펴본다.
▶ 프라이머리 키에 의한 클러스터링
- InnoDB의 모든 테이블은 기본적으로 프라이머리 키를 기준으로 클러스터링 되어 저장된다. 즉, 프라이머리 키 값의 순서대로 디스크에 저장된다는 뜻이며, 모든 세컨더리 인덱스는 레코드의 주소 대신 프라이머리 키의 값을 논리적인 주소로 사용한다.
- 프라이머리 키가 클러스터링 인덱스이기 때문에 프라이머리 키를 이용한 레인지 스캔은 상당히 빨리 처리될 수 있다.
- InnoDB 스토리지 엔진과는 달리 MyISAM 스토리지 엔진에서는 클러스터링 키를 지원하지 않는다.
▶ 외래 키 지원
- 외래 키에 대한 지원은 InnoDB 스토리지 엔진 레벨에서 지원하는 기능으로 MyISAM이나 MEMORY 테이블에서는 사용할 수 없다.
- InnoDB에서 외래 키는 부모 테이블과 자식 테이블 모두 해당 칼럼에 인덱스 생성이 필요하고, 변경 시에는 반드시 부모 테이블이나 자식 테이블에 데이터가 있는지 체크하는 작업이 필요하므로 잠금이 여러 테이블로 전파되고, 그로 인해 데드락이 발생할 때가 많으므로 개발할 때도 외래 키의 존재에 주의하는 것이 좋다.
▶ MVCC (Multi Version Concurrency Control)
- 일반적으로 레코드 레벨의 트랜잭션을 지원하는 DBMS가 제공하는 기능ㅇ며, MVCC의 가장 큰 목적은 잠금을 사용하지 않는 일관된 읽기를 제공하는 데 있다.
- InnoDB는 언두 로그(Undo log)를 이용해 이 기능을 구현한다.
- 여기서 멀티 버전이라 함은 하나의 레코드에 대해 여러 개의 버전이 동시에 관리된다는 의미이다.
→ 이해를 위해 격리 수준(Isolation level)이 READ_COMMITED인 MySQL 서버에서 InnoDB 스토리지 엔진을 사용하는 테이블의 데이터 변경을 어떻게 처리하는지 살펴본다.
- Update 문장이 실행되면 커밋 실행 여부와 관계없이 InnoDB의 버퍼 풀은 새로운 값인 '경기'로 업데이트된다.
- 그리고 디스크의 데이터 파일에는 체크포인트나 InnoDB의 Write 스레드에 의해 새로운 값으로 업데이트돼 있을 수도 있고 아닐 수도 있다.
- 아직 COMMIT이나 ROLLBACK이 되지 않은 상태에서 다른 사용자가 다음 같은 쿼리로 레코드를 조회하면 어디에 있는 데이터를 조회할가?
mysql > SELECT * FROM member WHERE m_id=12;
- 이 질문에 대한 답은 MySQL 서버의 시스템 변수(transaction_isolation)에 설정된 격리 수준(Isolation level)에 따라 다르다.
- 격리 수준이 READ_UNCOMMITTED인 경우에는 InnoDB 버퍼 풀이 현재 가지고 있는 변경된 데이터를 읽어서 반환한다. 즉, 데이터가 커밋됐든 아니든 변경된 상태의 데이터를 반환한다.
- READ_COMMITED나 그 이상의 격리 수준(REPEATABLE_READ, SERIALIZABLE)인 경우에는 아직 커밋되지 않았기 때문에 InnoDB 버퍼 풀이나 데이터 파일에 있는 내용 대신 변경되기 이전의 내용을 보관하고 있는 언두 영역의 데이터를 반환한다.
위 같은 과정을 DBMS에서는 MVCC라고 표현한다. 즉, 하나의 레코드에 대해 2개의 버전이 유지되고, 필요에 따라 어느 데이터가 보여지는지 여러 가지 상황에 따라 달라지는 구조다.
커밋이 된다고 언두 영역의 백업 데이터가 항상 바로 삭제되는 것은 아니다. 이 언두 영역을 필요로 하는 트랜잭션이 더는 없을 때 비로소 삭제된다.
▶ 잠금 없는 일관된 읽기 (Non-Locking Consistent Read)
- InnoDB 스토리지 엔진은 MVCC 기술을 이용해 잠금을 걸지 않고 읽기 작업을 수행한다.
- 잠금을 걸지 않기 때문에 InnoDB에서 읽기 작업은 다른 트랜잭션이 가지고 있는 잠금을 기다리지 않고, 읽기 작업이 가능하다.
위 그림에서 특정 사용자가 레코드를 변경하고 아직 커밋을 수행하지 않았다 하더라도 이 변경 트랜잭션이 다른 사용자의 SELECT 작업을 방해하지 않는다. 이를 '잠금 없는 일관된 읽기'라고 표현하며, InnoDB에서는 변경되기 전의 데이터를 읽기 위해 언두 로그를 사용한다.
▶ 자동 데드락 감지
- InnoDB 스토리지 엔진은 내부적으로 잠금이 교착 상태에 빠지지 않았는지 체크하기 위해 잠금 대기 목록을 그래프(Wait-for List) 형태로 관리한다.
- InnoDB 스토리지 엔진은 데드락 감지 스레드를 가지고 있어서 데드락 감지 스레드가 주기적으로 잠금 대기 그래프를 검사해 교착 상태에 빠진 트랜잭션들을 찾아서 그 중 하나를 강제 종료한다.
- 이때 어느 트랜잭션을 먼저 강제 종료할 것인지를 판단하는 기준은 트랜잭션의 언두 로그 양이며, 언두 로그 레코드를 더 적게 가진 트랜잭션이 일반적으로 롤백의 대상이 된다.
- 그 이유는 트랜잭션이 언두 레코드를 적게 가졌다는 것이 롤백을 해도 언두 처리를 해야할 내용이 적다는 것이며, 트랜잭션 강제 롤백으로 인한 MySQL 서버의 부하도 덜 유발하기 때문이다.
- 데드락 감지 스레드가 느려지면 서비스 쿼리를 처리 중인 스레드는 더는 작업을 진행하지 못하고 대기하면서 서비스에 악영향을 미치게 된다.
- 데드락 감지 스레드는 동시성 처리의 개수에 따라 영향을 맞기 때문에 매우 높은 동시성 처리를 요구하는 서비스가 있다면 'innodb_deadlock_detect'를 비활성화해서 성능 비교를 해보는 것이 좋다.
▶ 자동화된 장애 복구
- InnoDB 스토리지 엔진에서 데이터 파일이 손상되거나 MySQL 서버가 시작되지 못하는 경우는 거의 없지만 한 번 문제가 발생하면 복구하기 쉽지 않다.
- InnoDB 데이터 파일은 기본적으로 MySQL 서버가 시작될 때 항상 자동 복구를 수행한다. 이 단계에서 자동으로 복구될 수 없는 손상이 있다면 자동 복구를 멈추고 MySQL 서버는 종료돼버린다.
- 이때는 MySQL 서버의 설정 파일에 innodb_force_recovery 시스템 변수를 설정해서 MySQL 서버를 시작해야한다. 이 설정값은 MySQL 서버가 시작될 때 InnoDB 스토리지 엔진이 데이터 파일이나 로그파일의 손상 여부 검사 과정을 선별적으로 진핼할 수 있게 한다.
- InnoDB의 로그 파일이 손상됐다면 6으로 설정하고 MySQL 서버를 기동한다.
- InnoDB 테이블의 데이터 파일이 손상됐다면 1로 설정하고 MySQL 서버를 기동한다.
- 어떤 부분이 문제인지 알 수 없다면 innodb_force_recovery 설정값을 1부터 6으로 변경하면서 MySQL을 재시작해본다. 즉, innod _forece_recovery 설정값을 1로 설정한 후 MySQL 서버를 재시작해보고, MySQL이 시작되지 않으면 다시 2로 설정하고 재시작해보는 방식이다.
- innodb_force_recovery 값이 커질 수록 그만큼 심각한 상황이어서 데이터 손실 가능성이 커지고 복구 가능성은 적어진다.
- innodb_force_recovery 옵션에 설정 가능한 값은 1~6이며 자세한 내용은 생략한다.
'CS > MySQL' 카테고리의 다른 글
MySQL InnoDB 스토리지 엔진 잠금과 MySQL 격리 수준 (0) | 2023.07.02 |
---|---|
MySQL 트랜잭션이란? MySQL 잠금(Lock) (0) | 2023.07.01 |
MySQL 쿼리 실행 구조 / MySQL 복제 / 트랜잭션 지원 메타 데이터(MySQL 5.7 vs 8.0) (0) | 2023.06.23 |
MySQL 엔진 아키텍처 / 스레딩 구조 / 메모리 할당 및 구조 / 컴포넌트 (0) | 2023.06.23 |
MySQL 시스템 변수 특징 / 글로벌 변수와 세션 변수 / 정적 변수와 동적 변수 / SET PERSIST (0) | 2023.06.21 |