foreign_key_checks
시스템 변수를 OFF로 설정하면 외래 키 관계에 대한 체크 작업을 일시적으로 멈출 수 있다.ON DELETE CASCADE
와 UPDATE CASCADE
옵션)도 무시하게 된다.예) 2명이서 동시에 신청 했을 때, 1명만 되야 한다.
이런 상황에서 DBMS가 사용하는 공통적인 방법이 잠금(lock)이다.
- 정리하자면, 잠금은 트랜잭션 처리의 순차성을 보장하기 위한 방법이다.
Undo log
는 단일 트랜잭션과 관련된 Undo log 레코드의 집합
Undo log
레코드에는 클러스터드 인덱스 레코드의 트랜잭션에 의한 제일 최근의 변경사항의 변경 전 데이터에 대한 정보를 포함하고 있다.
- 만약 다른 트랜잭션이 변경 전 데이터를 읽기를 원한다면 undo log 레코드에서 변경 전 데이터를 읽는다.
Update
쿼리 실행 중 아직 COMMIT
이나 ROLLBACK
이 되지 않은 상태에서 SELECT
조회 시 어디에 있는 데이터를 조회 할까? (버퍼 풀, 언두 로그, 데이터 파일(디스크))
MySQL 서버의 시스템 변수(transaction_isolation
)에 설정된 격리 수준(Isolation level
)에 따라 다르다.
격리 수준이 READ_UNCOMMITTED
인 경우에는 InnoDB 버퍼 풀이 현재 가지고 있는 변경된 데이터를 읽어서 반환한다.
READ_COMMITED
, REPEATABLE_READ
, SERIALIZABLE
인 경우에는 언두 영역의 데이터를 반환
이러한 과정을 DBMS에서는 MVCC라고 표현한다.
InnoDB 스토리지 엔진은 MVVCC 기술을 이용해 잠금을 걸지 않고 읽기 작업을 수행한다.
다른 트랙잭션이 가지고 있는 잠금을 기다리지 않고 읽기 작업이 가능
특정 사용자가 레코드를 변경하고 아직 커밋을 수행하지 않았다 하더라도 다른 사용자의 SELECT
작업을 방해하지 않는다. 이를 잠금없는 일관된 읽기 라고 표현
InnoDB에서는 변경되기 전의 데이터를 읽기 위해 언두로그를 사용한다.
데드락 감지 스레드 : 교착상태에 빠진 트랜잭션들을 찾아서 그중 하나를 강제 종료
언두 로그 레코드를 적게가진 트랜잭션이 일반적으로 롤백의 대상
InnoDB 데이터 파일은 기본적으로 MySQL 서버가 시작될 때 항상 자동 복구를 수행한다. 자동으로 복구될 수 없는 손상이 있다면 자도 복구를 멈추고 MySQL 서버는 종료돼 버린다.
MySQL 서버의 설정 파일에 innodb_force_recovery
시스템 변수를 설정해서 MySQL 서버를 시작해야 한다.
어떤 부분이 문제인지 알 수 없다면 설정값을 1~6까지 변경하면서 MySQL을 재시작해 본다.
값이 커질수록 그만큼 심각한 상황
디스크의 데이터 파일이나 인덱스 정보를 메모리에 캐시해 두는 공간
쓰기 작업을 지연시켜 일괄 작업으로 처리할 수 있게 해주는 버퍼 역할도 같이 한다.
LRU(Least Recently Used), 플러시(Flush), 프리(Free) 리스트라는 3개의 자료 구조를 관리
프리 리스트는 실제 사용자 데이터로 채워지지 않은 비어 있는 페이지들의 목록, 사용자의 쿼리가 새롭게 디스크의 데이터 페이지를 읽어와야 하는 경우 사용된다.
LRU 리스트를 관리하는 목적은 디스크로부터 한 번 읽어온 페이지를 최대한 오랫동안 InnoDB 버퍼풀의 메모리에 유지해서 디스크 읽기를 최소화 하는 것
버퍼 풀에 상주하는 데이터 페이지는 사용자 쿼리가 얼마나 최근에 접근했었는지에 따라 나이(Age)가 부여되며, 버퍼 풀에 상주하는 동안 쿼리에서 오랫동안 사용되지 않으면 데이터 페이지에 부여된 나이가 오래되고(Aging) 결국 해당 페이지는 버퍼 풀에서 제거된다.
버퍼 풀의 데이터 페이지가 쿼리에 의해 사용되면 나이가 초기화 되어 다시 젊어지고 MRU의 헤더 부분으로 옮겨진다.
플러시 리스트는 디스크로 동기화되지 않은 데이터를 가진 데이터 페이지(더티 페이지)의 변경 시점 기준의 페이지 목록을 관리한다.
버퍼 풀은 디스크에서 읽은 상태로 전혀 변경되지 않은 클린페이지
와 함께 INSERT, UPDATE, DELETE 명령으로 변경된 데이터를 가진 더티페이지
도 가지고 있다.
데이터 변경이 계속 바생하면 리두 로그 파일에 기록됐던 로그 엔트리는 어느 순간 다시 새로운 로그 엔트리로 덮어 쓰인다.
InnoDB 엔진은 전체 리두 로그 파일에서 재사용 가능한 공간과 당장 재사용 불가능한 공간을 구분해서 관리한다.
재사용 불가능한 공간을 활성 리두 로그
라고 한다.
버퍼풀에서 아직 디스크로 기록되지 않은 더티 페이지들을 성능상의 악영향 없이 디스크에 동기화하기 위해 2개의 플러시 기능을 백그라운드로 실행한다.
리두 로그 공간의 낭비를 막기 위해 페이지의 변경된 내용만 기록한다. 더티 페이지를 디스크 파일로 플러시할 때 일부만 기록되는 문제가 발생하면 그 페이지의 내용은 복구할 수 없을 수도 있다. 이 문제를 막기 위해 InnoDB 스토리지 엔진은 Double-write
기법을 사용한다.
Double Write Buffer
실제 데이터 파일에 변경 내용을 기록하기 전에 더티 페이지를 묶어서 한 번의 디스크 쓰기로 시스템 테이블스페이스의 DoubleWrite
버퍼에 기록한다.
DoubleWrite
버퍼의 내용은 실제 데이터 파일의 쓰기가 중간에 실패할 때만 원래 목적으로 사용된다. 재시작될 때 항상 DoubleWrite
버퍼의 내용과 데이터 파일의 페이지들을 모두 비교해서 다른 내용을 담고 있는 페이지가 있으면 DoubleWrite
버퍼의 내용을 데이터 파일의 페이지로 복사한다. 데이터의 무결성이 매우 중요한 서비스에서는 DoubleWrite
를 사용하면 좋다.
트랜잭션과 격리 수준을 보장하기 위해 DML로 변경되기 이전의 데이터를 별도로 백업하는데, 이를 언두 로그라고 한다.
트랜잭션 보장
격리 수준 보장
mysql> UPDATE member SET name='홍길동' WHERE member_id=1;
위 문장이 실행되면 트랜잭션을 커밋하지 않아도 실제 데이터에 내용이 반영된다. 만약 변경되기 전의 값이 벽계수
였다면 언두 영역에는 벽계수
라는 값이 백업되는 것이다.
이상태에서 사용자가 커밋을 하면 현재 상태가 그대로 유지되고, 롤백을 하면 언두 영역에 저장된 데이터를 다시 복구한다.
언두 로그의 데이터는 두 가지 용도로 사용
언두 로그의 문제점
트랜잭션 B, C는 각각 UPDATE
, DELETE
를 실행했으므로 변경 이전의 데이터를 언두 로그에 백업했을 것이다.
하지만 먼저 시작된 A 트랜잭션이 아직 활성 상태이기 때문에 B, C 트랜잭션의 완료 여부와 관계없이 B, C 트랜잭션이 만들어낸 언두 로그는 삭제되지 않는다.
이렇게 누적된 언두 로그로 인해 디스크의 사용량이 증가하는 것은 상대적으로 큰 문제가 아니다.
하지만 그동안 빈번하게 변경되는 레코드를 조회하는 쿼리가 자주 실행되면 InnoDB 스토리지 엔진은 언두 로그의 이력을 필요한 만큼 스캔해야만 필요한 레코드를 찾을 수 있기 때문에 쿼리의 성능이 급격히 저하된다.
MySQL 8.0 부터는 언두 로그를 돌아가면서 순차적으로 사용하는 방식으로 디스크 공간을 줄이는 것을 가능하게 했으며, 때로는 MySQL 서버가 필요한 시점에 사용 공간을 자동으로 줄여주기도 한다.
하지만 여전히 서비스 중인 MySQL 서버에서 활성 상태의 트랜잭션이 장시간 유지되는 것은 성능상 좋지 않다.
서버 별로 안정적인 시점의 언두 로그 건수를 확인해 이를 기준으로 언두 로그의 급증 여부를 모니터링 하는 것이 좋다.
언두 로그가 저장되는 공간을 언두 테이블스페이스(Undo TableSpace)라고 한다.
레코드가 INSERT 되거나 UPDATE 될 때는 데이터 파일을 변경하는 작업뿐만 아니라 해당 테이블에 포함된 인덱스를 업데이트하는 작업 또한 필요하다.
변경해야 하는 인덱스 페이지가 버퍼 풀에 있으면 바로 업데이트를 수행하지만 디스크에서 읽어와야 한다면 즉시 실행하지 않고 임시 공간에 저장해두고 바로 사용자에게 결과를 반환하는 형태를 통해 성능을 향상 시키는데, 이 때 사용하는 임시 메모리를 체인지 버퍼(Change Buffer)라 한다.
사용자에게 결과를 전달하기 전에 반드시 중복 여부를 체크해야 하는 유니크 인덱스의 경우는 체인지 버퍼를 사용할 수 없다.
리두 로그(Rego Log)는 트랜잭션의 4가지 요소인 ACID 중에서 D(Durable)에 해당하는 영속성과 가장 밀접하게 연관되어 있다.
리두 로그는 하드웨어나 소프트웨어 등 여러 가지 문제점으로 인해 MySQL 서버가 비정상적으로 종료되었을 때 데이터 파일에 기록되지 못한 데이터를 잃지 않게 해주는 안정장치이다.
MySQL 서버가 비정상적으로 종료되는 경우 InnoDB 스토리지 엔진의 데이터 파일은 다음 두 가지 종류의 일관되지 않은 데이터를 가질 수 있다.
1번의 경우 리두 로그에 저장된 데이터를 데이터 파일에 다시 복사하기만 하면 된다.
2번의 경우는 리두 로그로는 해결이 불가능하다. 이 때는 변경되기 전 데이터를 가진 언두 로그의 내용을 가져와 데이터 파일에 복사하며 된다.
그렇다고 해서 2번의 경우 리두 로그가 전혀 필요하지 않은 것은 아니다. 최소한 그 변경이 커밋, 롤백 또는 트랜잭션 실행 중 상태였는지를 확인하기 위해서라도 리두 로그가 필요하다.
데이터베이스 서버에서 리두 로그는 트랜잭션이 커밋되면 즉시 디스크로 기록되도록 시스템 변수를 설정하는 것을 권장한다.
적절히 변경된 내용을 버퍼 풀에 모았다가 한 번에 디스크로 기록해야 하므로 리두 로그 파일의 전체 크기가 InnoDB 버퍼 풀의 크기에 맞게 적절히 선택되어야 한다.
하지만 사용량(특히 변경이 잦은 작업)이 매우 많은 DBMS의 경우는 이 리두 로그 기록 작업이 큰 문제가 되는데, 이러한 부분을 보완하기 위해 최대한 ACID 속성을 보장하는 수준에서 버퍼링 한다.
이러한 리두 로그 버퍼링에 사용되는 공간이 로그 버퍼이다. 로그 버퍼의 크기는 데이터를 자주 변경하는 경우에는 크게 설정하는 것이 좋고 일반적인 상황에서는 기본 값 수준에서 설정하는 것이 적합하다.
MySQL 서버에 유입되는 데이터 변경이 너무 많으면 이에 따라 리두 로그가 매우 빠르게 증가하여 새로 추가되는 리두 로그 내용을 복사하기도 전에 덮어쓰일 수 있다.
이렇게 아직 복사하지 못한 리두 로그가 덮어쓰이면 백업 툴이 리두 로그 엔트리를 복사할 수 없어서 백업이 실패하게 된다.
MySQL 8.0의 리두 로그 아카이빙기능은 데이터 변경이 많아서 리두 로그가 덮어쓰인다 하더라도 백업이 실패하지 않게 해준다.
MySQL 서버에서 트랜잭션이 커밋되어도 데이터 파일은 즉시 디스크로 동기화되지 않는 반면, 리두 로그(트랜잭션 로그)는 항상 디스크로 기록된다.
리두 로그를 비활성화하고 필요한 작업(대용량 데이터 적재 등)을 완료했다면 반드시 리두 로그를 재활성화하자.
리두 로그가 비활성화된 상태에서 MySQL 서버가 비정상적으로 종료되면 MySQL 서버의 마지막 체크포인트 이후 시점의 데이터는 모두 복구할 수 없게 되기 때문이다.
사용자가 수동으로 생성하는 인덱스가 아닌 InnoDB 스토리지 엔진에서 사용자가 요청 하는 데이터에 대해 자동으로 생성하는 인덱스이다.
B-Tree 검색 시간을 줄여주기 위해 도입된 기능이다.
어댑티브 해시 인덱스는 데이터 페이지를 메모리(버퍼 풀)내에서 접근하는 것을 더 빠르게 만드는 기능이기 때문에, 데이터 페이지를 디스크에서 읽어오는 경우가 잦은 경우에는 비활성화하는 것이 좋다.
MySQL 8.0 부터는 MySQL 서버의 모든 시스템들이 InnoDB 스토리지 엔진으로 교체되었고 공간 좌표 검색이나 전문 검색 기능들 또한 모두 InnoDB 스토리지 엔진을 지원하도록 개선되었다.
즉, MySQL의 모든 서버 기능들을 InnoDB 스토리지 엔진으로만 구현할 수 있게 된 것이다. 따라서, 추후 MyISAM 스토리지 엔진은 없어질 것으로 예상 된다.