InnoDB 스토리지 엔진
레코드 기반의 잠금을 제공하여 높은 동시성 처리와 안정적이고 뛰어난 성능을 가진다.
프라이머리 키에 의한 클러스터링
InnoDB의 모든 테이블은 기본적으로 프라이머리 키를 기준으로 클러스터링되어 저장된다. 모든 세컨더리 인덱스는 레코드의 주소 대신 프라이머리 키의 값을 논리적인 주소로 사용한다. 프라이머리 키를 이용한 범위 스캔은 굉장히 빨리 처리된다.
외래 키 지원
스토리지 엔진 레벨에서 지원하는 기능. 부모 자식 테이블 모두 해당 컬럼에 인덱스가 생성이 필요하며 변경 시에 부모 테이블이나 자식 테이블에 데이터가 있는 지 체크하는 작업이 필요하여 잠금이 여러 테이블로 전파되고 데드락 발생 가능성이 있기 때문에 외래 키 존재 여부에 주의하는 것이 좋다.
MVCC (Multi Version Concurrency Control)
레코드 레벨의 트랜잭션을 지원하는 DBMS가 제공하는 기능으로 MVCC의 가장 큰 목적은 잠금을 사용하지 않는 일관된 읽기를 제공하는 데 있다. InnoDB는 언두 로그 (Undo Log)를 이용해 이 기능을 제공한다. 멀티 버전의 뜻은 하나의 레코드에 대해 여러 개의 버전이 동시에 관리되는 의미이다.
격리 수준 transaction_isolation
Update 쿼리로 커밋 여부와 상관없이 해당 레코드의 변경이 InnoDB 버퍼 풀에서는 반영되지만 디스크 데이터 파일에는 아직 동기화가 안되어 있을 수 있다. 다른 사용자가 해당 레코드를 읽으면 격리 수준에 따라 업데이트된 내용을 읽을 수도 있고 그 이전 버전의 상태를 읽을 수도 있다.
READ_UNCOMMITTED : InnoDB 버퍼 풀이나 데이터 파일로부터 변경되지 않은 데이터를 읽어서 반환한다. 데이터가 커밋됐든 상관없이 변경된 상태의 데이터를 반환한다.
READ_COMMITED 혹은 그 이상의 격리 수준 (REPEATABLE_READ, SERIALIZABLE) 인 경우 아직 커밋되지 않았기 때문에 InnoDB 버퍼 풀이나 데이터 파일의 내용 대신 변경되기 이전의 내용을 보관하고 있는 언두 영역의 데이터를 반환한다.
Update 쿼리 실행으로 버퍼 풀에 데이터가 변경될 때마다 기존의 데이터가 언두 영역으로 복사된다. COMMIT 명령으로 InnoDB가 변경 작업 없이 지금 상태를 영구 상태로 만든다. 하지만 롤백을 실행하면, InnoDB는 언두 영역에 있는 백업된 데이터를 InnoDB 버퍼 풀로 다시 복구하고 언두 영역의 내용을 삭제한다. 커밋이 된다고 언두 영역의 백업 데이터가 바로 삭제되는 것이 아니고 언두 영역을 필요로 하는 트랜잭션이 더 이상 없을 경우 삭제된다.
잠금 없는 일관된 읽기(Non-Locking Consistent Read)
InnoDB 스토리지 엔진은 MVCC 기술을 이용하여 잠금을 걸지 않고 읽기 작업을 수행한다. 격리 수준이 SERIALIZABLE이 아닌 경우 INSERT와 연결되지 않은 순수한 읽기(SELECT) 작업은 다른 트랜잭션의 변경 작업과 관계없이 항상 잠금을 대기하지 않고 바로 실행된다. 이를 '잠금 없는 일관된 읽기' 라고 표현하며, InnoDB에서는 변경되기 전의 데이터를 읽기 위해 언두 로그를 사용한다.
그림과 같이 특정 사용자가 레코드를 변경하고 아직 커밋을 수행하지 않았다 하더라도 이 변경 트랜잭션이 다른 사용자의 SELECT 작업을 방해하지 않는다.
자동 데드락 감지
InnoDB 스토리지 엔진은 내부적으로 잠금이 교착 상태에 빠지지 않앗는지 체크하기 위해 잠금 대기 목록을 그래프 형태로 관리한다. 데드락을 감지하는 스레드가 따로 있으며 잠금 대기 그래프를 검사해 데드락을 감지하면 교착 상태에 빠진 트랜잭션을 찾아서 하나를 종료 시킨다.
동시 처리 스레드가 매우 많아지거나 트랜잭션이 가진 잠금의 개수가 많아지면 데드락 감지 스레드가 느려진다.
innodb_deadlock_detect : 데드락 감지 기능을 키고 끌수 있다.
innodb_lock_wait_timeout : 잠금을 설정한 시간 동안 획득하지 못하면 쿼리는 실패하고 에러를 반환한다.
자동화된 장애 복구
InnoDB에는 손실이나 장애로부터 데이터를 보호하기 위한 여러 가지 메커니즘이 존재한다. 그러한 메커니즘을 이용하여 MySQL 서버가 시작될 때 완료하지 못한 트랜잭션이나 디스크에 일부만 기록된 데이터 페이지 등에 대한 복구 작업이 자동으로 진행된다.
MySQL 서버와 무관하게 디스크나 서버 하드웨어 이슈로 InnoDB 스토리지 엔진이 자동으로 복구하지 못하는 경우가 발생할 수 있다.
innodb_force_recovery 시스템 변수를 설정하여 MySQL 서버를 시작하면 InnoDB 엔진이 데이터 파일이나 로그 파일의 손상 여부 검사과정을 선별적으로 진행할 수 있게 된다.
InnoDB 로그 파일이 손상되었다면 6으로 설정하고 MySQL 서버를 기동한다.
InnoDB 테이블의 데이터 파일이 손상됐다면 1로 설정하고 MySQL 서버를 기동한다.
어떤 부분이 문제인지 알 수없다면 innodb_force_recovery 설정값을 1부터 6까지 변경하면서 MySQL를 재시작해본다.
MySQL 서버가 기동되고 InnoDB 테이블이 인식된다면 mysqldump를 이용해 데이터를 가능한 만큼 백업하고 그 데이터로 MySQL 서버의 DB 테이블을 다시 생성하는 것이 좋다.
innodb_force_recovery 복구 수준
1(SRV_FORCE_IGNORE_CORRUPT)
InnoDB의 테이블 스페이스 데이터나 인덱스 페이지에서 손상된 부분이 발견돼도 무시하고 MySQL 서버를 시작한다.
2(SRV_FORCE_NO_BACKGROUND)
InnoDB는 쿼리의 처리를 위해 여러 종류의 백그라운드 스레드를 동시에 사용한다. 이 복구 모드에서는 이러한 백그라운드 스레드 가운데 메인 스레드를 시작하지 않고 MySQL 서버를 시작한다.
3(SRV_FORCE_NO_TRX_UNDO)
InnoDB에서 트랜잭션이 실행되면 롤백에 대비해 변경 전의 데이터를 언두 영역에 기록한다. 일반적으로 MySQL 서버는 다시 시작하면서 언두 영역의 데이터를 먼저 데이터 파일에 적용하고 그다음 리두 로그의 내용을 다시 덮어써서 장애 시점의 데이터 상태를 만들어낸다. 정상적인 MySQL 서버의 시작에서는 최종적으로 커밋되지 않은 트랜잭션은 롤백을 수행하지만 해당 복구 모드에서는 커밋되지 않은 트랜잭션의 작업을 롤백하지 않고 그대로 둔다.
4(SRV_FORCE_NO_IBUF_MERGE)
InnoDB는 INSERT, UPDATE, DELETE 등의 데이터 변경으로 인한 인덱스 변경 작업을 상황에 따라 즉시 처리할 수도 있고 인서트 버퍼에 저장해두고 나중에 처리할 수도 있다. 인서트 버퍼에 기록된 내용은 언제 데이터 파일에 병합될지 알 수 없다. MySQL을 종료해도 병합되지 않을 수 있는데, MySQL 서버가 재시작되면서 인서트 버퍼 손상을 감지하면 InnoDB는 에러를 발생시키고 서버는 시작하지 못한다.
해당 복구 모드에서는 InnoDB 스토리지 엔진이 인서트 버퍼의 내용을 무시하고 강제로 MySQL이 시작되게 한다.
5(SRV_FORCE_NO_UNDO_LOG_SCAN)
MySQL 서버가 장애나 정상적으로 종료되는 시점에 진행 중인 트랜잭션이 있었다면 MySQL은 그냥 단순히 그 커넥션을 강제로 끊어 버리고 별도의 정리 작업 없이 종료한다. MySQL 서버가 다시 시작하면 언두 레코드를 이용해 데이터 페이지를 복구하고 리두 로그를 적용해 종료 시점이나 장애 발생 시점의 상태를 재현해 낸다. 그리고 InnoDB는 마지막으로 커밋되지 않은 트랜잭션에서 변경한 작업은 모두 롤백 처리한다. InnoDB의 언두 로그를 사용할 수 없다면 엔진 에러로 서버를 서버를 재시작 할 수없다.
해당 복구 모두에서는 InnoDB 엔진이 언두 로그를 모두 무시하고 MySQL을 재시작한다. 이 복구 모두에서는 서버가 종료되는 시점에 커밋되지 않았던 작업도 모두 커밋된 것처럼 처리되므로 잘못된 데이터가 데이터 베이스에 남게된다.
6(SRV_FORCE_NO_LOG_REDO)
InnoDB 스토리지 엔진의 리두 로그가 손상되면 MySQL 서버가 시작되지 못한다. 이 복구 모드로 시작하면 InnoDB 엔진은 리두 로그를 모두 무시한 채로 MySQL 서버가 시작된다. 커밋됐다 하더라도 리두 로그에만 기록되고 데이터 파일에 기록되지 않은 데이터는 모두 무시된다. 마지막 체크포인트 시점의 데이터만 남게된다.
InnoDB 버퍼 풀
InnoDB 스토리지 엔진의 가장 핵심적인 부분으로 디스크의 데이터 파일이나 인덱스 정보를 메모리에 캐시해 두는 공간이다. 쓰기 작업을 지연시켜 일괄작업으로 처맇라 수 있게 해주는 버퍼 역할을 한다.
InnoDB 버퍼 풀은 innodb_buffer_pool_size 시스템 변수로 크기를 설정할 수 있으며, 동적으로 버퍼 풀의 크기를 확장할 수 있다. 주로 운영체제 전체 메모리 중 50% 이상 정도를 버퍼 풀 크기로 설정한다.
버퍼 풀 전체를 관리하는 잠금으로 인해 내부 잠금 경합을 많이 유발해왔는데, 이런 경합을 줄이기 위해 버퍼 풀을 여러 개로 쪼개어 관리할 수 있게 개선됐다. innodb_buffer_pool_instances 시스템 변수를 이용해 버퍼 풀을 여러 개로 분리해서 관리할 수 있는데, 각 버퍼 풀을 인스턴스라고 표현한다.
InnoDB 버퍼 풀의 구조
InnoDB 스토리지 엔진은 버퍼 풀이라는 거대한 메모리 공간을 페이지 크기(innodb_page_size)의 조각으로 쪼개어 InnoDB 스토리지 엔진이 데이터를 필요로 할 때 해당 데이터 페이지를 읽어서 각 조각에 저장한다.
버퍼 풀의 페이지 크기 조각을 관리하기 위해 스토리지 엔진은 크게 LRU 리스트와 플러시(Flush) 리스트, 그리고 프리(Free) 리스트라는 3개의 자료 구조를 관리한다. 프리 리스트는 버퍼 풀에서 실제 사용자 데이터로 채워지지 않은 비어있는 페이지 목록이며 사용자의 쿼리가 새롭게 디스크의 데이터 페이지를 읽어와야 하는 경우 사용된다.
LRU 리스트는 가장 사용이 많은 페이지 MRU(Most Recently Used)와 LRU가 합친 형태 라고 볼 수 있다.
LRU 리스트를 관리하는 목적은 디스크로부터 한 번 읽어온 페이지를 최대한 InnoDB 버퍼 풀에 유지하여 디스크 읽기를 최소화 하는 것이다.
InnoDB 스토리지 엔진에서 데이터를 찾는 과정은 대략 다음과 같다.
1. 필요한 레코드가 저장된 데이터 페이지가 버퍼 풀에 있는지 검사
- InnoDB 어댑티브 해시 인덱스를 이용해 페이지를 검색
- 해당 테이블의 인덱스를 이용해 버퍼 풀에서 페이지를 검색
- 버퍼 풀에 이미 데이터 페이지가 있었다면 해당 페이지의 포인터를 MRU 방향으로 승급
2. 디스크에서 필요한 데이터 페이지를 버퍼 풀에 적재하고 적재된 페이지에 대한 포인터를 LRU 헤더 부분에 추가
3. 버퍼 풀의 LRU 헤더 부분에 적재된 데이터 페이지가 실제로 읽히면 MRU 헤더 부분으로 이동
4. 버퍼 풀에 상주하는 데이터 페이지는 사용자 쿼리가 얼마나 최근에 접근했는 지에 따라 나이(Age)가 부여되며 버퍼 풀에 상주하는 동안 오랫동안 사용되지 않으면 나이가 오래되고 결국 해당 페이지는 버퍼 풀에서 제거 된다.
5. 필요한 데이터가 자주 접근됐다면 해당 페이지의 인덱스 키를 어댑티브 해시 인덱스에 추가
따라서 자주 읽히는 페이지는 MRU로 이동하게 되고 오랫동안 사용되지 않은 페이지는 LRU로 밀려나와 결국 버퍼 풀에서 제거 된다.
플러시 리스트는 디스크로 동기화되지 않은 데이터를 가진 데이터 페이지의 변경 시점 기준의 페이지 목록을 관리한다.
한 번 변경된 이력이 있는 페이지를 디스크로 기록해야하기 때문에 플러시 리스트에 따로 관리하도록 한다.
데이터가 변경 되면 변경 내용을 리두 로그에 기록하고 버퍼 풀의 데이터 페이지에 반영한다. 따라서 리두 로그의 각 엔트리는 특정 데이터 페이지와 연결된다. InnoDB 엔진은 체크포인터를 만들어 일정 시점에서 리두 로그와 데이터 페이지 상태를 디스크에 동기화 시킨다.
버퍼 풀과 리두 로그
InnoDB의 버퍼 풀은 서버의 메모리가 허용하는 만큼 크게 설정하면 할수록 쿼리의 성능이 빨라진다. 버퍼 풀은 데이터베이스 서버의 성능 향상을위해 데이터 캐시와 쓰기 버퍼링이라는 두가지 용도가 있는데, 버퍼 풀의 메모리 공간만 단순히 늘리는 것은 데이터 캐시 기능만 향상시키는것이다.
InnoDB의 버퍼 풀은 디스크에서 읽은 상태로 전혀 변경되지 않은 클린 페이지와 함께 INSERT, UPDATE, DELETE 명령으로 변경된 데이터를 가진 더티 페이지도 가지고 있다. 더티 페이지는 디스크와 버퍼 풀의 페이지 내용이 다르다는 것을 의미하기 때문에 언젠가는 디스크로 동기화되어야 한다.
리두 로그는 데이터 페이지가 변경될 때마다 꼬리 형식으로 계속 변경된 버전에 해당하는 리두 로그가 기존 로그 다음에 달리게 된다. 이 리두 로그 파일 중에 당장 재사용 불가능한 공간을 활성 리두 로그라고 한다.
로그 파일 공간은 계속해서 재활용되어 사용 되지만 매번 기록될 때마다 로그 포지션은 계속 증가된 값을 갖게 되는데, 이를 LSN(Log Sequence Number)이라고 한다. InnoDB 스토리지 엔진은 주기적으로 체크포인트 이벤트를 발생시켜 리두 로그와 버퍼 풀의 더티 페이지를 디스크로 동기화하는데, 이렇게 발생한 체크 포인트 중 가장 최근 체크포인트 지점의 LSN 활성 리두 공간의 시적짐이 된다.
체크 포인트가 발생하면 체크포인트 LSN보다 작은 리두 로그 엔트리와 관련된 모든 더티 페이지는 모두 디스크로 동기화되어야 한다. 당연 체크 포인트 LSN보다 작은 값을 가진 리두 로그 엔트리도 디스크로 동기화 되어야한다.
버퍼 풀 플러시 (Buffer Pool Flush)
InnoDB 스토리지 엔진은 버퍼 풀에서 아직 디스크로 기록되지 않은 더티 페이지들을 성능상의 악영향 없이 디스크에 동기화하기 위해 다음과 같이 2개의 플러시 기능을 백그라운드로 실행한다.
- 플러시 리스트 플러시
- LRU 리스트 플러시
플러시 리스트 플러시
리두 로그 공간을 재활용하기 위해 주기적으로 오래된 리두 로그 엔트리가 사용하는 공간을 비워야 한다. 오래된 리두 로그 공간이 지워지려면 반드시 InnoDB 버퍼 풀의 더티 페이지가 먼저 디스크로 동기화돼야 한다. InnoDB 스토리지 엔진은 주기적으로 플러시 리스트 플러시 함수를 호출하여 플러시 리스트에서 오래전에 변경된 데이터 페이지를 순서대로 디스크에 동기화 하는 작업을 수행한다.
InnoB 스토리지 엔진에서 더티 페이지를 디스크로 동기화하는 스레드를 클리너 스레드라고 한다.
LRU 리스트 플러시
InnoDB 스토리지 엔진은 LRU 리스트에서 사용 빈도가 낮은 데이터 페이지를 제거해서 새로운 페이지들을 읽어올 공간을 만들어야 하는데 이를 위해 LRU 리스트 플러시 함수가 사용된다. LRU 리스트의 끝부분부터 시작해서 최대 innodb_lru_scan_depth 시스템 변수에 설정된 개수만큼의 페이지들을 스캔한다. InnoDB 스토리지 엔진은 이때 스캔하면서 더티페이지는 디스크에 동기화하게 하며 클린 페이지는 즉시 프리 리스트로 페이지를 옮긴다.
Double Write Buffer
InnoDB 스토리지 엔진의 리두 로그는 리두 로그 공간의 낭비를 막기 위해 페이지의 변경된 내용만 기록한다. 이로 인해 InnoDB의 스토리지 엔진에서 더티 페이지를 디스크 파일로 플러시할 때 일부분만 기록되는 무제가 발생하면 그 페이지의 내용은 복구할 수 없을 수도 있다. 이러한 페이지를 파셜 페이지 혹은 톤 페이지라고 한다.
이러한 현상을 방지하기 위해 더티 페이지를 디스크에 기록하기전 모든 기록해야하는 더티 페이지를 모아서 시스템 테이블스페이스의 DoubleWrite 버퍼에 기록한다. 그리고 InnoB 엔진은 각 더티 페이지를 적당한 위치에 랜덤으로 쓰기를 실행한다.
언두 로그
InnoDB 스토리지 엔진은 트랜잭션과 격리 수준을 보장하기 위해 DML(INSERT, UPDATE, DELETE)로 변경되기 이전 버전의 데이터를 별도로 백업한다. 이렇게 백업된 데이터를 언두 로그라고 한다.
- 트랜잭션 보장
트랜잭션이 롤백되면 트랜잭션 도중 변경된 데이터를 변경 전 데이터로 복구해야 하는데 이 때 언두 로그에 백업해둔 이전버전의 데이터를 이용해 복구한다.
- 격리 수준 보장
특정 커넥션에서 데이터를 변경하는 도중에 다른 커넥션에서 데이터를 조회하면 격리 수준에 맞게 변경중인 레코드를 읽지 않고 언두 로그에 백업해둔 데이터를 읽어서 반환하기도 한다.
언두 로그는 주로 트랜잭션이 롤백 혹은 커밋된 이후 삭제된다. 하지만 여러 사용자가 트랜잭션을 할 경우 언두 로그가 길게 남아 있을 수 있다. 특정 사용자가 트랜잭션을 시작한 이후 오랫동안 트랜잭션을 완료하지 않으면 이후 트랜잭션을 시작한 다른 사용자의 트랜잭션 종료 여부와 상관없이 언두 로그가 같이 남게된다. 이렇게 변경된 레코드를 다른 쿼리가 조회하게 되면 해당 언두 로그를 스캔해야 필요한 레코드를 사용할 수 있기 때문에 쿼리 성능이 떨어지게 된다.
InnoDB 스토리지 엔진은 언두 로그를 따로 로그 파일로 저장하여 사용한다.
체인지 버퍼
RDBMS에서 레코드가 INSERT되거나 UPDATE될 때는 데이터 파일을 변경하는 작업뿐 아니라 해당 테이블에 포함된 인덱스를 업데이트하는 작업도 필요하다. 인덱스를 업데이트하는 작업은 랜덤하게 디스크를 읽는 작업이 필요하므로 테이블에 인덱스가 많다면 이 작업은 상당히 많은 자원을 소모하게 된다.
버퍼 풀에 변경해야할 인덱스 페이지가 있다면 바로 업데이트를 수행하지만 그러지 않고 디스크로부터 읽어와서 업데이트해야 한다면 즉시 실행하지 않고 임시 공간에 저장해 두고 사용자에게 결과를 반환하는 형태로 성능을 향상시키는데 이때 임시 공간을 체인지 버퍼라고 한다.
사용자에게 결과를 전달하기 전에 반드시 중복 여부를 체크해야 하는 유니크 인덱스는 체인지 버퍼를 사용할 수 없다. 체인지버퍼에 임시로 저장된 레코드 조각은 이후 백그라운드 스레드에 의해 병합되는데 이 스레드를 체인지 버퍼 머지 스레드 라고 한다.
리두 로그 및 로그 버퍼
리두 로그는 트랜잭션 ACID 원칙 중 Durable 영속성과 연관이 있다. 서버가 비정상적으로 종료했을 때 데이터 파일에 기록되지 못한 데이터를 잃지 않게 해주는 안전 장치 이다.
MySQL 서버를 포함한 대부분 데이터베이스 서버는 데이터 변경 내용을 로그로 먼저 기록한다. 데이터 파일 쓰기는 디스크의 랜덤 액세스가 필요하기 때문에 데이터 파일에 쓰는 비용은 상대적으로 크다. 성능 저하를 막기 위해 데이터베이스 서버는 쓰기 비용이 낮은 리두 로그 자료구조를 활용하고 비정상 종료가 발생하면 리두 로그의 내용을 이용해 데이터 파일을 다시 서버가 종료되기 이전의 상태로 복구한다. 성능을 위해 데이터 파일뿐만 아니라 리두 로그를 버퍼링할 수 있는 InnoDB 버퍼 풀이나 리두 로그를 버퍼링할수 있는 로그 버퍼와 같은 자료 구조도 가지고 있다.
MySQL 서버가 비정상 종료되는 경우 InnoDB 스토리지 엔진의 데이터 파일은 다음과 같은 두가지 종류의 일관되지 않은 데이터를 가질 수 있다.
- 커밋됐지만 데이터 파일에 기록되지 않은 데이터
- 롤백됐지만 데이터 파일에 이미 기록된 데이터
1번의 경우 리두 로그에저장된 데이터를 데이터 파일에 다시 복사하기만 하면 된다.
2번의 경우 리두 로그로만 해결할수 없는데, 이때는 변경되기 전 데이터를 가진 언두 로그 가져와 데이터 파일에 복사하면된다. 최소한 그 변경이 커밋됐는지 롤백됐는지 그 중간 상태였는지 알기 위해서라도 리두 로그가 필요하다.
데이터 베이스 서버에서 리두 로그는 트랜잭션이 커밋되면 즉시 디스크로 기록되도록 시스템 변수를 설정하는 것을 권장한다.
어댑티브 해시 인덱스
Adaptive Hash Index 어댑티브 해시 인덱스는 사용자가 수동으로 생성하는 인덱스가 아니라 InnoDB 스토리지 엔진에서 사용자가 자주 요청하는 데이터에 대해 자동으로 생성하는 인덱스 이며 사용자는 Innnodb_adaptive_hsash_index 시스템 변수를 이용해 관련 기능을 키고 끌 수 있다.
어댑티브 해시 인덱스는 B-Tree 검색 시간을 줄여주기 위해 도입된 기능이다. InnoDB 스토리지 엔진은 자주 읽히는 데이터 페이지의 키 값을 이용해 해시 인덱스를 만들고 필요할 때마다 어댑티브 해시 인덱스를 검색해서 레코드가 저장된 데이터 페이지를 찾아갈 수 있다.
해시 인덱스는 '인덱스 키값'과 해당 인덱스 키 값이 저장된 '데이터 페이지 주소'의 쌍으로 관리되는데, 인덱스 키 값은 'B-Tree인덱스의 고유번호와 B-Tree 인덱스의 실제 키 값' 조합으로 생성된다.
'DB > MySQL' 카테고리의 다른 글
MySQL 인덱스 (0) | 2022.01.09 |
---|---|
MySQL 트랜잭션과 잠금 (0) | 2022.01.02 |
MySQL 아키텍쳐 (0) | 2021.12.29 |
MySQL 사용자 및 권한 (0) | 2021.12.23 |
MySQL 시스템 변수 (0) | 2021.12.20 |