8.3 B-Tree 인덱스
- B-Tree는 칼럼의 원래 값을 변형하지 않고 인덱스 구조체 내에서는 항상 정렬된 상태로 유지한다.
- 전문 검색 같은 특수 상황이 아닌 경우, 대부분 B-Tree를 사용한다.
- B-Tree는 데이터베이스 인덱싱 알고리즘 가운데 가장 일반적으로 사용되고, 가장 먼저 도입된 알고리즘이다.
- 현재도 가장 범용적인 목적으로 사용된다.
- 일반적으로 B+-Tree, B*-Tree 가 사용된다.
- B-Tree의 B는 "balanced"를 의미한다.
8.3.1 구조 및 특성
- B-Tree는 트리 구조에서 최상위에 하나의 루트노드가 존재하고 그 하위에 자식 노드가 붙어 있는 형태이다.
- 트리 구조에서 가장 하위의 노드: 리프 노드(leaf node)
- 루트 노드도 아니고 리브 노드도 아닌 노드: 브랜치 노드(branch node)
- 아래 그림과 같이 인덱스의 키 값은 모두 정렬되어 있지만 데이터 파일의 레코드는 정렬돼있지 않고 임의의 순서로 저장돼 있다. (항상 insert 된 순서대로 저장되지는 않는다.)
- 이유: 테이블에서 어떤 레코드가 삭제되서 빈 공간이 생기면 그 공간에 새로운 데이터가 들어가도록 DBMS가 설계되기 때문이다.
- 인덱스는 테이블의 키 컬럼만 가지고 있으므로 나머지 컬럼을 읽으려면 데이터 파일에서 해당 레코드를 찾아와야한다.
- 이를 위해 인덱스의 리프 노드는 데이터 파일에 저장된 레코드의 주소를 가진다.
- 아래의 두 그림은 인덱스의 리프 노드와 데이터 파일 둘의 관계를 나타낸다.
- MyISAM
- 레코드 주소: MyISAM 테이블의 생성 옵션에 따라 레코드가 테이블에 INSERT된 순번이거나 데이터 파일 내의 위치(offset )이다.
- InnoDB
- InnoDB 스토리지 엔진을 사용하는 테이블에서는 PK가 ROWID의 역할을 한다.
- 두 테이블의 차이점: 세컨더리 인덱스를 통해 데이터 파일의 레코드를 찾아가는 방법이 다르다.
- MyISAM: 세컨더리 인덱스가 물리적 주소를 가진다.
- InnoDB: 논리적인 주소를 가진다. (PK를 주소처럼 사용하기 때문) 따라서, MyISAM과 다르게 데이터 파일을 바로 찾아가지 못한다.
- 과정: 인덱스에 저장된 PK 값을 이용해서 PK 키 인덱스를 한번더 검색한 뒤, 리프 페이지에 저장됀 레코드를 읽는다. 즉, InnoDB에서는 모든 세컨더리 인덱스 검색에서 데이터 레코드를 읽기 위해서 반드기 프라이머리 키를 저장하고 있는 B-Tree 를 다시 한번 검색해야한다.
B-Tree 리프노드와 테이블 데이터 레코드(MyISAM)
B-Tree 리프 노드와 테이블 데이터 레코드 (InnoDB)
8.3.2 B-Tree 인덱스 키 추가 및 삭제
8.3.2.1 인덱스 키 추가
- 새로운 키 값이 B-Tree에 저장될 때, 테이블의 스토리지 엔진에 따라 새로운 키 값이 즉시 인덱스에 저장될 수도 있고 그렇지 않을수도 있다.
- 저장될 때 저장될 키 값을 이용해서 B-Tree에 저장될 때 저장될 키 값을 이용해서 B-Tree에 상의 적절한 위치를 검색해야한다.
- 위치가 결정되면 레코드 키 값과 대상 레코드의 주소 정보를 B-Tree의 리프 노드에 저장한다.
- 리프 노드가 꽉 차면 리프 노드가 분리(split)되어야 하는데 이는 상위 브랜치 노드까지 처리 범위가 넓어진다.
- 따라서, B-Tree는 상대적으로 쓰기 작업(새 키 추가)에 비용이 많이 든다.
- MyISAM 이나 Memory 스토리지 엔진을 사용하는 테이블에서는 INSERT 문장이 실행되면 새로운 키 값을 즉시 B-Tree인덱스에 변경한다.
- 반면에 InnoDB 스토리지 엔진은 인덱스 키 추가 작업을 지연시켜 나중에 처리할 수 있다.
8.3.2.2 인덱스 키 삭제
- B-Tree 키 값이 삭제되는 경우는?
- 해당 키 값이 저장된 B-Tree의 리프 노드를 찾아서 그냥 삭제 마크만 하면 삭제된다.
- 삭제 마킹된 공간은 방치하거나 재활용할 수 있다.
- 인덱스 마킹 작업 또한 디스크 쓰기가 필요하므로 디스크 I/O가 필요하다.
- MyISAM 이나 Memory 스토리지 엔진의 테이블에서는 체인지 버퍼와 같은 기능이 없으므로 인덱스 키 삭제가 완료된 후, 쿼리 실행이 완료된다.
8.3.2.3 인덱스 키 변경
- 인덱스의 키 값은 그 값에 따라 저장될 리프 노드의 위치가 결정되므로 B-Tree의 키 값이 변경되는 경우에는 단순히 인덱스상의 키 값만 변경하는 것은 불가능하다.
- B-Tree 키 값 변경 과정: 키 값은 삭제한 다음 새로운 키 값을 추가하는 형태이다.
- InnoDB 스토리지 엔진을 사용하는 테이블에 대해서는 이 작업 모두 체인지 버퍼를 활용해서 지연 처리된다.
참고) 체인지 버퍼 https://oranthy.tistory.com/426
8.3.2.4 인덱스 키 검색
- INSERT, UPDATE, DELETE 작업 시, 인덱스 관리에 따르는 추가 비용을 감당하며서 인덱스를 구축하는 이유는 ?
- "빠른 검색"을 위해서이다.
- 인덱스 트리 탐색: 인덱스를 검색하는 작업은 B-Tree의 루트 노드부터 시작해서 브랜치 노드를 거쳐 최종 리프 노드까지 이동하면서 비교 작업을 수행한다.
- SELECT, UPDATE, DELETE에서 인덱스 트리 탐색을 사용한다.
- B-Tree 인덱스를 이용한 검색은 100% 일치 또는 값의 앞 부분(left most part)만 일치하는 경우에 사용할 수 있다.
- 부등호 비교 조건에서도 인덱스를 활용할 수 있지만 인덱스를 구성하는 키 값의 뒷부분만 검색하는 용도로는 인덱스를 사용할 수 없다.
- 중요 - 인덱스의 키 값에 변형이 가해진 다음 비교되는 경우는 B-Tree의 빠른 검색 기능을 사용할 수 없다.
- 이유: 이미 변형된 값은 B-Tree 인덱스에 존재하는 값이 아니다. 따라서, 함수, 연산을 수행한 결과로 정렬하거나 검색하는 작업은 B-Tree의 장점을 이용할 수 없으므로 주의한다.
- InnoDB 스토리지 엔진에서의 인덱스
- 레코드 잠금이나 넥스트 키락(갭락)이 검색을 수행한 인덱스를 잠근 후, 테이블의 레코드를 잠그는 방식으로 구현돼 있다.
- 따라서, UPDATE, DELETE 문장이 실행될 때, 테이블에 적절히 사용할 수 있는 인덱스가 없으면 불필요하게 많은 레코드를 잠금다.
8.3.3 B-Tree 인덱스 사용에 영향을 미치는 요소
- B-Tree 인덱스는 인덱스를 구성하는 칼럼의 크기와 레코드 건수, 그리고 유니크한 인덱스 키 값의 개수 등에 의해 검색이나 변경 작업의 성능이 영향을 받는다.
8.3.3.1 인덱스 키 값의 크기
- InnoDB 스토리지 엔진은 디스크에 데이터를 저장하는 기본 단위를 페이지(Page) 또는 블록(block)이라고 한다.
- 디스크의 모든 읽기, 쓰기 작업의 최소 작업 단위가 된다.
- 페이지는 InnoDB 스토리지 엔진의 버퍼 풀에서 데이터를 버퍼링하는 기본 단위이다.
- 위의 그림 B-Tree index 구조에서 루트 노드, 브랜치 노드, 리프 노드, 데이터 파일을 구분하는 기준이 페이지 단위이다.
8.3.3.2 B-Tree 깊이(depth)
- 깊이는 중요하지만 직접 제어할 방법은 없다.
- 키 값이 16바이트인 경우, 최대 2억(585 * 585 * 585) 개 정도의 키 값을 담을 수 있지만 32 바이트로 늘어나면 5천만(372 * 372 * 372)개로 줄어든다.
- B-TRee 깊이는 MySQL에서 값을 검색할 때 몇 번이라 랜덤하게 디스크를 읽어야하는지와 직결되는 문제이다.
- 결론: 인덱스 키 값의 크기가 커지면 커질수록 하나의 인덱스 페이지가 담을 수 있는 인덱스 키 값의 개수가 적어진다.
- 따라서, 같은 레코드 건수라고 하더라고 B-Tree의 깊이가 싶어져서 디스크 읽기가 더 많이 필요한다.
- 가능하면 인덱스 키 값의 크기를 작게 만든다.
8.3.3.3 선택도(Selectivity, 기수성 Cardinality)
- 모든 인덱스 키 값 가운데 유니크한 값의 수를 의미한다.
- 인덱스 키 값 중에서 중복값이 많을수록 기수성은 낮아진다.
- 인덱스는 선택도가 높을수록 검색 대상이 줄어들기 때문에 그만큼 빠르게 처리된다.
Ex) 유니크한 값의 개수는 인덱스나 쿼리의 효율성에 미치는 영향
- 전체 레코드 수: 10000건
- case A) country 칼럼의 유니크한 값의 개수가 10개
- case B) country 칼럼의 유니크한 값의 개수가 1000개
- country 컬럼에만 인덱스가 있다고 가정
- 국가와 도시가 중복되는 경우는 없다고 가정
SELECT *
FROM tb_test
WHERE country = 'KOREA' AND city = 'SEOUL';
- 쿼리를 실행하면 A는 평균 1000건, B는 평균 10건이 조회될 수 있다.
- 만약 찾으려는 칼럼이 1건인 경우, A는 불필요하게 999개를 더 검색한 것이다. 따라서 A 는 부적합
- 그래서 A케이스의 경우 country 칼럼에 생성된 인덱스를 비효율적이다.
CREATE TABLE tb_city(
country VARCHAR(10),
city VARCHAR(10),
INDEX ix_country (country)
);
- case A) country 칼럼의 유니크한 값의 개수가 10개
- 전체 레코드 건수 / 유니크 값의 개수 = 하나의 키 값
- 'KOREA'라는 조건으로 검색하면 10,000 / 10 => 대략 1000건이 일치할 것이다.
- 'SEOUL'인 레코드는 1건이므로 999건은 불필요하게 읽은 값이다.
- case B) country 칼럼의 유니크한 값의 개수가 1000개
- 전체 레코드 건수 / 유니크 값의 개수 = 하나의 키 값
- 'KOREA' 라는 조건으로 인덱스를 검색하면 10,000 / 1,000 = 10건이 일치한다.
- 10 건 중에서 'SEOUL'을 만족하는 1건이므로 9개는 불필요하게 읽은 것이다.
8.3.3.4 읽어야하는 레코드의 건수
- 인덱스를 통해 데이터를 읽는 것은 인덱스를 거치지 않는 것보다 높은 비용이 드는 작업이다.
- 인덱스를 이용할지 이용하지 않을지 결정하는 방법은?
- 인덱스의 손익 분기점을 비교한다.
- 인덱스를 통해 읽어야 할 레코드의 건수(옵티마이저가 판단한 예상 건수) > (전체 테이블 레코드의 20 ~ 25%)인 경우 테이블을 모두 직접 읽어서 필요한 레코드만 가려내는 것이 효율적이다.
8.3.4 B-Tree 인덱스를 통한 데이터 읽기
8.3.4.1 인덱스 레인지 스캔(Index range scan)
- 인덱스의 접근 방법 중 가장 대표적이다.
- 이 단원에서는 인덱스를 통해 레코드를 한 건만 읽는 경우와 한 건 이상을 읽는 경우를 묶어서 인덱스 레인지 스캔이라고 한다.
- 스캔해야할 인덱스 범위가 결정 됐을 때 사용하는 방식이다.
- 검색 값의 수나 검색 결과 레코드 건수와 상관없이 레인지 스캔이라고 표현한다.
- 아래 그림과 같이 루트 노드부터 시작해서 브랜치 노드를 거치고 최종적으로 리프 노트까지 들어가야만 필요한 레코드의 시작 지점을 찾을 수 있다.
- 스캔 하다가 리프 노의 끝까지 읽으면 리프 노드 간의 링크를 이용해서 다음 리프 노드를 찾아서 다시 스캔한다.
- 스캔을 멈춰야할 위치에 다다르면 지금껏 읽은 레코드를 사용자에게 반환하고 쿼리를 끝낸다.
인덱스 레인지 스캔 과정
- 인덱스에서 조건을 만족하는 값이 저장된 위치를 찾는다. (인덱스 탐색)
- 1번에서 탐색된 위치부터 필요한 만큼 인덱스를 차례대로 쭉 읽는다. (인덱스 스캔)
- 2번에서 읽어 들인 인덱스 키와 레코드 주소를 이용해서 레코드가 저장된 페이지를 가져오고 최종 레코드를 읽어온다. (커버링 인덱스)
- 위 그림은 실제 인덱스만 읽는 경우를 보여준다.
- 아래 그림은 B-Tree 인덱스의 리프 노드를 스캔하면서 실제 데이터 파일의 레코드를 읽어와야하는 경우를 보여준다.
- 루트와 브랜치 노드를 이용해서 스캔 시작 위치를 검색하고
- 필요한 방향으로 인덱스를 읽어 나간다.
- 어느 방식으로 스캔하든 상관없이 인덱스를 구성하는 칼럼의 순서 또는 역순으로 정렬된 상태의 레코드를 가져온다.
- 인덱스 자체의 정렬 특성 때문에 자동으로 그렇게 된다.
- 리프 노드에서 검색조건에 일치하는 것들은 데이터 파일에서 레코드를 읽어오는 과정이 필요한다.
- 리프 노드에 저장된 레코드 주소로 데이터 파일의 레코드를 읽어오는데 레코드 한 건 단위로 랜던 I/O가 한 번씩 일어난다.
- 즉, 인덱스를 통해 데이터 읽는 방법은 비용이 많이 든다.
8.3.4.2 인덱스 풀 스캔
- Def) 인덱스 풀 스캔: 인덱스 리프 노드의 제일 앞, 또는 제일 뒤로 이동한 후, 인덱스의 리프 노드를 연결하는 링크드 리스트를 따라서 처음부터 끝까지 스캔하는 방식을 말한다.
- 인덱스 레인지 스캔과 마찬가지로 인덱스를 사용하지만 인덱스의 처음부터 끝까지 모두 읽는 방식을 인덱스 풀 스캔이라고 한다.
- 쿼리의 조건절에 사용된 칼럼이 인덱스의 첫 번째 칼럼이 아닌 경우에 인덱스 풀 스캔을 사용한다.
- 인덱스 뿐만 아니라 데이터 레코드까지 읽어야하는 경우는 이 방법을 쓰지 않는다.
- 인덱스 레인지 스캔보다 빠르지는 않지만 테이블 풀 스캔 보다는 효율적이다.
8.3.4.3 루스 인덱스 스캔(Loose Index Scan)
- Def) 루스 인덱스 스캔: 느슨하게 인덱스를 읽는 것을 의미한다.
- 인덱스 레인지 스캔과 비슷하게 작동하지만 중간에 필요하지 않은 인덱스 키 값은 무시하고 다음으로 넘어가는 형태이다.
- GROUP BY, MAX(), MIN() 함수에 대해 최적화를 하는 경우에 사용된다.
- Oracle 의 "인덱스 스킵 스캔"이라는 기능과 작동 방식이 비슷한다.
- 인덱스 레인지 스캔, 인덱스 풀 스캔은 "타이트 인덱스 스캔"으로 분류한다.
8.3.4.4 인덱스 스킵 스캔
- 데이터베이스에서 인덱스의 핵심은 값이 정렬되어 있다는 것이고 이로 인해 인덱스를 구성하는 칼럼의 순서가 중요하다.
- ex)
- 다음과 같이 인덱스를 생성하고나서 사용하려면 WHERE 절에 gender, birth_date 에 대한 비교 조건이 필수적이다.
- 둘 중에 하나만 where 절에 넣으면 인덱스를 효율적으로 사용할 수 없다.
ALTER TABLE employees
ADD INDEX ix_gender_birthdate (gender, birth_date);
- Def) 인덱스 스킵 스캔: 여러 개의 칼럼으로 인덱스를 생성한 경우, 옵티마이저가 하나의 칼럼만으로도 인덱스 검색이 가능하도록 해주는 기능이다. MySQL 8.0 부터 도입됐다.
- MySQL 8.0 이전 버전에서 비슷한 기능인 "루스 인덱스 스캔"이 있었지만 GROUP BY 작업을 처리하기 위해 인덱스를 사용하는 경우에만 적용 가능했다.
- 인덱스 스킵 스캔은 WHERE 조건절의 검색을 위해 사용 가능하도록 용도가 넓어졌다.
- SET optimizer_switch='skip_scan=on'; => 활성화
- SQL 쿼리의 맨 앞에 "EXPLAIN"을 붙이면 id, table, type, key, extra 가 표와 같은 형태로 출력된다.
- type = index: 인덱스를 처음부터 끝까지 모두 읽었다는 뜻 (풀 인덱스 스캔) - 비효율적
- type = range: 인덱스에서 필요한 부분만 읽었다는 뜻이다.
- extra = Using index for skip scan : 인덱스 스킵 스캔을 활용해서 데이터를 조회했다는 것을 의미한다.
인덱스 스킵 스캔의 단점
- WHERE 조건전에 조건이 없는 인덱스의 선행 칼럼의 유니크한 값의 개수가 적어야 한다.
- 쿼리 실행 계획의 비용 관련 부분
- 유니크한 값이 많으면 인덱스 스캔 시작 지점을 검색하는 작업이 많아질 것이다. 따라서 처리 성능 느려질 수 있다.
- 따라서, 인덱스의 선행 칼럼이 가진 유니크한 값의 개수가 적을 때만 인덱스 스킵 스캔을 적용 가능하다.
- 쿼리가 인덱스에 존재하는 칼럼만으로 처리가 가능해야 한다. (커버링 인덱스)
8.3.5 다중 칼럼(Multu-column) 인덱스
- 실제 서비스용 데이터베이스에서는 2개 이상의 칼럼을 포함하는 인덱스(다중 칼럼 인덱스, 복합 칼럼 인덱스, Concatenated Index)가 많이 사용된다.
- 그림을 보면 인덱스 두 번째 칼럼은 첫 번째 칼럼에 의존해서 정렬돼 있다. 두 번째 칼럼의 정렬은 첫 번째 칼럼이 똑같은 레코드에서만 의미가 있다.
- 만약 컬럼이 4개인 인덱스라면?
- 세 번째 칼럼은 두 번째 칼럼에 의존해서 정렬되고 네 번째 칼럼은 세 번째 칼럼에 의존해서 정렬된다.
8.3.6 B-Tree 인덱스의 정렬 및 스캔 방향
- 인덱스를 어느 방향으로 읽을지는 쿼리에 따라 옵티마이저가 실시간으로 만들어내는 실행 계획에 따라 결정된다.
8.3.6.1 인덱스의 정렬
- 일반적인 상용 DBMS에서는 인덱스를 생성하는 시점에 인덱스를 구성하는 각 컬럼의 정렬을 오름차순 또는 내림차순으로 설정할 수 있다.
8.3.6.1.1 인덱스 스캔 방향
- 인덱스는 항상 오름차순으로 정렬되어 있지만 오름차순, 내림차순으로 값을 가져올 수 있다.
- 오름차순 인덱스(Ascending index): 작은 값의 인덱스 키가 B-Tree 의 왼쪽으로 정렬된 인덱스
- 내림차순 인덱스(Descending index): 큰 값의 인덱 키가 B-Tree 의 왼쪽으로 정렬된 인덱스
- 인덱스 정순 스캔(Forward Index scan): 인덱스 키의 크고 작음에 관계없이 인덱스 리프 노트의 왼쪽 페이지부터 오른쪽으로 스캔한다.
- 인덱스 역순 스캔(Backword index scan): 인덱스 키의 크고 작음에 관계없이 인덱스 리프 노드의 오른쪽 페이지부터 왼쪽으로 스캔한다.
- InnoDB에서 인덱스 역순 스캔이 인덱스 정순 스캔에 비해 느릴 수 밖에 없다.
- 이유 1) 페이지 잠금이 인덱스 정순 스캔에 적합한 구조이다.
- 이유 2) 페이지 내에서 인덱스 레코드가 단방향으로 연결된 구조이다.
8.3.7 B-Tree 인덱스의 가용성과 효율성
- 어떤 조건에서 인덱스를 사용할 수 있고 어떨 때 사용할 수 없는지 살펴 보자.
8.3.7.1 비교 조건의 종류와 효율성
- 다중 칼럼 인덱스에서 각 칼럼의 순서와 칼럼에 사용된 조건이 동등 비교(=) 인지 아니면 범위 조건(>, <)인지에 따라서 인덱스 칼럼의 활용 형태가 달라지고 효율도 달라진다.
Ex)
SELECT *
FROM dept_emp
WHERE dept_no = 'd002' AND emp_no >= 10114;
- case A: INDEX(dept_no, emp_no)
- dept_no = 'd002' AND emp_no >= 10114 를 만족하는 레코드를 찾고
- 그 dept_no = 'd002'가 아닐 때까지 인덱스를 쭉 읽기만 하면 된다.
- 사용자가 원하는 결과만 읽기 때문에 효율적이다.
- dept_no, emp_no: 둘다 작업 범위 결정 조건이다.
- case B: INDEX(emp_no, dept_no)
- emp_no >= 10114 AND dept_no = 'd002' 인 레코드를 찾고
- 그 이후 모든 레코드에 대해 dept_no = 'd002' 조건을 만족(필터링)하는 레코드를 찾는다.
- 필터링: 인덱스를 통해 읽은 레코드가 나머지 조건에 맞는지 비교하면서 취사 선택하는 작업을 말한다.
- 아래 그림을 보면 5건의 레코드를 찾기 위해 7번의 비교 과정을 거친 것이다.
- emp_no: 작업 범위 결정 조건, dept_no: 필터링 조건
8.3.7.2 인덱스의 가용성
- B-Tree 인덱스의 특징은 왼쪽 값을 기준으로 오른쪽 값이 정렬돼있는 것이다.
- 왼쪽이란 하나의 칼럼 뿐만 아니라 다중 칼럼 인덱스의 칼럼에 대해서도 함께 적용된다.
SELECT *
FROM employees
WHERE first_name LIKE '%mer';
- 이 쿼리는 인덱스 레인지 스캔 방식으로 인덱스를 이용할 수 없다.
- 이유: first_name 칼럼에 저장된 값의 왼쪽부터 한 글자씩 비교하면서 일치하는 레코드를 찾아야되는데 조건절에 주어진 상숫값(%mer)에는 왼쪽 부분이 고정되지 않았기 때문이다.
- 따라서 정렬 우선순위가 낮은 뒷부분의 값만으로는 왼쪽 기준 정렬 기반의 인덱스인 B-Tree에서는 인덱스의 효과를 얻을 수 없다.
SELECT *
FROM dept_emp
WHERE emp_no
- 인덱스가 (dept, emp_np) 칼럼 순서대로 생성됀 경우, 선행 칼럼인 dept_no 조건 없이 emp_no 값으로만 검색하면 인덱스를 효율적으로 사용할 수 없다.
- case B의 인덱스는 다중 칼럼으로 구성된 인덱스이므로 dept_no 칼럼에 대해 먼저 정렬한 다음 다시 emp_no 칼럼 값으로 정렬돼 있기 때문이다.
8.3.7.3 가용성과 효율성 판단
- 체크 조건으로 인덱스를 사용 가능
- NOT-EQUAL로 비교된 경우 (<>, NOT IN, NOT BETWEEN, IS NOT NULL)
- LIKE '%??' (앞부분이 아닌 뒷부분 일치) 형태로 문자열 패턴이 비교된 경우
- 스토어드 함수나 다른 연산자로 인덱스 칼럼이 변형된 후 비교된 경우
- NOT-DETERMINISTIC 속성의 스토어드 함수가 비교 조건에 사용된 경우
- 데이터 타입이 서로 다른 비교(인덱스 칼럼의 타입을 변환해야 비교가 가능한 경우)
- 문자열 데이터 타입의 콜레이션이 다른 경우
출처 - 「Real MySQL 8.0 - 백은빈, 이성욱」 위키북스
'컴퓨터 과학 > [Study] Real MySQL 8.0' 카테고리의 다른 글
Chapter 08.08 클러스터링 인덱스(Clustering Index) (1) | 2023.01.04 |
---|---|
Chapter 08.04 R-Tree 인덱스 ~ 08.05 전문 검색 인덱스 (0) | 2022.12.29 |
Chapter 08.01 디스크 읽기 방식 ~ 08.02 인덱스란? (0) | 2022.12.11 |
Chapter 05. 트랜잭션과 잠금 (0) | 2022.12.08 |
Chapter 04.03 MyISAM 스토리지 엔진 아키텍처(architecture) ~ 04.04 MySQL 로그 파일 (0) | 2022.12.06 |