https://dev.mysql.com/doc/refman/8.4/en/execution-plan-information.html
MySQL 8.0 버전부터는 인덱스되지 않은 컬럼들에 대해서도 데이터 분포도를 수집해서 저장하는 히스토그램(Histogram) 정보가 도입되었다.
innodb_index_stats 테이블과 innodb_table_stats 테이블로 영구적으로 저장 및 관리할 수 있게 개선되었다.CREATE TABLE tab_test (fd1 INT, fd2 VARCHAR(20), PRIMARY KEY(fd1))
ENGINE=InnoDB
STATS_PERSISTENT={ DEFAULT | 0 | 1 }; // 테이블 생성 시점에 값 설정// 옵션 설정값 변경
ALTER TABLE employees.empoyees STATS_PERSISTENT={ DEFAULT | 0 | 1 };
STATS_PERSISTENT=0
: 테이블의 통계 정보를 테이블에 저장하지 않음.
STATS_PERSISTENT=1
: 테이블의 통계 정보를 테이블에 저장해 관리함.
STATS_PERSISTENT=DEFAULT
: 테이블 생성 시 별도로 옵션 값을 설정하지 않았을 때의 디폴트 값이다.
테이블의 통계를 영구적으로 관리할지 말지를innodb_stats_persistent시스템 변수의 값으로 결정한다.
innodb_stats_persistent시스템 설정 변수는 기본적으로ON(1)로 설정되어 있어서 옵션 없이 테이블을 생성하면 영구적으로 통계 정보를 저장한다.
innodb_stats_auto_recalc 시스템 변수의 설정 값을 ON으로 설정하면 InnoDB가 데이터가 충분히 변했다고 판단되는 시점에 자동으로 통계를 다시 계산하는데, OFF면 자동 재계산을 안 하고 ANALYZE TABLE 같은 수동 트리거에 의존하게 된다.
innodb_stats_auto_recalc을 OFF할 것을 권하고 있다.STATS_AUTO_RECALC 옵션을 이용해 개별 테이블 단위로 자동 수집 여부를 결정할 수 있기 때문에 이걸 활용하자는 것.전역 변수 값은 OFF하고 데이터가 자주 바뀌는 테이블에 대해서만
STATS_AUTO_RECALC=1
: 테이블의 통계 정보를 자동으로 수집한다.
STATS_AUTO_RECALC=0
: 테이블의 통계 정보는 ANAYLYZE TABLE 명령을 실행할 때만 수집된다.
STATS_AUTO_RECALC=DEFAULT
: 테이블 생성 시 별도로 옵션을 설정하면 이 값으로 설정된다. 테이블의 통계 정보 수집을innodb_stats_auto_reclac시스템 변수의 값으로 결정한다.
STATS_AUTO_RECALC=1로 설정해 자동 수집을 하자는 것. 굉장히 합리적이고 이상적인 조합이긴 하다. 근데,STATS_AUTO_RECALC=0으로 설정해놨는데 잘못된 판단이었던 경우STATS_AUTO_RECALC 값을 설정 안 한 채로 테이블을 생성해버려서 자동 수집이 필요한데도 innodb_stats_auto_recalc OFF 설정대로 자동 수집이 안 되어버리는 경우innodb_stats_auto_recalc를 켜두는 것이 더 나을 수도 있을 것 같다.innodb_stats_transient_sample_pagesinnodb_stats_persistent_sample_pagesANALYZE TABLE 명령으로 통계 정보 수집을 수동으로 진행할 때, 몇 개의 페이지를 임의로 샘플링해서 분석하고 그 결과를 통계 정보로 활용할지를 정할 수 있는 시스템 변수이다. 기본값은 20이다.이 값들을 작게 설정하면 통계의 정확도가 낮아지고, 높게 설정하면 통계 정보 수집 시간이 길어질테니 적당히 설정하자.
// 통계 정보 조회
SELECT *
FROM innodb_index_stats
WHERE database_name='employees' AND TABLE_NAME='employees';
innodb_index_stats.stat_name='n_diff_pfx%'innodb_index_stats.stat_name='n_leaf_pages'innodb_index_stats.stat_name='size'innodb_index_stats.n_rowsinnodb_index_stats.clustered_index_sizeinnodb_index_stats.sum_of_other_index_sizesSTATS_AUTO_RECALC 옵션 값에 따라 0으로 보일 수도 있는데, 그 경우 다음과 같이 테이블에 대해 ANALYZE TABLE 명령을 실행하면 통곗값이 저장된다.ANALYZE TABLE employees.employees;https://dev.mysql.com/blog-archive/histogram-statistics-in-mysql/
MySQL 8.0으로 업그레이드되면서 MySQL 서버도 컬럼의 데이터 분포도를 참조할 수 있는 히스토그램 정보를 활용할 수 있게 되었다.
히스토그램은 버킷(Bucket) 단위로 레코드 건수나 컬럼값의 범위를 구분해 관리한다.
// 히스토그램 수집
ANALYZE TABLE employees.employees
UPDATE HISTOGRAM ON gender, hire_date;
// 수집된 히스토그램 조회
SELECT *
FROM COLUMN_STASTICS
FROM SCHEMA_NAME='empolyees' AND TABLE_NAME='employees';
ANALYZE TABLE ... UPDATE HISTOGRAM 명령을 실행함으로써 수동으로 수집 및 관리할 수 있다.information_schema 데이터베이스의 column_statistics 테이블로 로드한다.column_statistics 테이블을 SELECT해서 참조할 수 있다.information_schema.column_statstics 테이블의 HISTOGRAM 컬럼이 가진 나머지 필드들의 의미는 다음과 같다.histogram_generation_max_mem_size 시스템 변수에 설정된 메모리 크기에 맞게 적절히 샘플링한다. 이 변수의 초기값은 20MB이다.MySQL 8.0 버전 기준


ANALYZE TABLE employees.employees
DROP HISTOGRAM ON gender, hire_date;
SET GLOBAL optimizer_switch='condition_fanout_filter=off';
optimizer_switch 시스템 변수 값을 글로벌로 변경하면 된다. 이렇게 하면 MySQL 서버의 모든 쿼리가 히스토그램을 사용하지 않는다.condition_fanout_filter 옵션에 의해 영향을 받는 다른 최적화 기능들이 사용되지 않을 수도 있다고 한다.// 현재 커넥션에서 실행되는 쿼리만 히스토그램을 사용하지 않게 설정
SET SESSION optimizer_switch='condition_fanout_filter=off';
// 현재 쿼리에서만 히스토그램을 사용하지 않게 설정
SELECT /*+ SET_VAR(optimizer_switch='condition_fanout_filter=off') */ *
FROM ...
SELECT * FROM order WHERE user_id='matt.lee';Maintaining an index has a cost. If you have an index, every INSERT/UPDATE/DELETE causes the index to be updated. This is not free, and will have an impact on your performance. A histogram on the other hand is created once and never updated unless you explicitly ask for it. It will thus not hurt your INSERT/UPDATE/DELETE-performance.
대충 인덱스가 업데이트가 느리다는 단점을 커버할 수 있다는 얘기 같다. 히스토그램은 사용자가 ANALYZE TABLE을 실행할 때만 생성/갱신되니까.
If you have an index, the optimizer will do what we call “index dives” to estimate the number of records in a given range. This also has a certain cost, and it might become too costly if you have for instance very long IN-lists in your query. Histogram statistics are much cheaper in this case, and might thus be more suitable.
인덱스 다이브를 수행하는 것도 공짜가 아니다. 히스토그램은 이미 만들어져 있으니 쿼리 수행 시점에 새롭게 레코드 건수를 파악하고 하지 않고 그냥 가져다 쓰면 되는 거니까. 특히 IN 절의 조건이 엄청 많으면 그냥 전체 샘플링이랑 별 차이 없는 경우도 있다는 얘기를 하고 싶은 것 같다.
https://dev.mysql.com/doc/refman/8.0/en/cost-model.html
쿼리를 처리할 때는 다음과 같은 다양한 작업을 필요로 한다.
두 테이블 모두 mysql DB에 존재한다.
server_costengine_costcost_namedefault_valuecost_valuelast_updatedcommentlast_updated와 comment는 옵티마이저에 영향을 미치는 정보는 아니며, 단순 정보성으로 관리되는 컬럼이다.
engine_cost 테이블이 추가로 더 가지고 있는 컬럼(하지만 의미가 없음)enginedevice_type
Cost Model에서 중요한 것은 각 단위 작업에 설정되는 비용 값이 커지면 어떤 실행 계획들이 고비용으로 바뀌고 어떤 실행 계획들이 저비용으로 바뀌는지 파악하는 것이다.
io_block_read_costmemory_block_read_costdisk_temptable_create_cost와 disk_temptable_row_costkey_compare_costmemory_temptable_create_cost와 memory_temptable_row_costrow_evaluate_costFORMAT 옵션을 사용해 실행 계획의 표시 방법을 단순 테이블 형태, TREE, JSON 중 선택할 수 있다.
EXPLAIN
SELECT *
FROM employees e
INNER JOIN salaries s ON s.emp_no=e.emp_no
WHERE first_name='ABC';

EXPLAIN FORMAT=TREE
SELECT *
FROM employees e
INNER JOIN salaries s ON s.emp_no=e.emp_no
WHERE first_name='ABC';

EXPLAIN FORMAT=JSON
SELECT *
FROM employees e
INNER JOIN salaries s ON s.emp_no=e.emp_no
WHERE first_name='ABC';

EXPLAIN ANALYZE 기능이 추가되었다.EXPLAIN 명령에 FORMAT 옵션을 사용할 수 없다.EXPLAIN ANALYZE 명령을 실행했을 때 쿼리가 완료되어야 실행 계획의 결과를 확인할 수 있다.EXPLAIN 명령으로 먼저 실행 계획만 확인해서 어느 정도 튜닝한 후 EXPLAIN ANALYZE 명령을 실행하는 것이 좋다.들여쓰기가 다른 레벨에서는 가장 안쪽에 위치한 라인이 먼저 실행
들여쓰기가 같은 레벨에서는 상단에 위치한 라인이 먼저 실행
EXPLAIN ANALYZE
SELECT
p1_0.id,
p1_0.status,
p1_0.payment_id,
u1_0.name,
u1_0.email,
p2_0.category,
p2_0.title,
p1_0.total_amount,
p2_0.platform,
p1_0.requested_at,
p1_0.cancelled_amount,
p1_0.cancel_reason,
p1_0.cancelled_at,
m1_0.NAME
FROM
payments p1_0
LEFT JOIN
users u1_0
ON p1_0.user_id=u1_0.private_id
LEFT JOIN
MEMBER m1_0
ON p1_0.member_id=m1_0.ID
JOIN
products p2_0
ON p2_0.private_id=p1_0.product_id
ORDER BY
p1_0.requested_at desc
-> Nested loop inner join (cost=130 rows=113) (actual time=0.186..0.616 rows=113 loops=1)
-> Nested loop left join (cost=90.6 rows=113) (actual time=0.176..0.463 rows=113 loops=1)
-> Nested loop left join (cost=51.1 rows=113) (actual time=0.174..0.422 rows=113 loops=1)
-> Sort: p1_0.requested_at DESC (cost=11.6 rows=113) (actual time=0.156..0.169 rows=113 loops=1)
-> Filter: (p1_0.product_id is not null) (cost=11.6 rows=113) (actual time=0.038..0.118 rows=113 loops=1)
-> Table scan on p1_0 (cost=11.6 rows=113) (actual time=0.0366..0.11 rows=113 loops=1)
-> Single-row index lookup on u1_0 using UK85bxs2b2qmo9xv6u02x0dv93q (private_id=p1_0.user_id) (cost=0.251 rows=1) (actual time=0.00207..0.0021 rows=1 loops=113)
-> Single-row index lookup on m1_0 using PRIMARY (ID=p1_0.member_id) (cost=0.251 rows=1) (actual time=213e-6..215e-6 rows=0.0708 loops=113)
-> Single-row index lookup on p2_0 using UK40t6qkxhq6et2gvdjdfy0i8rv (private_id=p1_0.product_id) (cost=0.251 rows=1) (actual time=0.00117..0.0012 rows=1 loops=113)
EXPLAIN ANALYZE 결과에서 필드들의 의미-> Single-row index lookup on u1_0 using UK85bxs2b2qmo9xv6u02x0dv93q (private_id=p1_0.user_id) (cost=0.251 rows=1) (actual time=0.00207..0.0021 rows=1 loops=113)
actual time=0.00207..0.0021rows=1loops=113여기서 말하는 '평균'은 loop를 돌며 반복된 작업들 간에 평균을 냈다는 뜻이다.
loops=113이므로 113번의 작업을 실행하면서 나타난 평균 값이라는 것.
UNION이나 상관 서브쿼리와 같은 경우 순서대로 표시되지 않을 수도 있다.https://dev.mysql.com/doc/refman/8.0/en/explain-output.html
SELECT 쿼리별로 부여되는 식별자 값이다.SELECT 문장이 하위 SELECT 문장을 포함하는 형태의 쿼리에서는 실행 계획에 최소 2개 이상의 id 값이 표시될 것이다.select_type 컬럼SELECT 쿼리가 어떤 타입의 쿼리인지를 표시하는 컬럼이다.UNION이나 서브쿼리를 사용하지 않는 단순한 SELECT 쿼리인 경우이다.select_type이 SIMPLE인 단위 쿼리는 오직 1개만 존재한다.SELECT 쿼리에 표시된다.UNION이나 서브쿼리를 가지는 SELECT 쿼리의 실행 계획에서 가장 바깥쪽에 있는 단위 쿼리에 표시된다.select_type이 PRIMARY인 단위 SELECT 쿼리는 오직 1개만 존재한다.UNION으로 결합하는 단위 SELECT 쿼리 가운데 첫 번째를 제외한 두 번째 이후 단위 SELECT 쿼리에 표시된다.UNION으로 결합하는 단위 SELECT 쿼리 가운데 외부 쿼리에 의해 영향을 받는 것을 의미한다.table 컬럼type 컬럼index_merge를 제외한 나머지 접근 방법은 하나의 인덱스만 사용한다.type 컬럼의 값들은 성능이 빠른 순서대로 나열된 것이다.EXPLAIN
SELECT * FROM employees WHERE emp_no=10001;
WHERE 절을 가지고 있으며, 반드시 1건을 반환하는 쿼리의 처리 방식을 의미한다.const가 아닌 ref로 표시된다.EXPLAIN
SELECT * FROM dept_emp de, employees e
WHERE e.emp_no=de.emp_no AND de.dept_no='d005';
책에서 이 쿼리에 조인이 있다길래 JOIN절이 없는데 뭔 소리지? 했는데 이렇게 하는 게 옛날 구식 조인 문법이란다. 킹받네...
type 컬럼에 eq_ref가 표시된다.NOT NULL이어야 하며, 다중 컬럼으로 만들어진 PK 혹은 UK라면 인덱스의 모든 컬럼이 비교 조건에 사용되어야만 이 접근 방법이 사용될 수 있다.eq_ref와는 달리 조인의 순서와 관계 없이 사용되며, PK나 UK 등의 제약 조건도 없다.const나 eq_ref보다 빠르진 않지만, 그래도 동등 조건으로만 비교되므로 매우 빠른 조회 방법 중 하나이다.여기까지는 마음이 편안해지는 매우 좋은 접근 방법이다.
MATCH ... AGAINST ... 구문을 사용해서 실행한다.const, eq_ref, ref를 제외한) 일반 인덱스가 함께 사용됐다면 일반적으로 MySQL은 전문 인덱스를 사용해서 처리한다.fulltext보다 일반 인덱스를 이용하는 range가 더 빨리 처리되는 경우도 많으니 조건별로 성능을 확인해보자.ref 접근 방법과 같은데, NULL 비교가 추가된 형태이다.WHERE 조건절에서 사용될 수 있는 IN(subquery) 형태의 쿼리를 위한 접근 방법이다.IN(subquery)에서 subquery가 중복된 값을 반환할 수도 있다. 이때 서브쿼리 결과의 중복된 값을 인덱스를 이용해서 제거할 수 있을 때 이 접근 방법이 사용된다.EXPLAIN
SELECT * FROM employees WHERE emp_no BETWEEN 10002 AND 10004;
<, >, IS NULL, BETWEEN, IN, LIKE 등의 연산자를 이용해 인덱스를 검색할 때 사용한다.range나 const, ref로 인덱스를 사용하지 못하는 경우.possible_keys 컬럼possible_keys 컬럼에 있는 내용은 옵티마이저가 최적의 실행 계획을 만들기 위해 후보로 선정했던 접근 방법에서 사용되는 인덱스의 목록이다.key 컬럼PRIMARY인 경우 PK를 사용한다는 의미이며, 그 이외의 값은 모두 테이블이나 인덱스를 생성할 때 부여했던 고유 이름이다.index_merge가 사용된 경우에는 여러 개의 인덱스가 ','로 구분되어 표시된다.ALL일 때와 같이 인덱스를 전혀 사용하지 못하면 값이 NULL로 표시된다.key_len 컬럼NOT NULL이 아닌, 즉 NULLABLE 컬럼에 대해서는 컬럼 값이 NULL인지 아닌지를 저장하기 위해 1바이트를 추가로 사용한다. 그래서 때로는 key_len 필드의 값이 데이터 타입의 길이보다 조금 길게 표시되는 경우도 발생할 수 있다.다음 예제는 두 개의 컬럼(dept_no, emp_no)으로 구성된 PK를 가지는 dept_emp 테이블을 조회하는 쿼리이다. 그리고 이 쿼리는 PK 중 dept_no만 비교에 사용한다.
EXPLAIN
SELECT * FROM dept_emp WHERE dept_no='d005';

dept_no 컬럼의 타입이 CHAR(4)인데, MySQL 서버는 문자 하나당 고정적으로 4바이트를 할당한다. (실제 utf8mb4 문자의 크기는 1바이트부터 4바이트까지 가변적이지만 최악의 경우로 계산하는 것.)
그러니 key_len 컬럼의 값이 16으로 표시되어 있는 건 PK에서 앞쪽 16바이트(4*4바이트)만 유효하게 사용했다는 의미이다.
위 예제와 똑같은 인덱스를 사용하지만 dept_no 컬럼과 emp_no 모두를 조건절에 사용하는 다음 쿼리가 있다고 해보자.
SELECT * FROM dept_emp WHERE dept_no='d005' AND emp_no=10001;

emp_no 컬럼은 INTEGER 타입이며, INTEGER 타입은 4바이트를 차지한다.
위 쿼리는 두 인덱스를 모두 사용했기 때문에 key_len 컬럼의 값이 dept_no 컬럼의 길이(16)와 emp_no 컬럼의 길이(4)의 합인 20으로 표시된 것이다.
이런 식으로 인덱스 컬럼 중 몇 개가 사용되었는지를 이 필드를 통해 확인할 수 있다.
ref 컬럼ref인 경우, 참조 조건(Equals 비교 조건)으로 어떤 값이 제공되었는지 부여준다.func로 표시되면 조금 주의해서 볼 필요가 있다.EXPLAIN
SELECT *
FROM employees e
JOIN dept_emp de ON e.emp_no=(de.emp_no-1);위 쿼리는 de.emp_no 값에서 1을 뺀 값으로 employees 테이블과 조인하고 있다.
ref 값으로 조인 컬럼명 대신 func가 표시되고 있음을 알 수 있다.func로 출력된다.rows 컬럼filtered 컬럼rows 컬럼 값이다.) 중에서 인덱스를 타지 않는 WHERE 조건에 의해 필터링되고 남은 레코드의 비율을 의미한다.
위 실행 계획에서 ix_firstname 인덱스 조건에 만족하는 레코드의 수는 대략 233건이며, 이중에서 16.03%의 레코드만이 인덱스를 사용하지 못하는 나머지 조건에 일치한다는 뜻이다.
즉, 필터링되고 남은 레코드 수는 대략 37(233 * 0.1603)건 정도이다. 조인을 수행할 레코드 건수가 대략 37건임을 의미한다.

위 이미지는 쿼리에서 조인 순서만 반대로 바꾼 후의 실행 계획이다. 이번에는ix_firstname 인덱스 조건에 만족하는 레코드의 수가 대략 3314건이며, 이중에서 11.11%의 레코드만이 인덱스를 사용하지 못하는 나머지 조건에 일치한다.
즉, 필터링되고 남은 레코드 수는 대략 368(3314 * 0.1111)건 정도이다. 조인 순서를 바꾸니 기존보다 10배에 가까운 조인을 해야 하는 것이다. 그러니 옵티마이저는 이 조인 순서 대신 전자를 선택할 것이다.
이처럼, filtered 컬럼의 표시되는 값이 얼마나 정확히 예측되느냐에 따라 조인의 성능이 달라진다.
그렇긴 한데 이런 예측은 옵티마이저가 알아서 하는 거라 쿼리 최적화와는 큰 관련이 없다.....
Extra 컬럼const 접근 방법으로 테이블을 읽었지만 실제로 해당 테이블에 레코드가 1건도 존재하지 않았다는 뜻이다.col1 IN (SELECT col2 FROM ...) 과 같은 쿼리에서 자주 발생한다.col1이 NULL이면 서브쿼리에 사용된 테이블에 대해서 풀 테이블 스캔을 할 것임을 알려주는 것이다.col1이 NOT NULL로 정의된 컬럼이라면 표시되지 않는다.WHERE절에 col1 IS NOT NULL이라는 조건을 지정함으로써 이 문장이 표시되지 않도록 할 수 있다.SELECT *
FROM tb_test1
WHERE col1 IS NOT NULL // col1이 NULL이면 후속 조건이 아예 실행되지 않는다.
AND col IN (SELECT col2 FROM tb_test2);col1 중에서 NULL인 값이 하나도 없다면 풀 테이블 스캔은 발생하지 않으니 걱정할 필요 없다.HAVING 절 또는 WHERE절의 조건을 만족하는 레코드가 없다는 뜻이다.MIN()이나 MAX()와 같은 집합 함수가 있는 쿼리의 조건절에 일치하는 레코드가 한 건도 없다는 뜻이다.NULL을 반환할 것이다.EXPLAIN
SELECT *
FROM dept_emp de
JOIN (SELECT emp_no FROM employees WHERE emp_no=0) tb1 ON tb1.emp_no=de.emp_no
WHERE de.dept_no='d005';
위 쿼리에서 JOIN절 서브쿼리 결과 일치하는 레코드가 없다면 이 문장이 표시될 것이다.
UPDATE 또는 DELETE 명령의 실행 계획에서 표시될 수 있다.UPDATE하거나 DELETE할 대상 레코드가 없다는 뜻이다.SELECT 1; 같은 쿼리.https://dev.mysql.com/doc/refman/8.0/en/explain-for-connection.html
EXPLAIN FOR CONNECTION 명령을 실행했을 때 표시될 수 있다.SHOW PROCESSLIST; // <process_id> 확인
EXPLAIN FOR CONNECTION <process_id>;일반 EXPLAIN은 쿼리 실행 중 이렇게 할 예정이라고 말해주는 일종의 계획표다.EXPLAIN FOR CONNECTION은 이미 실행 중인 살아있는 쿼리 세션이 실제로 쓰고 있는 실행 계획을 훔쳐볼 수 있는 명령이다. 한 마디로 CCTV 같은 친구.Plan isn't ready yet은 해당 커넥션에서 아직 쿼리의 실행 계획을 수립하지 못한 상태에서 EXPLAIN FOR CONNECTION 명령이 실행된 것을 의미한다.MIN() 또는 MAX()만 SELECT 절에 사용되거나, GROUP BY로 MIN(), MAX()를 조회하는 쿼리가 인덱스를 오름차순 또는 내림차순으로 1건만 읽는 형태의 최적화가 적용된다는 뜻이다.ORDER BY 처리가 인덱스를 사용하지 못함을 의미한다.Using filesort가 출력되는 쿼리는 많은 부하를 일으키므로 가능하다면 퀴리를 튜닝하거나 인덱스를 생성하는 것이 좋다.1. 타이트 인덱스 스캔을 통한 GROUP BY 처리
GROUP BY 절을 처리할 수 있더라도 AVG(), SUM(), COUNT()와 같이 조회하려는 값이 모든 인덱스를 다 읽어야 하는 경우.2. Loose Index Scan을 통한 GROUP BY 처리
GROUP BY 절이 인덱스를 사용할 수 있으면서 MIN()이나 MAX()와 같이 조회하는 값이 인덱스의 첫 번째 또는 마지막 레코드만 읽어도 되는 쿼리인 경우.1. WHERE 조건절이 없는 경우
GROUP BY 절의 컬럼과 SELECT로 가져오는 컬럼이 Loose Index Scan을 사용할 수 있는 조건만 갖추면 된다.2. WHERE 조건절이 있지만 검색을 위해 인덱스를 사용하지 못하는 경우
3. WHERE 절의 조건이 있고, 검색을 위해 인덱스를 사용하는 경우
WHERE 절의 조건과 GROUP BY 처리가 똑같은 인덱스를 공통으로 사용할 수 있을 때만 Loose Index Scan을 사용할 수 있다.WHERE 절의 조건과 GROUP BY 처리가 사용할 수 있는 인덱스가 다른 경우 일반적으로 옵티마이저는 WHERE 조건절이 인덱스를 사용하도록 실행 계획을 수립하는 경향이 있기 때문.WHERE 조건에 의해 검색된 레코드 건수가 적으면 Loose Index Scan을 사용하지 않아도 매우 빠르게 처리될 수 있기 때문에 옵티마이저가 적절히 판단하여 사용하지 않을 수도 있다.조인 버퍼가 사용되는 실행 계획을 의미한다.
join_buffer_size라는 시스템 변수에 최대로 할당 가능한 조인 버퍼 크기를 설정할 수 있다.조인 조건이 없는 카테시안 조인을 수행하는 쿼리는 항상 조인 버퍼를 사용한다.
만약 조인 시 인덱스를 탈 것을 기대했는데 이 메시지가 출력됐다면, 조인 시 인덱스를 안 타고 있다는 뜻이니 확인이 필요하다.