컴퓨터 과학/[Study] Real MySQL 8.0

Chapter 09.04 쿼리 힌트

계란💕 2023. 1. 23. 18:10

 

  • MySQL 서버는 인간이 원하는 서비스를 100%이해하지는 못한다. 
  • 따라서 서비스 개발자 또는 DBA보다 MySQL서버가 부족한 실행 계획을 수립할 때가 있다. 
  • RDBMS에서는 이러한 목적으로 힌트라는 기능이 제공된다.
  • MySQL서버에서 가능한 쿼리 힌트는?
    • 인덱스 힌트: 예전 버전에서 사용되던 힌트들  ex) USE INDEX, STRAIGHT_JOIN
    • 옵티마이저 힌트  ex) MySQL 5.6 부터 새롭게 추가되기 시작한 힌트들을 뜻한다. STRAIGHT_JOIN 도 포함한다.  

 

 

 

9.4.1 인덱스 힌트

  • USE INDEX, STRAIGHT_JOIN 등을 포함한 인덱스 힌트들은 모두 MySQL 서버에 옵티마이저 힌트가 도입되기 전에 사용되던 기능들이다. 
  • 이들은 모두 SQL 문법에 맞게 사용해야되므로 사용하게 되면 ANSI-SQL 표준 문법을 준수하지 못하게 되는 단점이 있다. 
    • ANSI-SQL(American National Standards Institute): 미국 표준 협회(ANSI)에서 각기 다른 DBMS를 표준화해서 정립한 표준 SQL을 말한다
  • MySQL 5.6 버전 부터 추가되기 시작한 옵티마이저 힌트들은 모두 MySQL 서버를 제외한 다른 DBMS 에서는 주석으로 해석하기 때문에 ANSI-SQL 표준을 준수한다고 볼 수 있다. 
  • 따라서, 가능한 인덱스 힌트보다는 옵티마이저 힌트를 사용하는 게 좋다.
  • 인덱스 힌트는 SELECT, INSERT 명령에서만 사용 가능하다. 

 

 

 

 

9.4.1.1 STRAIGHT_JOIN

  • STRAIGHT_JOIN은 옵티마이저 힌트로도 쓰이고 조인 키워드로 쓰이기도 하다. 
  • 조인 키워드로 쓰인는 경우는 SELECT, UPDATE, DELETE 쿼리에서 여러 개의 테이블이 조인되는 경우 조인 순서를 고정하는 역할을 한다.

 

 

  Ex)

SELECT *
FROM employees e, dept_emp de, departments d
WHERE e.emp_no = de.emp_no AND  d.dept_no = de.dept_no;
  • 위 쿼리는 어느 테이블이 드라이빙이 되고 어느 게 드리븐이 될 지 알 수 없다. 옵티마이저가 그때 그때 각 테이블의 통계 정보, 쿼리 조건을 기반으로 최적이라고 판단하는 순서로 조인한다. 
  • 쿼리의 실행 계획을 조회해보면
    • 드라이빙 테이블: departments   (테이블의 레코드 건수가 가장 적어서 선택됨)
    • 두 번째: dept_emp
    • 세 번째: employee
  • 조인하기 위한 칼럼들의 인덱스 여부로 조인 순서가 결정되고 인덱스에 문제가 없는 경우는 (WHERE 조건이 있는 경우는 WHERE 조건을 만족하는) 레코드가 적은 테이블을 드라이빙 테이블로 정한다. 
  • 만약 조인 순서를 바꾸려면? STRAIGHT_JOIN 힌트를 사용할 수 있다. 

 

 

  Ex)

SELECT STRAIGHT_JOIN 
	e.first_name, e.last_name, d.dept_name
FROM employees e, dept_emp de, departments d
WHERE e.emp_no = de.emp_no
	AND d.dept_no = de.dept_no;
    
SELECT /*! STRAIGHT_JOIN*/ 
	e.first_name, e.last_name, d.dept_name
FROM employees e, dept_emp de, departments d
WHERE e.emp_no = de.emp_no
	AND d.dept_no = de.dept_no;

 

  • 위의 두 쿼리는 힌트의 표기법만 다를 뿐 동일한 쿼리이다. 
  • SELECT  바로 뒤에 STRAIGHT_JOIN를 사용한다. 
  • STRAIGHT_JOIN 힌트는 옵티마이저가 FROM절에 명시된 테이블의 순서대로 조인을 수행하도록 유도한다. 

 

 

 

어느 테이블을 드라이빙 테이블로 정하는 게 좋을까? 

  1. 임시 테이블(인라인 뷰 또는 파생된 테이블)과 일반 테이블의 조인
    • 임시 테이블을 선정하는 게 좋다. 
    • 일반테이블의 조인 컬럼에 인덱스가 없는 경우에는 레코드 건수가 작은 쪽을 먼저 읽도록 드라이빙으로 선택하는 게 좋다. 
    • 대부분 옵티마이저가 적절한 조인 순서를 선택했기 때문에 쿼리를 작성할 때부터 힌트를 사용할 필요는 없다. 
    • 옵티마이저가 실행 계획을 제대로 수립하지 못해서 심각한 성능 저하가 있는 경우에는 힌트를 사용하면 된다. 
  2. 임시 테이블끼리 조인
    • 임시 테이블(서브쿼리로 파생된 테이블)은 항상 인덱스가 없기 때문에 어느 테이블을 먼저 드라이빙으로 읽어도 무관하므로 크기가 작은 테이블을 드라이빙으로 선택해주는 게 좋다. 
  3. 일반 테이블끼리 조인
    • 양쪽 테이블 모두 조인 칼럼에 인덱스가 있거나 양쪽 테이블 모두 조인 칼럼에 인덱스가 없는 경우에는 레코드 건수가 적은 테이블을 드라이빙으로 선택하는 게 좋으며 그 이외의 경우에는 조인 칼럼에 인덱스가 없는 테이블을 드라이빙으로 선택하는 게 좋다. 
    • 여기서의 레코드 건수는 인덱스를 사용할 수 있는 WHERE 조건까지 포함해서 그 조건을 만족하는 레코드 건수를 의미하는 것이다. 

 

 

STRAIGHT_JOIN 힌트와 비슷한 효과를 내는 옵티마이저 힌트 종류

  • JOIN_FIXED_ORDER: STRAIGHT_JOIN(한 번 사용되면 FROM 절의 모든 테이블에 대해 조인 순서가 결정되는 효과) 힌트와 동일하다.
  • 아래 세 개의 옵티마이저 힌트는 STRAIGHT_JOIN과는 다르게 일부 테이블의 조인 순서에 대해서만 제안하는 힌트이다. 
  • JOIN_ORDER
  • JOIN_PREFIX
  • JOIN_SUFFIX

 

 

 

9.4.1.2 USE INDEX / FORCE INDEX / IGNORE INDEX

  • 인덱스 힌트는 STRAIGHT_JOIN 힌트와는 다르게 사용하려는 인덱스를 가지는 테이블 뒤에 힌트를 명시해야 한다. 
  • 옵티마이저는 어떤 인덱스를 사용할지 무난하게 선택하는 편이지만 3~4개 이상의 칼럼을 포함하는 비슷한 인덱스가 여러 개 존재하는 경우는 옵티마이저가 실수를 하기도 한다. 
  • 이런 경우에는 강제로 특정 인덱스를 사용하도록 힌트를 추가한다. 
  • 이런 인덱스를 쓰더라도 옵티마이저가 실수하는 경우도 있다. 

 

 

 

인덱스 힌트 종류

  • USE INDEX
    • 가장 자주 사용된다. 
    • 옵티마이저에게 특정 테이블의 인덱스를 사용하도록 권장한다. 
    • 대부분의 경우, 인덱스 힌트가 주어지면 옵티마이저는 사용자의 힌트를 채택하지만 항상 그 인덱스를 사용하는 것은 아니다. 

 

  • FORCE INDEX
    • USE INDEX와 같은 기능을 하지만 옵티마이저에게 미치는 영향이 더 강하다.
    • USE INDEX 만으로도 충분히 영향력이 크므로 거의 사용할 필요없다. 

 

  • IGNORE INDEX
    • USE INDEX나 FORCE INDEX와는 반대로 특정 인덱스를 사용하지 못하게 하는 용도
    • 때로는 옵티마이저가 풀 테이블 스캔을 사용하도록 유도하기 위해 IGNORE INDEX 힌트를 사용할 수도 있다. 

 

 

인덱스 힌트 용도

  • USE INDEX FOR JOIN: JOIN 키워드는 테이블 간 조인 뿐만 아니라 레코드 검색하기 위한 용도까지 포함하는 용어다. MySQL 서버에서는 하나의 테이블로부터 데이터를 검색하는 작업JOIN이라고 한다. 
  • USE INDEX FOR ORDER BY: 명시된 인덱스를 ORDER BY 용도로만 사용할 수 있게 제한한다. 
  • USE INDEX FOR GROUP BY: 명시된 인덱스를 GROUP BY 용도로만 사용할 수 있게 제한한다.

 

 

  Ex) 인덱스 힌트 사용법

SELECT * FROM employees WHERE emp_no = 10001;
SELECT * FROM employees FORCE_INDEX(primary) WHERE emp_no = 10001;
SELECT * FROM employees USE_INDEX(primary) WHERE emp_no = 10001;

SELECT * FROM employees IGNORE_INDEX(primary) WHERE emp_no = 10001;
SELECT * FROM employees FORCE_INDEX(ix_firstname) WHERE emp_no = 10001;
  • 1 ~ 3 번 쿼리는 모두 employees 테이블의 PK를 이용해서 동일한 실행 계획으로 쿼리를 처리한다. 
  • 인덱스 힌트가 주어지지 않아도 emp_no 라는 조건이 있기 때문에 PK를 사용하는 것이 최적이라는 게 옵티마이저도 인식하기 때문이다. 
  • 4번 쿼리는 일부러 인덱스를 사용하지 못하게 힌트를 추가했다.
  • 5번 쿼리는 PK는 버리고 풀 테이블 스캔을 하는 형태로 실행 계획이 출력된다.
  • 최적의 실행 계획은 데이터 성격에 따라 시시각각 변하므로 지금 PK를 사용하는 게 좋더라도 내일은 달라질 수 있다. 
  • 따라서 그때그때 옵티마이저가 당시 통계 정보를 가지고 선택하게 하는 것이 가장 좋다. 
  • 가장 좋은 최적화는 그 쿼리를  서비스에서 없애 버리거나 튜닝할 필요 없게 데이터를 최소화하는 것이다. 
  • 이게 어려우면 데이터 모델의 단순화를 통해서 쿼리를 간결하게 만들고 힌트가 필요치 않게 하는 것이다. 

 

 

9.4.1.3 SQL_CALC_FOUND_ROWS

  • LIMIT 키워드를 사용하면 명시된 수만큼 레코드를 찾고 즉시 검색 작업을 멈춘다. 
  • SQL_CALC_FOUND_ROWS  힌트가 포함된 쿼리는 LIMIT을 만족하는 수만큼 레코드를 찾았다고 하더라도 끝까지 검색을 수행한다. 사용자에게는 제한된 수만큼의 결과 레코드만 반환됨에도 불구하는데도 그렇다. 
  • SQL_CALC_FOUND_ROWS  힌트가 사용된 쿼리가 실행되는 경우는 FOUND_ROWS() 라는 함수를 이용하면 LIMIT를 제외한 조건을 만족하는 레코드가 전체 몇 건이었는지를 알아낼 수 있다.  

 

 

 

  Ex 1) SQL_CALC_FOUND_ROWS 사용법

SELECT SQL_CALC_FOUND_ROWS * FROM employees WHERE first_name = 'Georgi' LIMIT O0, 20;
SELECT FOUND_ROWS() AS total_record_count;
  • FOUND_ROWS() 함수의 실행을 위해 또 한번의 쿼리가 필요하기 때문에 쿼리를 2번 실행해야한다. 
  • first_name = 'Georgi'  조건을 처리하기 위해 employees 테이블의 ix_firstname 인덱스를 레인지 스캔으로 실제 값을 읽어오는데 실제 이 조건을 만족하는 레코드는 전체 253건이다. . 
  • LIMIT 조건이 처음 20건만 가져오도록 했지만 SQL _CALC_FOUND_ROWS 힌트 떄문에 조건을 만족하는 레코드 전부를 읽어봐야한다. 
  • 그래서 ix_firstname 인덱스를 통해서 실제 데이터 레코드를 찾아가는 작업을 253 번 실행하고 디스크 헤더가 특정 위치로 움직일 때까지 기다리는 랜덤I/O253번 일어난다. 

 

 

 

  Ex 2) 기존 2개의 쿼리로 쪼개어 실행하는 방법

SELECT COUNT(*) FROM employees WHERE first_name = 'Georgi';
SELECT * FROM employees WHERE first_name = 'Georgi' LIMIT 0, 20;
  • 쿼리를 두 번 실행한다. 
  • WHERE 조건절에 first_name = 'Georgi' 가 있기 때문에 똑같이 ix_firstname 인덱스를 레인지 스캔한다. 
  • 첫 번째 쿼리에서는 실제 레코드 데이터가 필요한 것이 아니라 건수만 가져오면 되기 때문에 실제로 데이터 레코드를 찾아가기 위한 랜덤 I/O는 발생하지 않는다. 
    • 커버링 인덱스(Covering index) 쿼리이기 때문이다.
  • 두 번째 쿼리에서는 실제 데이터 레코드를 읽어온다. 
  • SQL_CALC_FOUND_ROWS 에서 했던 것처럼 ix_firstname 인덱스를 레인지 스캔으로 접근한 후, 실제로 데이터 레코드를 읽으러 가야하므로 랜덤 I/O가 발생한다. 
  • 그런데 여기서는 LIMIT 제한이 있기 때문에 랜덤 I/O를 253번 실행하는 게 아니라 20번만 실행한다. 

 

 

  • 전기적 처리인 메모리나 CPU의 연산 작업에 비해 기계적 처리인 디스크 작업이 훨씬 느리기 때문에  SQL_CALC_FOUND_ROWS 를 사용하는 경우가 느리다
  • SELECT 쿼리 문장이 UNION으로 연결된 경우, SQL _CALC_FOUND_ROWS 힌트를 사용해도 FOUND_ROWS() 함수로 정확한 레코드 건수를 가져올 수 없는 문제도 있다. 
  • 인덱스나 쿼리 튜닝이 제대로 됐다면 2번 방식이 1번 방식보다 빠르게 실행될 것이다. 
  • 결국 SQL_CALC_FOUND_ROWS는 성능 향상이 아니라 개발자의 편의를 위해 만들어진 힌트인 것이다.
  • 만약, COUNT(*) 쿼리나 칼럼값을 읽어오는 SELECT 쿼리가 적절히 튜닝되지 않았거나 WHERE 조건에 대해서 적절한 인덱스가 준비되지 않은 경우에는 SQL_CALC_FOUND_ROWS 로 처리하는 게 빠른 경우도 있다. 
    • 그렇지 않으면 COUNT(*) 쿼리가 더 빠르다는 뜻?
  • 일반적으로는 레코드 카운터용 쿼리와 데이터를 조회하는 쿼리는 분리하는 게 더 효율적이다.
  • 속도가 왜 차이날까??

 

 

 

 

9.4.2. 옵티마이저 힌트

 

9.4.2.1 옵티마이저 힌트 종류

  • 인덱스: 특정 인덱스의 이름을 사용할 수 있는 옵티마이저 힌트
  • 테이블: 특정 테이블의 이름을 사용할 수 있는 옵티마이저 힌트
  • 쿼리 블록: 특정 쿼리 블록에 사용할 수 있는 옵티마이저 힌트로서 특정 쿼리 블록의 이름을 명시하는 게 아니라 힌트가 명시된 쿼리 블록에 대해서만 영향을 미치는 옵티마이저 힌트
  • 글로벌(쿼리 전체): 전체 쿼리에 대해서만 영향을 미치는 힌트

 

 

  • Def) 쿼리 블록(서브 쿼리): SQL 문장에서 여러 개의 SELECT 키워드를 여러 개 쓸 수 있는데 이 때 각 SELECT 키워드로 시작하는 서브쿼리 영역을 말한다.
  • 특정 쿼리 블록에 영향을 주는 옵티마이저 힌트는 그 쿼리 블록 내에서 사용될 수 있지마 외부 쿼리 블록에서 사용할 수도 있다. 
    • 이런 경우는 "QB_NAME()" 힌트를 이용해서 해당 쿼리 블록에 이름을 부여해야한다. 

 

 

 

 

  Ex) 사용 예시

SELECT /*+ JOIN_ORDER() */
	COUNT(*)
FROM  employees e
WHERE e.first_name = 'Matt'
   		AND e.emp_no IN (SELECT /*+ QB_NAME(subq1) */ s.emp_no 
        			FROM salaries s
                    		WHERE s.salary BETWEEN 50000 AND 50500);
  • 쿼리 블록에  sunq1 이라는 이름을 부여하고 외부 쿼리 블록에서 사용 가능하다. 
  • 이 쿼리 블록을 힌트에 사용한다. 
  • 서브 쿼리에 사용된 salaries 테이블이 세미 조인 최적화를 통해서 조인으로 처리될 것을 예상하고 JOIN_ORDER 힌트를 사용한 것이며 조인의 순서로 외부 쿼리 블록의  employees 테이블과 서브 쿼리 블록의 salaries 테이블을 순서대로 힌트를 사용한 것이다. 
  • 이런 방식은 일반적이지는 않다.

 

 

 

9.4.2.2 MAX_EXECUTION_TIME

  • 옵티마이저 힌트 중에서 유일하게 쿼리의 실행 계획에 영향을 미치지 않는 힌트이며 단순히 쿼리의 최대 실행 시간을 설정하는 힌트다. 
  • 밀리토 단위의 시간을 설정하고 쿼리가 지정된 시간을 초과하면 쿼리는 실패한다

 

 

 

9.4.2.3 SET_VAR

  • SET_VAR: 실행 계획을 바꾸는 용도 뿐만이 아니라 조인 버퍼정렬용 버퍼(소트 버퍼)의 크기를 일시적으로 증가시켜서 대용량 처리 쿼리의 성능을 향상시키는 용도로도 사용할 수 있다. 
  • 옵티마이저 힌트 뿐만이 아니라 MySQL 서버의 시스템 변수들도 쿼리의 실행 계획에 상당한 영향을 미친다. 
  • 조인 버퍼의 크기를 설정하는 join_buffer_size 시스템 변수의 경우, 쿼리에 아무런 영향을 주지 않을 것 같지만 조인 버퍼의 공간이 충분하면 조인 버퍼를 활용하는 형태의 실행 계획을 선택할 수 있다. 
  • 옵티마이저 힌트로 부족한 경우에 optimize_switch 시스템 변수를 제어해야 하는 경우도 SET_VAR 힌트를 사용한다. 
  • 다양한 형태의 시스템 변수 조정을 사용할 수 있다. 

 

 

 

9.4.2.4 SEMIJOIN & NO_SEMIJOIN

  • SEMIJOIN 힌트는 어떤 세부 전략을 사용할지를 제어하는데 사용 가능하다. 
  • 세미 조인 최적화 힌트는 외부 쿼리가 아닌 서브쿼리에 명시한다. 

 

 

최적화 전략과 (힌트)

  • Duplicated Weed-out: SEMIJOIN(DUPSWEEPOUT)
  • First Match: SEMIJOIN(FIRSTMATCH)
  • Loose Scan: SEMIJOIN(LOOSESCAN)
  • Materialization: SEMIJOIN(MATERIALIZATION)
  • Table Pull-out: 힌트를 사용할 수 없다. 

 

 

 

  Ex 1) FirstMatch 전략 실행

SELECT *
FROM departments d
WHERE d.dept_no IN
                (SELECT de.dept_no 
                 FROM dept_emp de);
  • 세미 조인 최적화에서 First Match 전략을 사용하는 실행 계획을 가진다. 

 

 

 

  Ex 2) 1번 쿼리가 다른 최적화 전략을 사용하도록 세미 조인 힌트를 사용

SELECT *
FROM departments d
WHERE d.dept_no IN
          	  (SELECT  /*+ SEMIJOIN(MATERIALIZATION) */ de.dept_no
                   FROM dept_emp de);

 

 

 

  Ex 3) 서브 쿼리에 쿼리 블록 이름을 정의하고  실제 세미 조인 힌트는  외부 쿼리 블록에 명시

SELECT /*+ SEMIJOIN(MATERIALIZATION) */ *
FROM departments s
WHERE d.dept_no IN
		(SELECT /*+ QB_NAME(subq1) */ de.dept_no
         FROM dept_emp de);

 

 

 

  Ex 4) 세미 조인 최적화 전략을 사용하지 않게 하려면 NO_SEMIJOIN 힌트를 명시해서 해당 최적화 전략을 사용하지 않도록 설정

SELECT *
FROM departments s
WHERE d.dept_no IN
		(SELECT /*+ NO_SEMIJOIN(DUPSWEEPOUT, FIRSTMATCH) */ de.dept_no
     	  	  FROM dept_emp de);

 

 

 

 

9.4.2.5 SUBQUERY

  • 서브 쿼리 최적화세미 조인 최적화가 사용되지 못할 때 사용하는 최적화 방법이다. 
  • 세미 조인 최적화는 주로 IN(subquery) 형태의 쿼리에 사용될 수 없다.
  • 서브 쿼리 최적화 전략은 사용할 기회가 그다지 많지 않음  

 

 

 

안티 세미 조인 최적화 방법과 (힌트)

  • 서브쿼리는 다음과 같이 2가지 형태로 최적화할 수 있다. 
  • 서브쿼리 최적화는 주로 IN(subquery) 형태의 쿼리에 사용될 수 있지만 안티 세미 조인의 최적화에는 사용될 수 없다
  • 그래서 안티 세미 최적화에는 아래 2가지 최적화가 사용된다 
    • IN-to-EXISTS: SUBQUERY(INTOEXISTS)
    • Materialization: SUBQUERY(MATERIALIZATION)

 

 

 

9.4.2.6 BNL & NO_BNL & HASHJOIN & NO_HASHJOIN

  • BNL: Block Nested Loop
  • MySQL 8.0.19까지는 블록 네스티드 루프 조인 알고리즘을 사용했으니 
  • MySQL 8.0.18 버전부터 도입된 해시 조인 알고리즘이 MySQL 8.0.20  버전부터는  블록 네스티드 루프 조인까지 대체하도록 개선
  • MySQL 8.0.20 부터는 블록 네스티드 루프 조인은 MySQL 서버에서 더이상 사용되지 않는다.
  • 하지만, BNL 힌트와  NO_BNL  힌트는 MySQL 8.0.20 이후 버전에서도 여전히 사용 가능하다.
    • BNL 힌트를 사용하면 해시 조인을 사용하도록 유도하는 힌트로 용도가 변경됐다.
    • 대신 HASHJOINNO_HASHJOIN 힌트는 MySQL 8.0.18 버전에서만 유효하고 이후 버전에서는 효력이 없다. 

 

 

  Ex) MySQL 8.0.20 이후 버전에서

SELECT /*+ BNL(e, de)*/ *
FROM employees e
INNER JOIN dept_emp de ON de.emp_no = e.emp_no;
  • 위와 같이 해시 조인을 유도하거나 해시 조인을 사용하지 않도록 설정하기 위해 
  • BNL, NO_BNL 힌트를 사용해야한다. 

 

 

 

9.4.2.7 JOIN_FIXED_ORDER & JOIN_ORDER & JOIN_PREFIX & JOIN_SUFFIX

  • 전통적으로 조인 순서를 결정하기 위해 STRAIGHT_JOIN 힌트를 사용해왔다 .
  • 그런데 STRAIGHT_JOIN 힌트는 쿼리의 FROM 절에 사용된 테이블의 순서를 조인 순서에 맞게 변경해야하는 번거로움이 있다. 
  • STRAIGHT_JOIN를 한 번 사용하면 FROM 절에 명시된 모든 테이블의 조인 순서가 결정되기 때문에 일부는 조인 순서를 강제하고 나머지는 옵티마이저에게 순서를 결정하도록 맡기는 게 불가능했다. 
  • 이런 단점 보완을 위해서 다음과 같은 4개의 힌트를 제공한다. 
    • JOIN_FIXED_ORDER: STRAIGHT_JOIN 힌트와 동일하게 FROM 절의 테이블 순서대로 조인을 실행하게 하는 힌트
    • JOIN_ORDER: FROM 절에 사용된 테이블의 순서가 아니라 힌트에 명시된 테이블의 순서대로 조인을 실행하는 힌트
    • JOIN_PREFIX: 조인에서 드라이빙 테이블만 강제하는 힌트
    • JOIN_SUFFIX: 조인에서 드리븐 테이블(가장 마지막에 조인돼야 할 테이블들)만 강제하는 힌트

 

 

  Ex) 조인 순서와 관련된 옵티마이저 힌트의 사용법 - FROM 절에 나열된 테이블의 순서대로 조인 실행

SELECT /*+ JOIN_FIXED_ORDER() */ * 
FROM employeese e
	INNER JOIN dept_emp de ON de.emp_no = e.emp_no
    INNER JOIN departments d de ON d.dept_no = de.dept_no;
  • 위에 주석 안에 JOIN_FIXED_ORDER 쪽에  힌트명만 바꾸면 다르게 사용 가능하다.

 

 

9.4.2.8 MERGE & NO_MERGE

  • 예전의 MySQL 서버에서는 FROM절에 사용된 서브쿼리를 항상 내부 임시 테이블로 생성했다.
  • 이를 "파생 테이블(Derived table)"라고 하는데 이는 불필요한 자원 소모를 유발한다.
  • 그래서 5.7과 8.0 버전에서는 가능한 임시테이블을 사용하지 않도록 FROM 절의 서브쿼리를 외부 쿼리와 병합하는 최적화를 도입했다. 
  • 병합하는 게 무조건 성능이 좋은 걸까?

 

 

 

9.4.2.9 INDEX MERGE  & NO INDEX MERGE

  • 서버는 가능한 테이블 당 하나의 인덱스만 이용해서 쿼리를 처리하려 한다. 

 

 

9.4.2.10 NO_ICP

  • ICP: Index Condition Pushdown (인덱스 컨디션 푸시다운)
  • 사용 가능하면 성능이 향상 되므로 옵티마이저는 최대한 사용하는 쪽으로 실행 계획을 수립한다. 
  • 그런데 ICP 로 인해 여러 실행 계획의 비용 계산이 잘못된다면 잘못된 실행 계획을 수립할 수도 있다. 
  • 참고: 324p ICP(인덱스 컨디션 푸시다운)

 

 

9.4.2.11 SKIP_SCAN & NO_CKIP_SCAN

 

  • 인덱스 스킵 스캔은 인덱스의 선행 칼럼에 대한 조건 없어도 옵티마이저가 해당 인덱스를 사용할 수 있도록 해주는 훌륭한 최적화 기능이다. 

 

 

 

 

 

9.4.2.12 INDEX & NO_INDEX

 

 

 

 

 

 

 

 

출처 - 「Real MySQL 8.0 - 백은빈, 이성욱」