2025-09-10
오늘 배운 것
8.6 함수 기반 인덱스
- 칼럼의 값을 변형해서 만들어진 값에 대해 인덱스를 구축해야 할 때 함수 기반의 인덱스를 활용
- 구현 방법
- 가상 칼럼을 이용한 인덱스
- 함수를 이용한 인덱스
8.6.1 가상 칼럼을 이용한 인덱스
ADD full_name VARCHAR(30) AS (CONCAT(first_name,' ',last_name)) VIRTUAL,
ADD INDEX ix_fullname(full_name);
- 이처럼 가상 컬럼을 추가하고 해당 가상 칼럼에 인덱스를 생성할 수 있음
- VIRTUAL이나 STORED 옵션 중 어떤 옵션으로 생성됐든 관계없이 해당 가상 칼럼에 인덱스 생성이 가능하다
- 단점
- 가상 칼럼은 새로운 칼럼을 추가하는 것과 같은 효과를 내기 때문에 실제 테이블의 구조가 변경됌
8.6.2 함수를 이용한 인덱스
CREATE TABLE use (
user_id BIGINT,
first_name VARCHAR(10),
last_name VARCHAR(10),
PRIMARY KEY (user_id),
INDEX ix_fullname ((CONCAT(first_name,' ',last_name)))
);
- 이처럼 테이블의 구조를 변경하지 않고, 함수를 직접 사용하는 인덱스를 생성할 수 있음
- 활용할 때 반드시 조건절에 함수 기반 인덱스에 명시된 표현식이 그대로 사용되어야 함
- 표현식이 다르면 옵티마이저가 다른 표현식으로 간주하여 인덱스 활용이 안된다.
8.7 멀티 밸류 인덱스
- 모든 인덱스는 레코드와 인덱스 키 값이 1:1
- 멀티 밸류 인덱스는 하나의 데이터 레코드가 여러 개의 키 값을 가질 수 있는 형태의 인덱스
- 이는 JSON 데이터 타입을 지원하기 위해 시작됨
- 활용하려면 일반적인 조건 방식을 사용하면 안됨
- MEMBER_OF()
- JSON_CONTAINS()
- JSON_OVERLAPS()
- 이 함수들을 이용해서 검색해야 옵티마이저가 멀티 밸류 인덱스를 활용한 실행 계획을 수립
8.8 클러스터링 인덱스
- MySQL 서버에서 클러스터링은 테이블의 레코드를 비슷한것(PK를 기준으로)들끼리 묶어서 저장하는 형태로 구현
- 왜? 주로 비슷한 값을 동시에 조회하는 경우가 많기 때문
8.8.1 클러스터링 인덱스
- 프라이머리 키 값이 비슷한 레코드끼리 묶어서 저장하는 것
- 특징
- 프라이머리 키 값에 의해 레코드의 저장 위치가 변함
- 프라이머리 키 값이 변경되면 레코드의 물리적 위치가 변함
- 프라이머리 키 기반의 검색이 매우 빠름
- 레코드의 저장이나 프라이머리 키의 변경이 상대적으로 느림
- 리프 노드에 레코드의 모든 칼럼이 같이 저장
- 프라이머리 키가 없는 경우 스토리지 엔진이 대체 칼럼을 선택하는 우선 순위
- 프라이머리키가 있으며 기본적으로 프라이머리키를 클러스터링 키로 선택
- NOT NULL 옵션의 유니크 인덱스 중에서 첫 번째 인덱스를 클러스터링 키로 선택
- 자동으로 유니크한 값을 가지도록 증가되는 칼럼을 내부적으로 추가한 후, 클러스터링 키로 선택
- 자동으로 추가된 프라이머리키는 사용자에게 노출이 되지 않으며, 쿼리 문장에 명시적으로 사용할 수 없음
- 따라서 가능한 프라이머리키를 명시적으로 생성하는 것이 좋음
8.8.2 세컨더리 인덱스에 미치는 영향
- 세컨더리 인덱스에 리프 노드에는 레코드 주소가 아닌 프라잉머리 키 값이 저장되어 있음
- 따라서 클러스터링 키 값이 변경될때마다 해당 테이블의 모든 인덱스에 저장된 주솟값을 변경하지 않아도되는 장점이 있음
8.8.3 클러스터링 인덱스의 장점과 단점
- 장점
- 프라이머리 키로 검색할 때 처리 성능이 매우 빠름
- 특히 범위 검색의 경우 매우 빠름
- 테이블의 모든 세컨더리 인덱스가 프라이머리 키를 가지고 있기 때문에 인덱스 만으로 처리될 수 있는 경우가 많음 → 커버링 인덱스
- 프라이머리 키로 검색할 때 처리 성능이 매우 빠름
- 단점
- 테이블의 모든 세컨더리 인덱스가 클러스터링 키를 갖기 때문에 클러스터링 키 값의 크기가 클 경우 전체적으로 인덱스의 크기가 커짐
- 세컨더리 인덱스를 통해 검색할 때 프라이머리 키로 다시 한번 검색해야 하므로 처리 성능이 느림
- INSERT 할 때 프라이머리 키에 의해 레코드의 저장위치가 결정되기 때문에 처리 성능이 느림
- 프라이머리 키를 변경할 때 레코드를 DELETE하고 INSERT하는 작업이 필요하기 때문에 처리 성능이 느림
- OLTP 환경에서 쓰기와 읽기의 비율이 2:8 또는 1:9 정도이기 때문에 조금의 느린 쓰기를 감수하고 읽기를 빠르게 유지하는것이 매우 중요
8.8.4 클러스터링 테이블 사용 시 주의사항
8.8.4.1 클러스터링 인덱스 키의 크기
- 클러스터링 테이블의 경우 모든 세컨더리 인덱스가 프라이머리 키 값을 포함
- 따라서 프라이머리 키의 크기가 커지면 세컨더리 인덱스도 자동으로 크기가 커진다.
8.8.4.2 프라이머리 키는 AUTO-INCREMENT보다는 업무적인 칼럼으로 생성(가능한 경우)
- 프라이머리 키로 검색하는 경우 클러스터링 되지 않은 테이블에 비해 레코드가 프라이머리 키 값에 의해 위치가 결정되므로 매우 빠르게 처리 될 수 있음
- 따라서 설령 칼럼의 크기가 크더라도 업무적으로 해당 레코드를 대표할 수 있다면 그 칼럼을 프라이머리 키로 설정하는 것이 좋음
8.8.4.3 프라이머리 키는 반드시 명시할 것
- 프라이머리키를 명시하지 않으면 InnoDB스토리지 엔진이 내부적으로 일련번호 칼럼을 추가
- 자동으로 추가된 칼럼은 사용자에게 보이지 않기 때문에 사용자가 전혀 접근할 수가 없음
- 따라서 정의하지 않든 AUTO_INCREMENT 칼럼을 생성하고 PK로 사용하든 결과는 똑같음
- 그래서 차라리 활용하기 위해 pk를 명시하는 것이 좋음
8.8.4.4 AUTO-INCREMENT 칼럼을 인조 식별자로 사용할 경우
- 여러 개의 칼럼이 복합으로 프라이머리 키가 만들어져서 길이가 길어져도 세컨더리 인덱스가 필요치 않다면 그래로 PK를 사용하는 것이 좋다.
- 만약 세컨더리 인덱스가 필요하고 PK가 길다면 AUTO_INCREMENT 칼럼을 추가하고 이를 PK로 설정
- 인조 식별자 : PK를 대체하기 위해 인위적으로 추가된 프라이머리 키
- 로그 테이블 처럼 조회보다는 INSERT 위주의 테이블들은 인조 식별자를 PK로 설정하는 것이 성능 향상에 도움이 됨
8.9 유니크 인덱스
- MySQL에서는 인덱스 없이 유니크 제약만 설정할 방법이 없음
- 유니크 인덱스에서 NULL도 저장될 수 있는데, NULL은 특정 값이 아니므로 2개 이상 저장될 수 있음
- 프라이머리 키는 기본적으로 NULL을 허용하지 않는 유니크 속성이 자동으로 부여
8.9.1 유니크 인덱스와 일반 세컨더리 인덱스의 비교
8.9.1.1 인덱스 읽기
- 유니크하지 않은 세컨더리 인덱스에서 한 번 더 해야하는 작업은 디스크 읽기가 아니라 CPU에서 칼럼 값을 비교하는 작업이기 때문에 이는 성능상 영향이 거의 없다.
- 일반 세컨더리 인덱스는 중복된 값이 허용되므로 읽어야 할 레코드가 많아서 느린 것이지, 인덱스 자체의 특성 때문에 느린 것이 아님
- 읽어야 할 레코드 건수가 같다면 성능상의 차이는 미미
8.9.1.2 인덱스 쓰기
- 유니크 인덱스의 키 값을 쓸 때는 중복된 값이 있는지 없는지 체크하는 과정이 한 단계 더 필요
- 유니크하지 않은 세컨더리 인덱스의 쓰기보다 느림
- 중복된 값을 체크할 때는 읽기 잠금을 사용, 쓰기를 할 때는 쓰기 잠금을 사용 → 데드락이 아주 빈번히 발생
- 체인지 버퍼 : 인덱스 키의 저장을 버퍼링하기 위해 사용
- 그러나 유니크 인덱스는 반드시 중복 체크를 해야 하므로 작업 자체를 버퍼링하지 못함
8.9.2 유니크 인덱스 사용 시 주의사항
- 더 성능이 좋아질 것으로 생각하고 불필요하게 유니크 인덱스를 생성하지는 않는 것이 좋음
- 유니크 인덱스도 일반 세컨더리 인덱스와 같은 역할을 동일하게 수행할 수 있으므로 중복으로 만들어 줄 필요는 없음
- 유일성이 꼭 보장돼야 하는 칼럼에 대해서는 유니크 인덱스를 생성하되, 꼭필요하지 않다면 유니크 인덱스보다는 유니크하지 않은 세컨더리 인덱스를 생성하는 방법 고려하기
8.10 외래키
- 외래키는 InnoDB 스토리지 엔진에서만 생성가능
- 외래키 제약 설정되면 자동으로 연관되는 테이블의 칼럼에 인덱스까지 생성
- 외래키가 제거되지 않은 상태에서는 자동으로 생성된 인덱스를 삭제할 수 없음
- 특징
- 테이블의 변경(쓰기 잠금)이 발생하는 경우에만 잠금 경합(잠금 대기)이 발생한다.
- 외래키와 연관되지 않은 칼럼의 변경은 최대한 잠금 경합(잠금 대기)을 발생시키지 않는다.
8.10.1 자식 테이블의 변경이 대기하는 경우
- 자식 테이블의 외래 키 칼럼의 변경(INSERT, UPDATE)는 부모 테이블의 확인이 필요
- 이 상태에서 부모 테이블의 해당 레코드가 쓰기 잠금이 걸려 있으면 해당 쓰기 잠금이 해제될 때까지 기다리게 됨
- 자식 테이블의 외래키가 아닌 컬럼의 변경은 외래키로 인한 잠금 확장이 발생하지 않는다.
8.10.2 부모 테이블의 변경 작업이 대기하는 경우
- 부모 키를 참조하는 자식 테이블의 레코드를 변경하면 자식 테이블의 레코드에 대해 쓰기 잠금 획득
- 이 상태에서 부모 테이블에서 해당 키를 삭제하는 경우 자식 테이블의 레코드에 대한 쓰기 잠금이 해제될 때까지 기다려야 함
- 원인 : ON DELETE CASACADE로 인해 부모 레코드가 삭제되면 자식 레코드도 동시에 삭제되는 식으로 작동
-
따라서 외래키를 물리적으로 생성할 때 잠금 경합까지 고려해 모델링을 진행하는 것이 좋음
- 옵티마이저
- 쿼리를 최적으로 실행하기 위해 각 테이블의 데이터가 어떤 분포로 저장돼 있는지 통계 정보를 참조하며, 그러한 기본 데이터를 비교해 최적의 실행 계획을 수립
9.1 개요
9.1.1 쿼리 실행 절차
- 쿼리가 실행되는 과정
- 사용자로부터 요청된 SQL 문자를 잘게 쪼개서 MySQL 서버가 이해할 수 있는 수준으로 분리(파스 트리)한다.
- SQL 파서 모듈로 진행 → SQL 파싱
- SQL 문장이 문법적으로 잘못됐다면 이 단계에서 걸러짐
- SQL 파스 트리가 만들어짐
- SQL의 파싱 정보(파스 트리)를 확인하면서 어떤 테이블부터 읽고 어떤 인덱스를 이용해 테이블을 읽을지 선택한다.
- 최적화 및 실행 계획 수립 → 옵티마이저가 처리
- 불필요한 조건 제거 및 복잡한 연산의 단순화
- 여러 테이블의 조인이 있는 경우 어떤 순서로 테이블을 읽을지 결정
- 각 테이블에 사용된 조건과 인덱스 통계 정보를 이용해 사용할 인덱스를 결정
- 가져온 레코드들을 임시 테이블에 넣고 다시 한번 가공해야 하는지 결정
- 최적화 및 실행 계획 수립 → 옵티마이저가 처리
- 두번째 단계에서 결정된 테이블의 읽기 순서나 선택된 인덱스를 이용해 스토리지 엔진으로부터 데이터를 가져온다.
- 수립된 실행 계획대로 스토리지 엔진에 레코드를 읽어오도록 요청
- MySQL 엔진에서는 스토리지 엔진으로부터 받은 레코드를 조인하거나 정렬하는 작업을 수행
- 사용자로부터 요청된 SQL 문자를 잘게 쪼개서 MySQL 서버가 이해할 수 있는 수준으로 분리(파스 트리)한다.
- 첫번째, 두번째 단계 → MySQL 엔진
- 세번째 단계 → MySQL 엔진 + 스토리지 엔진
9.1.2 옵티마이저 종류
- 규칙 기반 최적화
- 대상 테이블의 레코드 건수나 선택도 등을 고려하지 않고 옵티마이저에 내장된 우선순위에 따라 실행계획을 수립하는 방식
- 같은 쿼리에 대해서는 거의 항상 같은 실행 방법을 만들어 냄
- 사용자의 데이터는 분포도가 매우 다양하여 이제는 거의 사용되지 않음
- 각 테이블이나 인덱스의 통계정보가 거의 없고 상대적으로 느린 CPU 연산 탓에 비용 계산 과정이 부담스럽다는 이유로 사용되었었음
- 비용 기반 최적화
- 쿼리를 처리하기 위한 여러 가지 가능한 방법을 만들고, 각 단위 작업의 비용(부하) 정보와 대상 테이블의 예측된 통계 정보를 이용해 실행 계획별 비용을 산출
- 산출된 실행 방법별로 비용이 최소로 소요되는 처리방식을 선택해 최종적으로 쿼리를 실행
9.2 기본 데이터 처리
9.2.1 풀 테이블 스캔과 풀 인덱스 스캔
- 풀 테이블 스캔 : 인덱스를 사용하지 않고 테이블의 데이터를 처음부터 끝가지 읽어서 요청된 작업을 처리하는 작업
- 사용 조건
- 테이블의 레코드 건수가 너무 작아서 인덱스를 통해 읽는 것보다 풀 테이블 스캔을 하는 편이 더 빠른 경우
- 일반적으로 테이블이 페이지 1개로 구성된 경우
- WHERE 절이나 ON 절에 인덱스를 이용할 수 있는 적절한 조건이 없는 경우
- 인덱스 레인지 스캔을 사용할 수 있는 쿼리라고 하더라도 옵티마이저가 판단한 조건 일치 레코드 건수가 너무 많은 경우
- 인덱스의 B-Tree를 샘플링해서 조사한 통계 정보 기준
- 테이블의 레코드 건수가 너무 작아서 인덱스를 통해 읽는 것보다 풀 테이블 스캔을 하는 편이 더 빠른 경우
- 사용 조건
- 테이블의 전체 크기는 인덱스보다 훨씬 크기 때문에 테이블을 처음부터 끝까지 읽는 작업은 상당히 많은 디스크 읽기가 필요
- 따라서 풀 테이블 스캔을 실행할 때 한꺼번에 여러 개의 블록이나 페이지를 읽어오는 기능을 내장하고 있음
- 리드 어헤드 : 어떤 영역의 데이터가 앞으로 필요해지리라는 것을 예측해서 요청이 오기 전에 미리 디스크에서 읽어 InnoDB의 버퍼 풀에 가져다 두는 것
- 처음 몇 개의 데이터 페이지는 포그라운드 스레드가 페이지 읽기를 실행
- 특정 시점 부터는 읽기 작업을 백그라운드 스레드로 넘김
- 백그라운드 스레드가 읽기를 넘겨 받는 시점부터 한 번에 4개 또는 8개씩의 페이지를 읽으면서 계속 그 수를 증가시킴
- 한번에 최대 64개의 데이터 페이지까지 읽어서 버퍼 풀에 저장
- 포그라운드 스레드는 미리 버퍼 풀에 준비된 데이터를 가져다 사용하기만 하면 되므로 쿼리가 상당히 빨리 처리됨
- 리드 어헤드는 풀 테이블 스캔, 풀 인덱스 스캔에서 동일하게 사용
- 단순 레코드의 건수만 필요하는 쿼리는 용량이 작은 인덱스를 선택하는 것이 디스크 읽기 횟수를 줄일 수 있어 풀 테이블 스캔보다는 풀 인덱스 스캔을 하게될 가능서잉 높음
9.2.2 병렬 처리
innodb_parallel_read_threads
라는 시스템 변수를 이용해 하나의 쿼리를 최대 몇 개의 스레드를 이용해서 처리할지를 변경할 수 있음- 아무런 WHERE 조건 없이 단순히 테이블의 전체 건수를 가져오는 쿼리만 병렬로 처리할 수 있음
- 병렬 처리용 스레드 개수를 아무리 늘리더라도 서버에 장착된 CPU의 코어 개수를 넘어서는 경우에는 오히려 성능이 떨어질 수도 있으니 주의
9.2.3 ORDER BY 처리 (Using filesort)
- 레코드 1~2건을 가져오는 쿼리를 제외하면 대부분의 SELECT 쿼리에서 정렬은 필수적으로 사용
- 정렬 처리 방법
- 인덱스 이용
- 장점
INSERT, UPDATE, DELETE 쿼리가 실행될 때이미 인덱스가 정렬돼 있어서 순서대로 읽기만 하면 되므로 매우 빠르다. → 근데 이거 오타 같음 저 실행될때 이전 내용 잘못된것 같음
- 단점
- INSERT, UPDATE, DELETE 작업 시 부가적인 인덱스 추가/삭제 작업이 필요하므로 느리다.
- 인덱스 때문에 디스크 공간이 더 많이 필요하다.
- 인덱스의 개수가 늘어날수록 InnoDB의 버퍼 풀을 위한 메모리가 많이 필요하다.
- 장점
- Filesort
- 장점
- 인덱스를 생성하지 않아도 되므로 인덱스를 이용할 때의 단점이 장점이 된다.
- 정렬해야할 레코드가 많지 않으면 메모리에서 Filesort가 처리되므로 충분히 빠르다.
- 단점
- 정렬작업이 쿼리 실행 시 처리되므로 레코드 대상 건수가 많아질수록 쿼리의 응답속도가 느리다.
- 장점
- 인덱스 이용
- 인덱스를 이용하여 정렬이 불가능할 때
- 정렬 기준이 너무 많아서 요건별로 모두 인덱스를 생성하는 것이 불가능한 경우
- GROUP BY의 결과 또는 DISTINCT 같은 처리의 결과를 정렬해야 하는 경우
- UNION의 결과와 같이 임시 테이블의 결과를 다시 정렬해야 하는 경우
- 랜덤하게 결과 레코드를 가져와야 하는 경우
9.2.3.1 소트 버퍼
- 소트 버퍼 : 정렬을 수행하기 위해 별도의 메모리 공간을 할당 받는 것
- 정렬이 필요한 경우에만 할당
- 버퍼의 크기는 정렬해야 할 레코드의 크기에 따라 가변적으로 증가
- 최대 사용 가능한 소트 버퍼 공간 → sort_buffer_size 시스템 변수로 설정
- 쿼리의 실행이 완료되면 즉시 시스템으로 반납
- 정렬해야할 레코드 건수가 소트 버퍼의 크기보다 클 때
- 메모리의 소트 버퍼에서 정렬을 수행하고, 그 결과를 임시로 디스크에 기록
- 다음 레코드를 가져와서 다시 정렬해서 반복적으로 디스크에 임시 저장
- 각 버퍼 크기만큼 정렬된 레코드를 다시 병합하면서 정렬을 수행 → 멀티 머지
- 이 작업들 모두 디스크의 쓰기와 읽기를 유발
- 레코드 건수가 많을수록 이 반복 작업의 횟수가 많아짐
- 그렇다고 소트 버퍼의 크기를 크게 설정한다고 성능이 좋아지지 않음
- 리눅스 계열의 운영체제에서는 너무 큰 sort_buffer_size를 사용하는 경우, 큰 메모리 공간 할당 때문에 성능이 훨씬 떨어질 수 있다.
- 정렬을 위해 할당하는 소트 버퍼는 세션 메모리 영역
- 즉 여러 클라이언트가 공유해서 사용할 수 있는 영역이 아님
- 커넥션이 많으면 많을수록, 정렬 작업이 많으면 많을 수록 소트 버퍼로 소비되는 메모리 공간이 커짐
- 이에 따라 운영체제는 메모리 부족 현상을 겪을 수 있고 여유 메모리 확보를 위해 MySQL 서버를 강제 종료 할 수 있음
9.2.3.2 정렬 알고리즘
- 싱글 패스 : 레코드를 정렬할 때 레코드 전체를 소트버퍼에 담음
- 투 패스 : 레코드를 정렬할 때 정렬 기준 칼럼만 소트 버퍼에 담음
- MySQL 서버의 정렬 방식
- <sort_key, rowid> : 정렬 키와 레코드의 로우아이디만 가져와서 정렬
- <sort_key, additional_fields> : 정렬 키와 레코드 전체를 가져와서 정렬하는 방식으로, 레코드의 칼럼들은 고정 사이즈로 메모리 저장
- <sort_key, packed_additional_fields> : 정렬 키와 레코드 전체를 가져와서 정렬하는 방식으로, 레코드의 칼럼들은 가변사이즈로 메모리 저장
9.2.3.2.1 싱글 패스 정렬 방식
- 소트 버퍼에 정렬 기준 칼럼을 포함해 SELECT 대상이 되는 칼럼 전부를 담아서 정렬을 수행
- 정렬이 완료되면 정렬 버퍼의 내용을 그대로 클라이언트로 넘겨줌
9.2.3.2.2 투 패스 정렬 방식
- 정렬 대상 칼럼과 프라이머리 키 값만 소트 버퍼에 담아서 정렬을 수행
- 정렬된 순서대로 다시 프라이머리 키로 테이블을 읽어서 SELECT할 컬럼을 가져오는 정렬 방식
- 싱글 패스 VS 투패스
- 싱글 패스
- 장점
- 한번만 읽고 그대로 리턴 가능
- 정렬 대상 레코드의 크기나 건수가 작은 경우 빠른 성능
- 단점 : 더 많은 소트 버퍼 공간 필요
- 장점
- 투 패스
- 장점
- 더 적은 소트 버퍼 공간 필요
- 정렬 대상 레코드의 크기나 건수가 많은 경우 빠른 성능
- 단점 : 두번 읽어야함
- 장점
- 싱글 패스
- 최신 버전을 주로 싱글 패스 사용함
- 싱글 패스 사용 불가한 경우
- 레코드의 크기가 max_length_for_sort_data 시스템 변수에 설정된 값보다 클 때
- BLOB이나 TEXT 타입의 컬럼이 SELECT 대상에 포함할 때
9.2.3.3 정렬 처리 방법
- ORDER BY 사용시 정렬하는 3가지 처리 방법(아래쪽으로 갈 수록 처리 속도 떨어짐)
- 인덱스를 사용한 정렬
- 조인에서 드라이빙 테이블만 정렬
- 조인에서 조인 결과를 임시 테이블로 저장 후 정렬
- 옵티마이저의 실행 계획 수립 과정
- 인덱스 사용 가능한지 확인 → 가능하다면 filesort 없이 인덱스로 정렬
- 사용 불가 시, WHERE 조건에 일치하는 레코드를 검색해 정렬 버퍼에 저장하면서 정렬을 처리
- 이때 정렬 대상 레코드를 최소화 하기 위해 2가지 방법 중 하나 선택
- 조인의 드라이빙 테이블만 정렬한 다음 조인을 수행 → 효율적
- 조인이 끝나고 일치하는 레코드를 모두 가져온 후 정렬을 수행
9.2.3.3.1 인덱스를 이용한 정렬
- 사용 조건
- ORDER BY에 명시된 칼럼이 제일 먼저 읽는 테이블에 속하고 , ORDER BY의 순서대로 생성된 인덱스가 있어야 함
- WHERE절에 첫 번째로 읽는 테이블의 칼럼에 대한 조건이 있다면 그 조건과 ORDER BY는 같은 인덱스를 사용할 수 있어야 함
- B-Tree 계열의 인덱스가 아닌 해시 인덱스나 전문 검색 인덱스 등에서는 인덱스를 이용한 정렬을 사용할 수 없음
- R-Tree도 안됨
- 여러 테이블이 조인되는 경우에는 네스티드-루프 방식의 조인에서만 이 방식 사용 가능
- 주의점
- 인덱스로 정렬이 처리될 때 ORDER BY를 명시된다고 해서 성능상의 손해가 없고 예외 상황에서도 버그로 연결되지 않으므로 굳이 없앨 필요는 없음
9.2.3.3.2 조인의 드라이빙 테이블만 정렬
- 조인을 실행하기 전에 첫 번째 테이블의 레코드를 먼저 정렬한 다음 조인을 실행
- 조건 : 조인에서 첫 번째로 읽히는 테이블의 칼럼만으로 ORDER BY 절을 작성해야 함
9.2.3.3.3 임시 테이블을 이용한 정렬
- ORDER BY 절의 정렬 기준 칼럼이 드라이빙 테이블이 아니라 드리븐 테이블에 있는 칼럼일 때는 조인 후 정렬해야 함
9.2.3.3.4 정렬 처리 방법의 성능 비교
- LIMIT는 테이블이나 처리 결과의 일부만 가져오기 때문에 MySQL 서버가 처리해야 할 작업량을 줄이는 역할을 함
- 하지만 ORDER BY나 GROUP BY 같은 작업은 WHERE 조건을 만족하는 레코드 LIMIT 건수 만큼 가져와서는 처리할 수 없음
- 따라서 WHERE 조건이 아무리 인덱스를 잘 활용하도록 튜닝해도 잘못된 ORDER BY나 GROUP BY 때문에 쿼리가 느려지는 경우가 자주 발생
- 쿼리 처리 방법
- 스트리밍 처리
- 버퍼링 처리
9.2.3.3.4.1 스트리밍 방식
- 서버 쪽에서 처리할 데이터가 얼마인지에 관계없이 조건에 일치하는 레코드가 검색될 때마다 바로바로 클라이언트로 전송해주는 방식
- 쿼리가 얼마나 많은 레코드를 조회하느냐에 상관없이 빠른 응답 시간을 보장
- OLTP환경에서 응답시간이 중요하므로 매우 효율적
- 스트리밍 방식에서는 LIMIT 처럼 결과 건수를 제한하는 조건들은 쿼리의 전체 실행 시간을 상당히 줄여줌
9.2.3.3.4.2 버퍼링 방식
- ORDER BY나 GROUP BY같은 처리는 쿼리의 결과가 스트리밍 되는 것을 불가능
- WHERE 조건에 일치하는 모든 레코드를 가져온후, 정렬하거나 그루핑해서 차례대로 보내야 하기 때문
- 모든 레코드를 검색하고 정렬 작업을 하는 동안 클라이언트는 아무것도 하지 않고 기다려야 하기 때문에 응답속도가 느려진다.
- 따라서 LIMIT처럼 결과 건수를 제한하는 조건이 있어도 성능향상에 별로 도움이 되지 않음
- ORDER BY 3가지 처리방법 방식 구분
- 인덱스 → 스트리밍
- 드라이빙 테이블 정렬 후 조인 → 버퍼링
- 조인 후 정렬 → 버퍼링
- 어떤 테이블이 먼저 드라이빙되어 조인되는지도 중요하지만 어떤 정렬 방식으로 처리되는지는 더 큰 성능 차이를 만듦
- 가능한 인덱스로 유도하고 안되면 드라이빙 테이블로 유도
9.2.3.4 정렬 관련 상태 변수
- Sort_merge_passes : 멀티 머지 처리 횟수
- Sort_range : 인덱스 레인지 스캔을 통해 검색된 결과에 대한 정렬 작업 횟수다
- Sort_scan : 테이블 스캔을 통해 검색된 결과에 대한 정렬 작업 횟수
- Sort_rows : 지금까지 정렬한 전체 레코드 건수