책너두 (Real MySQL 8.0 1권) 32일차 (~347p)

요약

  • 세미조인 최적화의 방법 중, 일부를 이해하게 됨.
    • 테이블 풀-아웃
      • 서브쿼리 → 아우터 쿼리로 끄집어낸 후 쿼리를 조인 쿼리로 재작성
    • 퍼스트 매치
      • EXISTS(subquery)로 최적화함.
      • 단축 실행 경로로 수행함. (서브쿼리가 참조하는 모든 아우터 테이블이 먼저 조회된 이후에 실행됨)
    • 루스 스캔
      • 루스 인덱스 스캔와 비슷한 방식으로 스캔함.
    • 구체화
      • 서브쿼리를 통째로 구체화 하여 최적화 함. → 내부 임시테이블을 생성함.
    • 중복 제거
      • 세미조인 서브쿼리 → INNER JOIN + 중복 제거 처리를 최적화함.

메모

세미조인 최적화 1-1

테이블 풀-아웃(Table Pull-out)

  • Table pullout 최적화는 세미 조인의 서브쿼리에 사용된 테이블을 아우터 쿼리로 끄집어낸 후에 쿼리를 조인 쿼리로 재작성하는 형태의 최적화임.
    • 이는 서브쿼리 최적화 도입 전에 수동으로 쿼리를 튜닝하던 대표적인 방법이었음.
mysql> EXPLAIN
             SELECT * FROM employees e
             WHERE e.emp_no IN (SELECT de.emp_no FROM dept_emp de WHERE de.dept_no='d009');

+----+-------------+-------+--------+---------+--------+-------------+
| id | select_type | table | type   | key     |  rows  | Extra       |
+----+-------------+-------+--------+---------+--------+-------------+
| 1  | PRIMARY     | de    | ref    | PRIMARY |  46012 | Using index |
| 1  | SUBQUERY    | e     | eq_ref | PRIMARY |      1 | NULL        |
+----+-------------+-------+--------+---------+--------+-------------+
  • 위 조회 쿼리와 같이 IN(subquery) 형태의 세미 조인이 가장 빈번하게 사용되는 형태의 쿼리임.
  • 실행계획이 dept_emp 테이블과 employees 테이블이 순서대로 표시되어있음.
    • 가장 중요한 부분은 id 칼럼 값이 모두 1이라는 것임.
    • 즉, 이 값이 동일하다는 건, 두 테이블이 서브쿼리 형태가 아니라 조인으로 처리됐음을 의미함.
  • Table pullout 최적화는 별도로 실행 계획의 Extra 칼럼에 “Using table pullout” 과 같은 문구가 출력되지 않는다.
    • 그래서 Table pullout 최적화가 사용됐는지는 실행 계획에서 해당 테이블의 id 칼럼이 같은지 다른지 비교해 보는 것이 가장 간단한 방법임.
    • Table pullout 최적화가 사용됐는지 더 정확하게 확인하는 방법은 EXPLAIN 명령을 실행한 직후, SHOW WARNINGS 명령으로 MySQL 옵티마이저가 재작성(Re-Write)한 쿼리를 살펴볼 수 있음.
  • Table pullout 최적화는 모든 형태의 서브쿼리에서 사용될 수 있는 것은 아님.
  • 아래 Table pullout 최적화의 몇 가지 제한 사항 및 특성이 있다.
    • Table pullout 최적화는 세미 조인 서브쿼리에서만 사용 가능함.
    • Table pullout 최적화는 서브쿼리 부분이 Unique 인덱스나 프라이머리 키 룩업으로 결과가 1건인 경우에만 사용 가능함.
    • Table pullout이 적용된다고 하더라도 기존 쿼리에서 가능했던 최적화 방법이 사용 불가능한 것은 아니므로 MySQL 에서는 가능하다면 Table pullout 최적화를 최대한 적용함.
    • Table pullout 최적화는 서브쿼리의 테이블을 아우터 쿼리로 가져와서 조인으로 풀어쓰는 최적화를 수행함. 만약, 서브쿼리의 모든 테이블이 아우터 쿼리로 끄집어 낼 수 있다면, 서브쿼리 자체는 없어짐.
    • MySQL에서는 “최대한 서브쿼리를 조인으로 풀어서 사용해라” 는 튜닝 가이드가 많음. Table pullout 최적화는 사실, 이 가이드를 그대로 실행하는 것임.

퍼스트 매치(firstmatch)

  • First Match 최적화 전략은 IN(subquery) 형태의 세미 조인을 EXISTS(subquery) 형태로 튜닝한 것과 비슷한 방법으로 실행됨.
mysql> EXPLAIN SELECT *
             FROM employees e WHERE e.first_name='Matt'
           AND e.emp_no IN (
                 SELECT t.emp_no FROM titles t
                 WHERE t.from_date BETWEEN '1995-01-01' AND '1995-01-30'
             );

+----+-------+------+--------------+------+-----------------------------------------+
| id | table | type | key          | rows | Extra                                   |
+----+-------+------+--------------+------+-----------------------------------------+
| 1  |  t    | ref  | ix_firstname |  233 | NULL                                    |
| 1  |  e    | ref  | PRIMARY      |    1 | Using where; Using index; FirstMatch(e) |
+----+-------+------+--------------+------+-----------------------------------------+
  • 위 쿼리의 실행 계획에서, id 칼럼 값이 모두 1로 표시됨.
    • 위 FirstMatch 최적화 예제에서, titles 테이블이 서브쿼리 패턴으로 실행되지 않고, 조인으로 처리됐다는 걸 알 수 있음.
  • Extra 칼럼에 “FirstMatch(e)” 문구가 출력됨.
    • employees 테이블의 레코드에 대해 titles 테이블에 일치하는 레코드 1건만 찾으면 더이상의 titles 테이블 검색을 하지 않음을 의미함.
    • 즉, 실제 의미론적으로 EXISTS(subquery)와 동일하게 처리는 것임.
    • 하지만, FirstMatch는 서브쿼리가 아니라, 조인으로 풀어서 실행하면서 일치하는 첫 번째 레코드만 검색하는 최적화를 실행한 것임.

  • employees 테이블에서 first_name 칼럼 값이 ‘Matt’인 사원 정보를 ix_firstname 인덱스를 이용하여 레인지 스캔으로 읽은 결과가 위 그림의 왼쪽 테이블인 employees 테이블임.
  • employees 테이블의 emp_no를 titles 과 조인하여 from_date의 조건에 일치하는지 확인하여 일치하면 더이상 titles 테이블을 검색하지고 바로 레코드를 반환함.
  • p340에 예제가 잘 설명되어 있음.
  • First Match 최적화는 MySQL 5.5에서 수행했던 최적화 방법인 IN-to-EXISTS 변환과 거의 비슷한 처리 로직을 수행함.
  • 5.5의 IN-to-EXISTS 변환에 비해, First Match 최적화 전략은 다음과 같은 장점이 있음.
    • 여러 테이블이 조인되는 경우, 원래 쿼리에 없던 동등 조건을 옵티마이저가 자동으로 추가하는 형태의 최적화가 실행되기도 함.
      • 기존의 IN-to-EXISTS 최적화는 동등 조건 전파(Equality propagation)가 서브쿼리 내에서만 가능했음.
      • FirstMatch는 조인 형태로 처리하므로 서브쿼리뿐만 아니라 아우터 쿼리의 테이블까지 전파될 수 있음.
        • 따라서, FirstMatch 최적화로 실행되면 더 많은 조건이 주어지는 것이므로 더 나은 실행 계획을 수립할 수 있음.
    • In-to-EXISTS 변환 최적화 전략에서는 아무런 조건 없이 변환이 가능한 경우에는 무조건 그 최적화를 수행했음.
      • FirstMatch 최적화에서는 서브쿼리의 모든 테이블에 대해 FirstMatch 최적화를 수행할지 아니면 일부 테이블에 대해서만 수행할지 취사선택할 수 있다는 것이 장점임.
  • FirstMatch 최적화 또한 특정 형태의 서브쿼리에서 자주 사용되는 최적화임.
  • 아래 FirstMatch 최적화의 몇 가지 제한 사항 및 특성이 있다.
    • FIrstMatch는 서브쿼리에서 하나의 레코드만 검색되면 더이상 검색을 멈추는 단축 실행 경로(Short-cut path)이므로 FirstMatch 최적화에서 서브쿼리는 그 서브쿼리가 참조하는 모든 아우터 테이블이 먼저 조회된 이후에 실행됨.
    • FirstMatch 최적화가 사용되면 실행 계획의 Extra 칼럼에 “FirstMatch(table-N)” 문구가 표시됨.
    • FirstMatch 최적화는 상관 서브쿼리(Correlated subquery)에서도 사용될 수 있음.
    • FirstMatch 최적화는 GROUP BY나 집합 함수가 사용된 서브쿼리의 최적화에는 사용될 수 없음.
  • FirstMatch 최적화는 optimizer_switch 시스템 변수에서 semijoin 옵션과 firstmatch 옵션이 모두 ON으로 활성화된 경우에만 사용할 수 있다.

루스 스캔(loosescan)

  • 루스 인덱스 스캔(Loose Index Scan)과 비슷한 읽기 방식을 사용함.
mysql> EXPLAIN
           SELECT * FROM departments d WHERE d.dept_no IN (
                     SELECT de.dept_no FROM dept_emp de);

+----+-------+--------+---------+--------+------------------------+
| id | table | type   | key     | rows   | Extra                  |
+----+-------+--------+---------+--------+------------------------+
| 1  |  de   | index  | PRIMARY | 331143 | Using index; LooseScan |                                    |
| 1  |  e    | eq_ref | PRIMARY |      1 | NULL                   |
+----+-------+--------+---------+--------+------------------------+
  • departments 테이블의 레코드 건수는 9건밖에 없음.
  • dept_emp 테이블의 레코드 건수는 무려 33만건 가까이 저장되어 있음.
  • dept_emp 테이블은 (dept_no, emp_no) 칼럼 조합으로 프라이머리 키 인덱스가 만들어져 있음.
    • dept_no로 그루핑하면 9건 밖에 없는 상황임.
    • 이때, dept_emp 테이블의 프라이머리 키를 루스 인덱스 스캔으로 유니크한 dept_no만 읽으면 아주 효율적으로 서브쿼리 부분을 실행할 수 있음. (중복된 레코드까지 제거하면서 가능함)

  • 위 그림에서, 서브쿼리에 사용된 dept_emp 테이블이 드라이빙 테이블로 실행됨.
    • dept_emp 테이블의 프라이머리 키를 dept_no 부분에서 유니크하게 한 건씩만 읽고 있음을 보여줌.
    • 루스 인덱스 스캔 의 “Using index for group-by”도 위 그림과 같이 dept_emp 테이블의 프라이머리 키를 읽는 방식과 동일하게 작용함.
  • 위 쿼리의 실행 계획에서 Extra 칼럼에 “LooseScan” 문구가 표시됨.
  • 실행계획 칼럼의 id 값이 동일하게 1인 것도, MySQL 내부적으로 조인처럼 처리됐음을 알려줌.
  • LooseScan 최적화는 다음과 같은 특성을 가짐.
    • LooseScan 최적화는 루스 인덱스 스캔으로 서브쿼리 테이블을 읽고, 그다음으로 아우터 테이블을 드리븐으로 사용해서 조인을 수행함. 그래서 서브쿼리 부분이 루스 인덱스 스캔을 사용할 수 있는 조건이 갖춰져야 사용할 수 있는 최적화임.

구체화(Materialization)

  • Materialization 최적화는 세미 조인에 사용된 서브쿼리를 통째로 구체화해서 쿼리를 최적화한다는 의미임.
  • 여기서, 구체화는 쉽게 표현하면 내부 임시 테이블을 생성한다는 것을 의미함.
mysql> EXPLAIN
             SELECT *
             FROM employees e
             WHERE e.emp_no IN
                   (SELECT de.emp_no FROM dept_emp de
                        WHERE de.from_date='1995-01-01');

+----+--------------+-------------+--------+-------------+---------------------+
| id | select_type  | table       | type   | key         |  ref                |
+----+--------------+-------------+--------+-------------+---------------------+
| 1  | SIMPLE       | <subquery2> | ALL    | NULL        |  NULL.              |
| 1  | SIMPLE       | e           | eq_ref | PRIMARY     |  <subquery2>.emp_no |
| 2  | MATERIALIZED | de          | ref    | ix_fromdate |  const              |
+----+--------------+-------------+--------+-------------+---------------------+
  • 이 쿼리는 FirstMatch 최적화를 사용하면 employees 테이블에 대한 조건이 서브쿼리 이외에는 아무것도 없으므로 employees 테이블 풀 스캔 해야 함.
    • 따라서, 위 형태의 세미 조인에서 FirstMatch 최적화는 성능 향상에 별로 도움되지 않음.
  • MySQL 서버 옵티마이저는 이런 형태의 쿼리를 위해 서브쿼리 구체화(Subquery Materialization)라는 최적화를 도입함.
  • 위 쿼리의 실행계획은 서브쿼리 구체화에 대한 내용임.
  • 실행 계획 마지막 라인의 select_type 칼럼은 “MATERIALIZED” 라고만 표시됨.
  • 이 쿼리에서 사용하는 테이블은 2개인데, 실행 계획은 3개 라인이 출력된 것을 보면, 이 쿼리의 실행 계획 어디선가 임시 테이블이 생성됐다는 걸 짐작할 수 있음.
  • dept_emp 테이블을 읽는 서브쿼리가 먼저 실행되어 그 결과로 임시테이블인 가 만들어짐.
    • 최종적으로 서브쿼리가 구체화된 임시 테이블 와, employees 테이블을 조인해서 그 결과를 반환함.
  • Materialization 최적화는 다른 서브쿼리 최적화와 달리, 다음 쿼리와 같이 서브쿼리 내에 GROUP BY 절이 있어도 이 최적화 전략을 사용할 수 있음. (p344)
  • Materialization 최적화가 사용될 수 있는 형태의 쿼리에도 역시 몇가지 제한 사항과 특성이 있음.
    • IN(subquery)에서 서브쿼리는 상관 서브쿼리(Correlated subquery)가 아니어야 함.
    • 서브쿼리는 GROUP BY나 집합 함수들이 사용돼도 구체화를 사용할 수 있음.
    • 구체화가 사용된 경우, 내부 임시 테이블이 사용됨.
  • Materialization 최적화는 optimizer_switch 시스템 변수에서 semijoin 옵션과 materialization 옵션이 모두 ON으로 활성화된 경우에만 사용됨.

중복 제거(Duplicated Weed-out)

  • Duplicate Weedout은 세미 조인 서브쿼리를 일반적인 INNER JOIN 쿼리로 바꿔서 실행하고 마지막에 중복된 레코드를 제거하는 방법으로 처리되는 최적화 알고리즘임.
mysql> EXPLAIN
             SELECT * FROM employees e
             WHERE e.emp_no IN (SELECT s.emp_no FROM salaries s WHERE s.salary>150000);
  • salaries 테이블의 프라이머리 키가 (emp_no, from_date) 이므로 salary가 150000 이상인 레코드를 salaries 테이블에서 조회하면 그 결과에는 중복된 emp_no가 발생할 수 있음.
  • 따라서, 위 쿼리를 아래와 같이 재작성하여 GROUP BY 절을 넣어 주면, 위의 세미조인 서브쿼리와 동일한 결과를 얻을 수 있음.
mysql> SELECT e.*
             FROM employees e, salaries s
             WHERE e.emp_no=s.emp_no AND s.salary>150000
             GROUP BY e.emp_no;
  • 실제로 Duplicate Weedout 최적화 알고리즘은 원본 쿼리를 위와 같이 INNER JOIN + GROUP BY 절로 바꿔 실행하는 것과 동일한 작업으로 쿼리를 처리함.

  • 위 그림은 위 쿼리를 Duplicate Weedout 최적화 알고리즘으로 처리하는 과정을 그림으로 표현함.
    1. salaries 테이블의 ix_salary 인덱스를 스캔하여 salary 가 150000보다 큰 사원을 검색하여 employees 테이블 조인을 실행함.
    2. 조인된 결과를 임시 테이블에 저장함.
    3. 임시 테이블에 저장된 결과에서 emp_no 기준으로 중복 제거
    4. 중복 제거 후, 남은 레코드를 최종적으로 반환함.
  • Duplicate Weedout 최적화의 실행 계획에는 “Duplicate Weedout” 문구가 별도 표시되지 않음.
    • 하지만, Extra 칼럼에 “Start temporary”와 “End temporary” 문구가 별도로 표기된 것을 확인할 수 있음.
    • 위 예시에서 1,2 과정 (조인 + 임시테이블 저장작업)은 반복적으로 실행되는 과정임.
      • 이 반복 과정이 시작되는 테이블의 실행 계획 라인에는 “Start tempoary” 문구가 표시됨. (salaries)
      • 반복 과정이 끝나는 테이블의 실행 계획 라인에는 “End temporary” 문구가 표시됨. (employees)
  • Duplicate Weedout 최적화는 다음의 장점과 제약 사항이 있음.
    • 서브쿼리가 상관 서브쿼리라고 하더라도 사용할 수 있음.
    • 서브쿼리가 GROUP BY나 집합 함수가 사용된 경우에는 사용될 수 없음.
    • Duplicate Weedout은 서브쿼리의 테이블을 조인으로 처리하기 떄문에 최적화할 수 있는 방법이 많음.

댓글

Designed by JB FACTORY