Real MySQL 8.0 - 10.실행 계획 - 인덱스 Type
- DB
- 2023. 7. 31.
들어가기 전
이 글은 Real MySQL 8.0을 공부하며 작성한 글입니다.
인덱스 레인지 스캔이란?
- const, ref, range로 인덱스를 접근하면 인덱스 레인지 스캔이라고 의미함.
- 인덱스 레인지 스캔 = 인덱스를 효율적으로 이용함 = 작업 범위 결정 조건으로 인덱스를 이용함
10.3.5 Type 컬럼
실행 계획에서 Type 이후의 컬럼은 각 테이블의 레코드를 어떤 방식으로 읽었는지를 나타낸다. 인덱스를 탔는지, 인덱스를 탔다면 어떤 방식으로 탔는지, 테이블 풀스캔을 했는지 등을 알 수 있다. Type 컬럼에 올 수 있는 값은 다음과 같다.
- const
- eq_ref
- ref
- fulltext
- ref_or_null
- unique_subquery
- index_subquery
- range
- index_merge
- index
- ALL
여기서 ALL은 테이블 풀 스캔이고, 나머지 모두는 인덱스를 탄 것이다. 어떻게 인덱스를 탔느냐에 따라 효율이 결정된다.
const
다음 조건을 만족하면 const 인덱스를 탄다.
- 쿼리가 WHERE 조건 절에 PK / 유니크 인덱스를 사용함.
- 다중 컬럼을 이용한 PK / 유니크 인덱스 중 일부만 이용할 때는, const 인덱스를 타지 않음.
- 반환하는 레코드는 반드시 1개.
- type const인 실행 계획은 옵티마이저가 쿼리를 최적화 할 때, 해당 쿼리를 실행해서 결과를 상수로 만들어서 다른 쿼리에 사용한다.
- WHERE first_name = (SELECT first_name FROM employees e2 WHERE emp_no=1); 이런 조건이 있고, SELECT 절의 실행 계획이 const인 상황. 그리고 결과는 hello임.
- 이 경우, 쿼리가 실행될 때 WHERE first_name = 'hello'으로 치환되서 쿼리가 실행됨
예를 들어 아래 쿼리는 const 인덱스를 탄다. 왜냐하면 PK가 조건절에 왔고, PK 인덱스에 해당되는 레코드는 1건이기 때문이다.
// PK -> (emp_no, dept_no)
EXPLAIN
SELECT * FROM employees WHERE emp_no=10001 and dept_no=30000\G;
id : 1
select_type : SIMPLE
table : employees
type : const
key : PRIMARY
key_len : 4
반면 PK 인덱스 중 일부 컬럼(emp_no)만 사용하는 경우에는 ref 형식으로 인덱스를 탄다.
// PK -> (emp_no, dept_no)
EXPLAIN
SELECT * FROM employees WHERE emp_no=10001\G;
id : 1
select_type : SIMPLE
table : employees
type : ref
key : PRIMARY
key_len : 4
eq_ref
다음 조건을 만족하면 eq_ref 인덱스를 사용한다.
- 여러 테이블이 조인되는 쿼리일 때.
- 드라이빙 테이블에서 읽은 테이블의 컬럼값이 두번째 테이블의 PK, 유니크 키 칼럼의 검색 조건(WHERE)에 사용.
- 두번째 테이블에서 읽는 레코드의 값이 단 1개일 때
예를 들면 다음 쿼리에서 사용된다.
// 첫번째 테이블 dept_emp de
// 두번째 테이블 employees e
// 검색 조건 e.emp_no = de.emp_no
EXPLAIN
SELECT * FROM dept_emp de, employees e
WHERE e.emp_no=de.emp_no AND de.dept_no='d005'\G;
id select_type table type key key_len rows
1 SIMPLE de ref PRIMARY 16 166561
1 SIMPLE e eq_ref PRIMARY 4 1
위의 경우 두번째 테이블이 해당된다.
첫번째 테이블에서 읽은 컬럼이 두번째 테이블의 WHERE 조건의 PK로 사용되고, 반환하는 레코드가 1건이기 때문에 대응된다.
ref
- ref는 조인 조건 / 유니크 인덱스 / PK 인덱스와 관련없이 사용된다.
- 인덱스 종류와 관계 없이 동등 조건 (Equal)로 검색할 때 Ref가 사용됨.
ref 인덱스로 검색 시, 검색 결과가 하나의 레코드가 아닐 수 있기 때문에 eq_ref, const에 비해서는 빠르지 않지만 동등한 조건으로만 비교되기 때문에 빠른 레코드 조회 방법이다.
// dept_no는 PK가 아님.
EXPLAIN
SELECT * FROM dept_emp WHERE dept_no='d005';
id : 1
select_type : SIMPLE
table : dept_emp
type : ref
key : PRIMARY
key_len : 16
ref : const
상당히 빠른 인덱스 조회 조건
- const : 조인의 순서와 관계없이 PK, 유니크 키의 모든 컬럼에 대해 equal 조건으로 검색 + 반드시 1건 반환하는 경우
- eq_req : 조인 시, 첫번째 테이블의 읽은 컬럼이 두번째 테이블의 검색 조건 (WHERE)의 유니크 인덱스 / PK로 동등 (equal) 조건 검색. 반드시 1건 반환하는 경우
- ref : 동등 조건으로 검색. 조인 / 인덱스 종류 상관없음. 여러 레코드 반환 가능
이 인덱스의 공통점은 모두 'equal' 조건으로 검색을 해야한다는 것이다. 세 가지 모두 좋은 접근 방법이고, 이 인덱스가 사용되는 경우 쿼리 튜닝을 더 하지 않아도 된다.
ref_or_null
- ref 인덱스와 접근 방법은 동일한데, NULL 비교가(IS NULL) 추가됨.
아래 쿼리에서 사용됨.
SELECT * FROM titles
WHERE to_date='1985-03-01' OR to_date IS NULL; // 인덱스에 NULL 비교를 하는 경우.
id : 1
select_type : SIMPLE
table : titles
type : ref_or_null // ref_or_null 사용됨
key : ix_todate
key_len : 4
ref : const
rows : 2
unique_subquery
- WHERE 조건 절에서 IN(Subquery) 형태의 쿼리를 위한 접근 방법이다.
- Subquery에서 유니크한 값만 반환할 때 이 접근 방법이 사용됨.
- 이 쿼리의 결과가 유니크한 값만 반환한다는 것이 보장될 때임. 중복을 따로 제거해주는 것이 아님.
EXPLAIN
SELECT * FROM departments
WHERE dept_no IN (SELECT dept_no FROM dept_emp WHERE emp_no=10001);
id : 1
select_type : PRIMARY
table : departments
type : index
key :ux_deptname
key_len : 162
id : 2
select_type : DEPENDENT SUBQUERY
table : dept_emp
type : unique_subquery // 여기서 사용됨
key : PRIMARY
key_len : 20
index_subquery
- IN 절에는 중복이 제거된 값이 들어가야한다.
- unique_subquery는 IN 절에서 사용된 서브 쿼리의 결과가 유니크한 값을 반환하는 것이 보장될 때 사용되는 방법이다.
- 쿼리가 유니크한 값을 반환하지 않는데, 사용한 인덱스를 이용해 중복 값을 제거할 수 있는 경우 index_subquery를 사용한다.
range
- 인덱스 레인지 스캔을 의미함.
- 좋은 성능을 보여줌.
- 범위로 검색하는 경우에 주로 사용됨. (<, >, BETWEEN, IS NULL, IN, LIKE )
EXPLAIN
SELECT * FROM employees WHERE emp_no BETWEEN 10002 AND 10004;
id: 1
select_type: SIMPLE
table: employees
type: range // RANGE 스캔이 사용됨.
key: PRIMARY
key_len: 4
rows: 3
index_merge
- 2개 이상의 인덱스를 이용해 각각의 검색 결과를 만들어 낸 후, 이 결과를 Merge 한 후에 레코드를 반환함.
- 효율적으로 동작하지 않음.
- 각각의 인덱스를 검색해야 함.
- Merge 하는 과정에서 중복되는 것들을 제거하는 비교 연산이 들어감.
예를 들면 아래 쿼리에서 사용한다.
// 인덱스 ix_firstname (first_name)
mysql>
EXPLAIN
SELECT * FROM employees
WHERE emp_no BETWEEN 10001 AND 11000
OR first_name='Smith';
>>>
id : 1
type : index_merge
key : PRIMARY, ix_firstname
key_len : 4,58
Extra : Using Union(PRIMARY, ix_firstname); Using Where
위 쿼리를 살펴보면 emp_no (PK)로 검색을 한 결과이거나 first_name이 스미스인 경우를 찾는다. 인덱스를 이용해 각각을 검색할 수 있기 때문에 다음과 같이 처리한다.
- PRIMARY, ix_firstname 인덱스에 대해서 각각 검색한다.
- MERGE 한 후에 결과를 반환함.
index
- 풀 인덱스 스캔을 의미함. 만들어진 인덱스를 처음부터 끝까지 다 읽음.
- 풀 테이블 스캔과 읽는 레코드 건수는 동일하지만, 인덱스의 용량이 적기 때문에 디스크 I/O가 적게 발생함.
- 또한 인덱스는 정렬이 되어 있기 때문에 이 순서대로 읽기를 원한다면, 인덱스를 이용하는게 더 좋음
- 아래 경우에 풀 인덱스 스캔을 함.
- const, ref, range 접근 방법으로 인덱스를 사용하지 못하는 경우 (필)
- 인덱스에 포함된 컬럼으로만 처리될 수 있는 커리인 경우 (선1)
- 인덱스를 이용한 정렬이나 그룹핑 작업이 가능한 경우 (선2)
- 필수 조건에 선택 1 / 2 중 하나를 만족하면 풀 인덱스 스캔으로 동작함.
ALL
- 풀 테이블 스캔을 의미함. 테이블을 처음부터 끝까지 읽음. 가장 비효율적임.
- 선택할 수 있는 방법이 없을 때 선택함.
- 다만 처리 속도를 빠르게 하기 위해서 MySQL은 Read Ahead를 지원함. (클라이언트 쓰레드는 백그라운드 쓰레드가 미리 가져온 데이터를 서빙만 함.