[Real MySQL 10장] 실행 계획

정훈희·2023년 9월 13일
0

Real MySQL

목록 보기
5/5
post-thumbnail

통계 정보

통계 정보는 MySQL의 실행 계획에 가장 큰 영향을 미친다.

통계 정보를 테이블 및 인덱스에 대한 통계 정보히스토그램으로 나누어 살펴보자.

테이블 및 인덱스 통계 정보

  • MySQL 서버의 통계 정보
    MySQL 5.5 버전 까지는 각 테이블의 통계 정보가 메모리에서만 관리되어서 서버 재시작시 모두 사라졌다.
    이후의 버전에서는 각 테이블의 통계 정보를 mysql 데이터베이스의 innodb_index_stats 테이블과 innodb_index_stats 테이블로 관리할 수 있게되어 재시작 되어도 통계 정보가 유지된다.
    특정 테이블의 통계 정보를 영구적으로 관리하고 싶지 않을 경우 테이블 생성 시 STATS_PERSISTENT를 0으로 설정하면 된다.
    STATS_PERSISTENT를 설정하지 않으면 innodb_stats_persistent 시스템 변수의 값에 따라 결정한다. (ON이면 영구 저장, OFF면 영구 저장 X)
    // employees 테이블의 인덱스 통계 정보
    SELECT *
    FROM innodb_index_stats
    WHERE database_name='employees'
    	AND TABLE_NAME='employees';

image
employees 테이블에 있는 인덱스들의 통계 정보는 위와 같이 저장되어 있다.

  • stat_name=’n_diff_pfx%’: 인덱스가 가진 유니크한 값의 개수
  • stat_name=’n_leaf_pages’: 인덱스의 리프 노드 페이지 개수
  • stat_name=’size’: 인덱스 트리의 전체 페이지 개수
    // employees 테이블의 통계 정보
    SELECT *
    FROM innodb_table_stats
    WHERE database_name='employees'
    	AND TABLE_NAME='employees';

image
employees 테이블의 통계 정보는 위와 같이 저장되어 있다.

  • n_rows: 테이블의 전체 레코드 건수
  • clustered_index_size: 프라이머리 키의 크기(InnoDB 페이지 개수)
  • sum_of_other_index_sizes: 프라이머리 키를 제외한 인덱스의 크기(InnoDB 페이지 개수)

통계 정보는 아래와 같은 이벤트들이 발생하면 갱신된다.

  • 테이블이 새로 오픈되는 경우
  • 테이블의 레코드가 대량으로 변경되는 경우
  • ANALYZE TABLE 명령이 실행되는 경우
  • SHOW TABLE STATUS 명령이나 SHOW INDEX FROM 명령이 실행되는 경우 하지만 갑자기 통계 정보가 변경되면 의도치 않게 실행 계획이 변경되는 문제가 발생할 수 있는데, innodb_stats_auto_recalc 시스템 변수의 값을 OFF로 설정하면 이를 막을 수 있다.
    또한, 통계 정보를 자동으로 수집할지 여부도 옵션을 통해 테이블 단위로 조정할 수 있다.
    통계 정보를 수집할 때 몇 개의 InnoDB 테이블 블록을 샘플링할지 설정하는 시스템 변수가 2개있다.
    • innodb_stats_transient_sample_pages: 기본값=8, 자동으로 통계가 수집될 때 8개의 페이지만 분석하여 통계 정보로 활용함을 의미한다.
    • innodb_stats_persistent_sample_pages: 기본값=20, ANALYZE TABLE 명령이 실행되면 20개의 페이지만 분석하여 통계 정보로 활용함을 의미한다.

→ 정확도를 높히고 싶다면 위 시스템 변수 값을 올리면 되지만, 통계 수집 시간이 길어지므로 주의해야한다.

히스토그램

기존 통계 정보만으로는 최적의 실행 계획을 수립하기에는 많이 부족했다.

MySQL 8.0부터는 컬럼의 데이터 분포도를 참조할 수 있는 히스토그램을 활용할 수 있다.

히스토그램 정보는 컬럼 단위로 관리되는데, 이는 자동으로 수집되지 않고 ANALYZE TABLE … UPDATE HISTOGRAM 명령을 실행해 수동으로 수집된다.

이 히스토그램 정보를 조회하려면 column_statistics 테이블을 SELECT하면 된다.

히스토그램은 싱글톤 히스토그램, 높이 균형 히스토그램 두 가지가 지원된다.

코스트 모델

전체 쿼리의 비용을 계산하는데 필요한 단위 작업들의 비용을 코스트 모델이라고 한다.

MySQL의 코스트 모델은 다음 2개 테이블에 저장되어 있는 설정값을 사용한다.

  • server_cost: 인덱스를 찾고 레코드를 비교하고 임시 테이블 처리에 대한 비용 관리
  • engine_cost: 레코드를 가진 데이터 페이지를 가져오는데 필요한 비용 관리

두 테이블은 아래 5개의 컬럼을 공통으로 가지고있다.

  • cost_name: 코스트 모델의 각 단위 작업
  • default_value: 각 단위 작업의 비용(기본값, MySQL 서버 소스 코드에 설정된 값)
  • cost_value: DBMS 관리자가 설정한 값(NULL이면 default_value 값 사용)
  • last_updated: 단위 작업의 비용이 변경된 시점
  • comment: 비용에 대한 추가 설명

engine_cost 테이블은 아래 2개의 컬럼을 더 가지고 있다.

  • engine_name: 비용이 적용된 스토리지 엔진
  • device_type: 디스크 타입

단위 작업의 종류는 아래와 같이 8개가 있다.

테이블 이름cost_namedefault_value설명
engine_costio_block_read_cost1디스크 데이터 페이지 읽기
engine_costmemory_block_read_cost0.25메모리 데이터 페이지 읽기
server_costdisk_temptable_cost20디스크 임시 테이블 생성
server_costdisk_temptable_row_cost0.5디스크 임시 테이블의 레코드 읽기
server_costkey_compare_cost0.05인덱스 키 비교
server_costmemory_temptable_create_cost1메모리 임시 테이블 생성
server_costmemory_temptable_row_cost0.1메모리 임시 테이블의 레코드 읽기
server_costrow_evaluate_cost0.1레코드 비교

코스트 모델은 각 단위 작업에 설정되는 비용이 커지면 어떤 실행 계획의 비용이 변하는지 파악하는 것이 중요하다.

웬만하면 위 테이블들의 default_value를 바꾸지 말자


실행 계획 확인

MySQL의 실행 계획은 DESC 또는 EXPLAIN 명령으로 확인할 수 있다.

실행 계획의 포맷은 아래와 같이 테이블, 트리, JSON 3가지 중 하나를 선택할 수 있다.

  • EXPLAIN [FORMAT=TREE or JSON] (테이블이 기본값)

쿼리의 실행 시간 확인

EXPLAIN ANALYZE 명령으로 쿼리의 실행 계획과 단계별 소요된 시간 정보를 확인할 수 있다.

EXPLAIN ANALYZE
SELECT e.emp_no, avg(s.salary)
FROM employees e
	INNER JOIN salaries s ON s.emp_no=e.emp_no
						 AND s.salary>50000
						 AND s.from_date<='1990-01-01'
						 AND s.to_date>'1990-01-01'
WHERE e.first_name='Matt'
GROUP BY e.hire_date;

위 쿼리의 결과는 아래와 같다.

A) -> Table scan on <temporary> (actual time=0.001..0.004 rows=48 loops=1)
B)     -> Aggregate using temporary table (actual time=3.799. .3.808 rows=48 loops=1)
C)         -> Nested loop inner join (cost=685.24 rows=135)
                         (actual time=0.367..3.602 rows=48 loops=1)
D)             -> Index lookup on e using ix_firstname (first_name='Matt') (cost=215.08 rows=233)
                         (actual time 0.348..1.046 rows=233 loops=1)
E)             -> Filter: ((s.salary > 50000) and (s.from_date <= DATE' 1990-01-01')
																			and (s.to_date > DATE' 1990-01-01')) (cost=0.98 rows=1)
                         (actual time 0.009..0.011 rows=0 loops=233)
F)                 -> Index lookup on s using PRIMARY (emp_no=e.emp_no) (cost=0.98 rows=10)
                         (actual time=0.007..0.009 rows=10 loops=233)

위와 같은 TREE 포맷의 실행 계획에서 들여쓰기는 호출 순서를 의미하며 규칙은 아래와 같다.

  • 들여쓰기가 같은 레벨에서는 상단에 위치한 라인이 먼저 실행
  • 들여쓰기가 다른 레벨에서는 가장 안쪽에 위치한 라인이 먼저 실행

위 실행 계획을 풀어서 설명하면

  1. D, employees 테이블의 ix_firstname 인덱스를 통해 first_name=’Matt’ 조건에 일치하는 레코드를 찾는다.
  2. F, salaries 테이블의 PK를 이용해 1번 결과의 emp_no와 같은 emp_no를 가진 레코드를 s테이블에서 찾는다.
  3. E, 2번 결과를 s.salary > 50000 and s.from_date <= DATE'1990-01-01' and s.to_date > DATE'1990-01-01' 조건으로 필터링한다.
  4. C, 1번 결과와 3번 결과를 조인한다.
  5. B, 임시 테이블에 결과를 저장하며 GROUP BY 집계를 실행한다.
  6. A, 임시 테이블의 결과를 읽어서 결과를 반환한다.
F) -> Index lookup on s using PRIMARY (emp_no=e.emp_no) (cost=0.98 rows=10)
         (actual time=0.007..0.009 rows=10 loops=233)

실행 계획의 F라인을 자세히 분석해보자.

  • actual time=0.007 ..0.009
    employees 테이블에서 읽은 emp_no 값을 기준으로 salaries 테이블에서 일치하는 레코드를 검색하는 데 걸린 시간(밀리초)을 의미한다.
    첫 번째 숫자는 첫 번째 레코드를 가져오는 데 걸린 평균 시간, 두 번째 숫자는 마지막 레코드를 가져오는데 걸린 평균 시간을 의미한다.
  • rows=10
    employees 테이블에서 읽은 emp_no에 일치하는 salaries테이블의 평균 레코드 건수를 의미한다.
  • loops=233
    employees테이블에서 읽은 emp_no를 이용해 salaries테이블의 레코드를 찾는 작업이 반복된 횟수를 의미한다.
    employees테이블에서 읽은 emp_no의 개수가 233개임을 의미한다.

salaries 테이블에서 emp_no일치 건을 찾는 작업을 233번 반복했는데, 매번 salaries 테이블에서 첫 번째 레코드를 가져오는데 0.007 밀리초가, 10개의 레코드를 모두 가져오는 데 0.009 밀리초가 걸린 것이다.

실행 계획 분석

  • id 컬럼: 실행 계획의 id 컬럼은 단위 SELECT 쿼리별로 부여되는 식별자 값이다. 만약 하나의 SELECT에서 여러 테이블을 조인하면 조인 테이블 수 만큼 실행 계획 레코드가 출력되지만 같은 id가 부여된다. id가 같은 레코드들은 조인이 레코드 순서대로 실행된다.
  • select_type 컬럼: 각 단위 SELECT 쿼리가 어떤 타입의 쿼리인지 표시되는 컬럼이다.
    • SIMPLE
      UNION이나 서브쿼리를 사용하지 않는 단순한 SELECT 쿼리의 경우 select_typeSIMPLE이다.
      SIMPLE 쿼리는 하나만 존재하고, 일반적으로 제일 바깥 SELECT 쿼리이다.
    • PRIMARY
      UNION이나 서브쿼리를 사용하는 SELECT 쿼리의 가장 바깥쪽 쿼리는 select_typePRIMARY이다.
    • UNION
      UNION으로 결합하는 단위 SELECT 쿼리 가운데 첫 번째 이후 쿼리의 select_typeUNION으로 표시된다. 첫 번째 쿼리는 DERIVED로 표시된다. 왜냐하면 여러 쿼리의 결과를 합치기 위한 임시 테이블이 필요하기 때문이다.
    • DEPENTENT UNION
      이 경우도 UNION으로 결합하는 쿼리에서 표시되지만, 외부 쿼리의 영향을 받는 경우를 말한다.
    • UNION RESULT
      결과를 버퍼링 하는 임시 테이블의 select_typeUNION RESULT이다.
    • SUBQUERY
      FROM절 이외에서 사용되는 서브쿼리만을 의미한다.
      FROM절에 사용된 서브쿼리는 DERIVED로 표시된다.
    • DEPENTENT SUBQUERY
      서브쿼리가 바깥쪽에 정의된 컬럼을 사용하는 경우 표시된다.
    • DERIVED
      DERIVED는 단위 SELECT 쿼리의 결과로 메모리나 디스크에 임시 테이블을 생성하는 것을 의미한다.
    • DEPENTENT DERIVED
      LATERAL JOIN을 통해 FROM절 서브쿼리에서 외부 컬럼을 참조할 수 있는데, 이때 나오는 select_type이다.
    • UNCACHEABLE SUBQUERY
      같은 서브쿼리가 여러번 실행될 때는 이전 실행 결과를 그대로 사용하도록 서브쿼리의 결과를 캐시한다.
      하지만 캐시를 사용하지 못하는 경우 select_typeUNCACHEABLE SUBQUERY이다.
    • UNCACHEABLE UNION
      UNION + UNCACHEABLE
    • MATERIALIZED
      주로 FROM절이나 IN(subquery) 형태의 쿼리에 사용된 서브쿼리의 최적화를 위해 사용된다.
      서브쿼리 내용을 임시 테이블로 구체화한 뒤 임시 테이블과 employees 테이블을 조인하는 형태로 최적화되어 처리된다.
  • table 컬럼: 실행 계획은 테이블 기준으로 표시된다. table 컬럼에는 실행 계획의 테이블 이름이 들어간다. , <union M,N> 과 같이 “<>”로 둘러싸인 경우는 임시 테이블을 의미하고, 안의 숫자는 id값을 지칭한다. image 만약 위와 같은 실행 계획이 있다고 하면 <derived2>의 경우는 id가 2인 SELECT의 결과를 의미한다. 즉, 2번 SELECT문에서 생성된 임시테이블과 e테이블을 조인한 것이 결과가 된다. select_typeMATERIALIZED인 실행 계획에서는 <subquery N>과 같은 값이 표시된다.
  • partitions 컬럼: 불필요한 파티션을 빼고 쿼리를 수행하기 위해 접근해야 할 테이블만 골라내는 과정을 파티션 프루닝이라 한다. 파티션 컬럼은 해당 쿼리가 어느 파티션에 접근했다는 것을 알려준다.
  • type 컬럼: type 컬럼은 각 테이블의 접근 방법이라고 생각하면 된다. 총 12가지 방법이 있으며, ALL을 제외한 나머지 타입은 모두 인덱스를 사용한다.
    • system: 레코드가 1건만 존재하는 테이블 또는 한 건도 존재하지 않는 테이블을 참조하는 형태의 방법을 말한다. InnoDB 스토리지 엔진을 사용하는 테이블에서는 나타나지 않는다.
    • const: PK나 유니크키 컬럼을 이용하는 WHERE절을 가지고 있고, 1건만 반환하는 방식의 쿼리를 말한다. type이 const인 실행 계획은 옵티마이저가 쿼리를 최적하며 먼저 실행해서 통째로 상수화한다. 예를들어 SELECT name FROM user WHERE id=1; 이런 서브쿼리가 있다면 이 서브쿼리를 통째로 ‘name1'으로 상수화 하는 것이다.
    • eq_ref: 조인에서 처음 읽은 테이블의 컬럼값을 다음 읽을 테이블의 PK나 유니크키 컬럼 검색 조건에 사용할 때의 접근 방법을 eq_ref라고 한다. 즉, 조인에서 두 번째 이후에 읽는 테이블에 조건에 맞는 레코드가 1건만 존재한다는 보장이 있어야한다.
    • ref: ref 접근 방법은 인덱스의 종류와 상관없이 동등 조건으로 검색할 때 사용된다. 레코드가 반드시 1건이란 보장이 없으므로 consteq_ref보다 느리지만 매우 빠른 조회 방법이다.
    • fulltext: fulltext 접근 방법은 전문 검색 인덱스를 사용해 레코드를 읽는 방법을 의미한다.
    • ref_or_null: ref 방식 또는 NULL비교 접근 방법을 의미한다. 잘 사용되진 않지만 나쁘지 않은 접근 방법이다.
    • unique_subquery: WHERE절에서 사용될 수 있는 IN(subquery) 형태의 쿼리를 위한 접근 방법이다. 말 그대로 서브쿼리에서 중복되지 않은 값만 반환할 때 이 방법을 사용한다.
    • index_subquery: unique_subquery와 비슷하지만 중복된 값이 있을 수 있어서 중복 제거 작업이 필요한 경우 사용되는 접근 방법이다. MySQL 8.0 버전에서는 세미조인을 최적화 하는 많은 기능이 생겨 unique_subqueryindex_subquery은 잘 보이지 않는다.
    • range: 인덱스 레인지 스캔 형태의 접근 방법이다. 나쁘지 않은 접근 방법이다.
    • index_merge: 여러개의 인덱스를 이용해 검색 결과를 만들어낸 뒤 그 결과를 병합해서 처리하는 방식이다. 이 실행 계획은 여러 인덱스를 읽어야하고, 두 결과를 가지고 교집합, 합집합, 중복 제거와 같은 부가적인 작업이 더 필요하기 때문에 그렇게 효율적이지 않다. index_merge 접근 방법이 이용되면 Extra 컬럼에 추가적인 내용이 표시된다.
    • index: index 접근 방법은 인덱스 풀 스캔을 의미한다. LIMIT 조건이 없거나 가져올 레코드 건수가 많으면 상당히 느린 처리를 수행한다.
    • ALL: 풀 테이블 스캔을 의미하는 접근 방법이다. 가장 비효율적인 방법이다.
  • possible_keys 컬럼: 옵티마이저가 사용을 고려했던 인덱스의 목록들을 담고있는 컬럼이다. 즉, 사용되지 않은 인덱스들이 들어있고, 그냥 무시해도 되는 컬럼이다.
  • key 컬럼: 옵티마이저가 최종으로 선택한 인덱스를 담고있는 컬럼이다.
  • key_len 컬럼: key_len 컬럼은 ****쿼리를 처리하기 위해 다중 컬럼으로 구성된 인덱스에서 몇 바이트 까지 썼는지를 의미한다.
  • ref 컬럼: 접근 방법이 ref면 참조 조건으로 어떤 값이 제공됐는지 보여준다. 상숫값을 지정했다면 const, 다른 테이블의 컬럼 값이면 그 테이블이름과 컬럼이름이 표시된다. ref 컬럼의 값이 func면 값에 연산을 거쳐서 참조했다는 것을 의미한다.
  • rows 컬럼: rows 컬럼은 인덱스를 사용하는 조건에만 일치하는 레코드 건수를 예측한 값이다.
  • filtered 컬럼: fintered 컬럼은 인덱스를 사용한 조건으로 걸러진 레코드들 중 인덱스를 사용하지 못하는 조건으로 인해 필터링되고 남은 레코드의 비율을 의미한다. 즉, rows가 233이고, filtered가 16.03이면 결과 레코드 건수는 233 * 0.1603 = 37이 된다.
  • Extra 컬럼: 쿼리의 실행 계획에서 성능에 관련된 중요한 내용이 Extra 컬럼에 자주 표시된다.
    • const row not found: const 접근 방법으로 테이블을 읽었지만 레코드가 1건도 없을 때 표시된다.
    • Distinct:
      SELECT DISTINCT d.dept_no
      FROM departments d, dept_emp de WHERE de.dept_no=d.dept_no;
      image Extra 컬럼에 Distinct가 표시되면 위와 같이 처리된다. 만약 departments 테이블을 조회하는데, 조건에 dept_emp 테이블에 존재하는 dept_no를 가져야 하는 경우 위와 같이 일부 레코드만 조인한다.
    • FirstMatch, LooseScan: 세미조인 전략 중 FirstMatch, LooseScan 전략이 사용되면 표시된다.
    • Full scan on NULL key: col1 IN (SELECT col2 FROM …) 과 같은 조건을 가진 쿼리의 실행 계획에서 표시될 수 있다. Full scan on NULL key는 위와 같은 쿼리에서 col1NULL일때 발생할 수 있고, 결과를 가지는지 확인하기 위해 서브쿼리 테이블에 대해 풀 스캔을 할 것이라는 것을 의미한다.
    • Impossible HAVING, WHERE: HAVING, WHERE절의 조건이 무조건 FALSE가 나오는 경우 표시된다.
    • No matching min/max row: MIN이나 MAX함수의 대상이 NULL일 때 표시된다.
    • no matching row in const table: const 방법으로 접근할 때 일치하는 레코드가 없을 때 표시된다.
    • No matching rows after partition pruning: 파티션된 테이블에 대한 UPDATE or DELETE 명령의 실행 계획에서 해당 파티션이 없을 때 표시된다.
    • Not exists: A 테이블에는 존재하지만 B 테이블에는 없는 값을 조회할 경우 NOT IN(subquery) 나 NOT EXISTS 연산자를 주로 사용하는데, 이러한 형태의 조인을 안티 조인이라고 한다.
      SELECT *
      FROM dept_emp de
      	LEFT JOIN departments d ON de.dept_no=dept_no
      WHERE d.dept_no IS NULL;
      레코드 건수가 많을 때는 위와 같이 아우터 조인을 이용해서 안티 조인을 구현하는 것이 빠른데, 이렇게 아우터 조인을 이용해 안티 조인을 수행하는 쿼리의 경우 Not exists가 표시된다. Not exists의 의미는 옵티마이저가 조인 시 departments 테이블의 레코드가 존재여부만 판단한다는 것을 의미한다. 즉, 조건에 일치하는 레코드가 여러 건 있어도 1건만 조회해보고 처리를 완료하는 최적화를 의미한다.
    • Plan ins’t ready yet: 아직 쿼리의 실행 계획이 수립되지 않았을 때 표시된다.
    • Range checked for each record(index map: N):
      SELECT *
      FROM employees e1, employees e2
      WHERE e2.emp_no >= e1.emp_no;
      위 쿼리는 WHERE절의 조인 조건에 변수만 있기 때문에 인덱스 레인지 스캔과 풀 테이블 스캔 중 어느 것이 효율적인지 판단할 수 없다. 즉, 의미 그대로 레코드 마다 인덱스 레인지 스캔을 체크한다는 뜻이다. index map: N 의 경우는 사용할 인덱스의 후보에 대한 정보를 담고있다.
    • Recursive: CTE를 이용한 재귀 쿼리의 실행 계획의 Extra 컬럼에서 표시된다.
    • Rematerialize: 래터럴 조인 시 조인되는 테이블은 선행 테이블의 레코드별로 서브쿼리를 실행해서 그 결과를 임시 테이블에 저장하는데, 이 과정을 Rematerialining이라고 한다. 결국 각 레코드 마다 새 내부 임시 테이블이 생성되는데 이때 Rematerialize가 표시된다.
    • Select tables optimized away: MIN() 또는 MAX()SELECT 절에 사용되거나 GROUP BYMIN(), MAX()를 조회하는 쿼리가 인덱스를 이용해 1건만 읽는 형태의 최적화가 적용되면 표시된다.
    • Start temporary, End temporary: 세미 조인 최적화 중 Duplicate Weed-out 전략이 사용되면 표시된다. 이 전략은 중복제거를 위해 내부 임시 테이블을 사용하는데, 어떤 테이블들이 조인되어 임시 테이블에 저장되는지 알 수 있게 첫 테이블에 Start temporary, 마지막 테이블에 End temporary를 표시한다.
    • unique row not found: 두 개의 테이블이 각각 유니크 컬럼으로 아우터 조인을 수행하는 쿼리에서 아우터 테이블에 일치하는 레코드가 존재하지 않을 때 표시된다.
    • Using filesort: ORDER BY 처리가 인덱스를 사용하지 못할 때 표시된다. Using filesort가 표시되는 쿼리는 많은 부하를 일으키므로 튜닝이 필요하다.
    • Using index(커버링 인덱스): 인덱스만 읽어서 쿼리를 모두 처리할 수 있을 때(커버링 인덱스) 표시된다. 인덱스로만 쿼리를 처리할 수 있으면 디스크 접근이 필요 없어지므로 성능이 향상된다.
    • Using index condition: 옵티마이저가 인덱스 컨디션 푸시 다운 최적화를 사용하면 표시된다.
    • Using index for group-by: 인덱스를 사용하여 GROUP BY 처리를 수행하면 별도의 정렬 작업이 필요 없어지고, 인덱스의 필요한 부분만 읽으면(루스 인덱스 스캔) 되므로 성능이 향상되는데, 이 때 Using index for group-by메시지가 표시된다. 인덱스를 이용하여 GROUP BY를 처리할 수 있더라도 AVG(), SUM() 처럼 조회하려는 값이 모든 인덱스를 다 읽어야 할 경우 루스 인덱스 스캔이 불가능하다. 이 경우에는 Using index for group-by메시지가 표시되지 않는다. 참고로, 루스 인덱스 스캔은 대량의 레코드를 GROUP BY 하는 경우엔 성능 향상효과가 있지만 레코드 건수가 적으면 루스 인덱스 스캔을 사용하지 않아도 빠르게 처리가 가능하므로 무조건 좋은 것은 아니다.
    • Using index for skip scan: 인덱스 스킵 스캔 최적화를 사용할 때 표시된다.
    • Using join buffer (hash join, Batched Key Access): 조인 버퍼가 사용되는 실행 계획에 표시된다.
    • Using MRR: MRR 최적화를 사용할 경우 표시된다.
    • Using union, sort_union, intersect: index_merge 접근 방법으로 실행되는 경우 어떤 방식의 index_merge인지 알려주기 위해 표시된다.
    • Using temorary: 쿼리 실행 시 임시 테이블이 생성되면 표시된다. (표시 안되도 생성되는 경우도 있음)
    • Using where: MySQL 엔진 레이어에서 필터링 작업을 처리한 경우 표시된다.
profile
DB를 사랑하는 백엔드 개발자입니다. 열심히 공부하고 열심히 기록합니다.

0개의 댓글