[테스트 환경]
<"employee"."e"."emp_no"> 에 따라서 에 출력되는 결과 값을 캐싱해두어 사용한다. 서브쿼리의 r_loops가 1인 것을 확인한다.
# 메인쿼리의 데이터가 유일하고 서브쿼리의 데이터에 중복이 존재할 때 따라서 semi join으로 쿼리를 변환해서 salary > 66961를 만족하는 데이터가 존재하면 리턴하는 방식으로 진행한다.
서브쿼리 내의 데이터가 유일하지 않기 때문에 캐싱이 exists로 나타난 것을 확인할 수 있다.
titles 테이블의 pk 인덱스를 통해서 임시 테이블을 검색한다.(출처 :http://dev.mysql.com/doc/refman/5.7/en/explain-extended.html)
-- 서브쿼리의 데이터를 group by 로 유일하게 만든 후 진행.
OS : CentOS7
DB : MariaDB 10.1.12
# optimizer_switch 파라미터로 optimizer의 최적화를 제어할 수 있다.
MariaDB [(none)]> show variables like '%optimizer_switch%' \G *************************** 1. row *************************** Variable_name: optimizer_switch Value: index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_merge_sort_intersection=off,engine_condition_pushdown=off,index_condition_pushdown=on,derived_merge=on,derived_with_keys=on,firstmatch=on,loosescan=on,materialization=on,in_to_exists=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on,subquery_cache=on,mrr=off,mrr_cost_based=off,mrr_sort_keys=off,outer_join_with_cache=on,semijoin_with_cache=on,join_cache_incremental=on,join_cache_hashed=on,join_cache_bka=on,optimize_join_buffer_size=off,table_elimination=on,extended_keys=on,exists_to_in=on 1 row in set (0.00 sec)
#메인쿼리의 데이터가 유일하고 서브쿼리의 데이터가 유일할 때
-- semijoin 기능 on MariaDB [employee]> set optimizer_switch='semijoin=on'; Query OK, 0 rows affected (0.00 sec) MariaDB [employee]> explain extended -> select * -> from employees e -> where e.emp_no in (select de.emp_no -> from dept_emp de -> where de.dept_no = 'd001'); +------+-------------+-------+--------+-----------------+---------+---------+--------------------+-------+----------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +------+-------------+-------+--------+-----------------+---------+---------+--------------------+-------+----------+--------------------------+ | 1 | PRIMARY | de | ref | PRIMARY,dept_no | dept_no | 12 | const | 40528 | 100.00 | Using where; Using index | | 1 | PRIMARY | e | eq_ref | PRIMARY | PRIMARY | 4 | employee.de.emp_no | 1 | 100.00 | | +------+-------------+-------+--------+-----------------+---------+---------+--------------------+-------+----------+--------------------------+ 2 rows in set, 1 warning (0.00 sec) MariaDB [employee]> show warnings; +-------+------+--------------------------------------------------------------------------------------- select "employee"."e"."emp_no" AS "emp_no","employee"."e"."birth_date" AS "birth_date" ,"employee"."e"."first_name" AS "first_name","employee"."e"."last_name" AS "last_name" ,"employee"."e"."gender" AS "gender","employee"."e"."hire_date" AS "hire_date" from "employee"."dept_emp" "de" join "employee"."employees" "e" where (("employee"."e"."emp_no" = "employee"."de"."emp_no") and ("employee"."de"."dept_no" = 'd001')) | +-------+------+---------------------------------------------------------------------------------------+ 1 row in set (0.00 sec) analyze format=json select * from employees e where e.emp_no in (select de.emp_no from dept_emp de where de.dept_no = 'd001'); { "query_block": { "select_id": 1, "r_loops": 1, "r_total_time_ms": 43.369, "table": { "table_name": "de", "access_type": "ref", "possible_keys": ["PRIMARY", "dept_no"], "key": "dept_no", "key_length": "12", "used_key_parts": ["dept_no"], "ref": ["const"], "r_loops": 1, "rows": 40528, "r_rows": 20211, "r_total_time_ms": 4.9655, "filtered": 100, "r_filtered": 100, "attached_condition": "(de.dept_no = 'd001')", "using_index": true }, "table": { "table_name": "e", "access_type": "eq_ref", "possible_keys": ["PRIMARY"], "key": "PRIMARY", "key_length": "4", "used_key_parts": ["emp_no"], "ref": ["employee.de.emp_no"], "r_loops": 20211, "rows": 1, "r_rows": 1, "r_total_time_ms": 32.032, "filtered": 100, "r_filtered": 100 } } }-- 옵티마이저가 변경한 쿼리를 보면 서브쿼리 테이블인 dept_emp 테이블이 드라이빙이 되어 inner join으로 풀린것 을 확인할 수 있다.
dept_emp 테이블의 pk는 emp_no와 dept_no이며 조건절에 상수값으로 dept_no를 받기 때문에 emp_no가 유일함을 확인할 수 있다. 그래서 inner join으로 풀려도 데이터에 영향을 미치지 않는다.
-- semijoin 기능 off MariaDB [employee]> set optimizer_switch='semijoin=off'; Query OK, 0 rows affected (0.00 sec) MariaDB [employee]> explain extended -> select * -> from employees e -> where e.emp_no in (select de.emp_no -> from dept_emp de -> where de.dept_no = 'd001'); +------+--------------+-------+------+-----------------+---------+---------+-------+--------+----------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +------+--------------+-------+------+-----------------+---------+---------+-------+--------+----------+--------------------------+ | 1 | PRIMARY | e | ALL | NULL | NULL | NULL | NULL | 299290 | 100.00 | Using where | | 2 | MATERIALIZED | de | ref | PRIMARY,dept_no | dept_no | 12 | const | 40528 | 100.00 | Using where; Using index | +------+--------------+-------+------+-----------------+---------+---------+-------+--------+----------+--------------------------+ 2 rows in set, 1 warning (0.00 sec) MariaDB [employee]> show warnings; +-------+------+--------------------------------------------------------------------------------------- select "employee"."e"."emp_no" AS "emp_no","employee"."e"."birth_date" AS "birth_date","employee"."e"."first_name" AS "first_name" ,"employee"."e"."last_name" AS "last_name","employee"."e"."gender" AS "gender","employee"."e"."hire_date" AS "hire_date" from "employee"."employees" "e" where<"employee"."e"."emp_no">( ("employee"."e"."emp_no","employee"."e"."emp_no" in ( (select "employee"."de"."emp_no" from "employee"."dept_emp" "de" where ("employee"."de"."dept_no" = 'd001') ), ("employee"."e"."emp_no" in on distinct_key where (("employee"."e"."emp_no" = " "."emp_no"))))))| +-------+------+--------------------------------------------------------------------------------------- 1 row in set (0.00 sec) analyze format=json select * from employees e where e.emp_no in (select de.emp_no from dept_emp de where de.dept_no = 'd001'); { "query_block": { "select_id": 1, "r_loops": 1, "r_total_time_ms": 242.03, "table": { "table_name": "e", "access_type": "ALL", "r_loops": 1, "rows": 299290, "r_rows": 300024, "r_total_time_ms": 108.06, "filtered": 100, "r_filtered": 6.7365, "attached_condition": " (e.emp_no,e.emp_no in (subquery#2))" }, "subqueries": [ { "query_block": { "select_id": 2, "r_loops": 1, "r_total_time_ms": 16.54, "table": { "table_name": "de", "access_type": "ref", "possible_keys": ["PRIMARY", "dept_no"], "key": "dept_no", "key_length": "12", "used_key_parts": ["dept_no"], "ref": ["const"], "r_loops": 1, "rows": 40528, "r_rows": 20211, "r_total_time_ms": 6.736, "filtered": 100, "r_filtered": 100, "attached_condition": "(de.dept_no = 'd001')", "using_index": true } } } ] } }
-- 서브쿼리 최적화에서 semijoin으로 쿼리변환이 일어나지 않으면 서브쿼리 캐싱을 사용한다. 이 쿼리에서는 dept_no ='d001'인 데이터를 파라미터 값
-- semijoin 기능 on MariaDB [employee]> set optimizer_switch='semijoin=on'; Query OK, 0 rows affected (0.02 sec) MariaDB [employee]> explain extended -> select * -> from employees e -> where e.emp_no in (select s1.emp_no -> from salaries s1 -> where s1.salary > 66961); +------+-------------+-------+------+---------------+---------+---------+-------------------+--------+----------+----------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +------+-------------+-------+------+---------------+---------+---------+-------------------+--------+----------+----------------------------+ | 1 | PRIMARY | e | ALL | PRIMARY | NULL | NULL | NULL | 299290 | 100.00 | | | 1 | PRIMARY | s1 | ref | PRIMARY | PRIMARY | 4 | employee.e.emp_no | 4 | 100.00 | Using where; FirstMatch(e) | +------+-------------+-------+------+---------------+---------+---------+-------------------+--------+----------+----------------------------+ 2 rows in set, 1 warning (0.00 sec) MariaDB [employee]> show warnings; +-------+------+--------------------------------------------------------------------------------------- select "employee"."e"."emp_no" AS "emp_no","employee"."e"."birth_date" AS "birth_date","employee"."e"."first_name" AS "first_name" ,"employee"."e"."last_name" AS "last_name","employee"."e"."gender" AS "gender","employee"."e"."hire_date" AS "hire_date" from "employee"."employees" "e" semi join ("employee"."salaries" "s1") where (("employee"."s1"."emp_no" = "employee"."e"."emp_no") and ("employee"."s1"."salary" > 66961)) | +-------+------+--------------------------------------------------------------------------------------- 1 row in set (0.00 sec) analyze format=json select * from employees e where e.emp_no in (select s1.emp_no from salaries s1 where s1.salary > 66961); { "query_block": { "select_id": 1, "r_loops": 1, "r_total_time_ms": 925.3, "table": { "table_name": "e", "access_type": "ALL", "possible_keys": ["PRIMARY"], "r_loops": 1, "rows": 299290, "r_rows": 300024, "r_total_time_ms": 101.68, "filtered": 100, "r_filtered": 100 }, "table": { "table_name": "s1", "access_type": "ref", "possible_keys": ["PRIMARY"], "key": "PRIMARY", "key_length": "4", "used_key_parts": ["emp_no"], "ref": ["employee.e.emp_no"], "r_loops": 300024, "rows": 4, "r_rows": 6.4283, "r_total_time_ms": 689.72, "filtered": 100, "r_filtered": 8.1146, "attached_condition": "(s1.salary > 66961)", "first_match": "e" } } }-- salaries 테이블에는 한 emp_no당 여러 건의 데이터가 존재하기 때문에 inner join으로 쿼리변환을 하면 변환하기 전의 데이터와 동일하지 않은 결과를 리턴한다.
-- semijoin 기능 off MariaDB [(none)]> set optimizer_switch='semijoin=off'; Query OK, 0 rows affected (0.00 sec) MariaDB [employee]> explain extended -> select * -> from employees e -> where e.emp_no in (select s1.emp_no -> from salaries s1 -> where s1.salary > 66961); +------+--------------------+-------+----------------+---------------+---------+---------+------+--------+----------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +------+--------------------+-------+----------------+---------------+---------+---------+------+--------+----------+-------------+ | 1 | PRIMARY | e | ALL | NULL | NULL | NULL | NULL | 299290 | 100.00 | Using where | | 2 | DEPENDENT SUBQUERY | s1 | index_subquery | PRIMARY | PRIMARY | 4 | func | 4 | 100.00 | Using where | +------+--------------------+-------+----------------+---------------+---------+---------+------+--------+----------+-------------+ 2 rows in set, 1 warning (0.00 sec) MariaDB [employee]> show warnings; +-------+------+--------------------------------------------------------------------------------------- select "employee"."e"."emp_no" AS "emp_no","employee"."e"."birth_date" AS "birth_date","employee"."e"."first_name" AS "first_name" ,"employee"."e"."last_name" AS "last_name","employee"."e"."gender" AS "gender","employee"."e"."hire_date" AS "hire_date" from "employee"."employees" "e" where-- access_type인 index_subquery는 in 서브쿼리 형태를 index lookup function으로 대체해서 더 나은 성능으로 쿼리를 실행하는 타입이며 nonuique index를 사용하는 쿼리에서 나타난다. 이 때 r_loops 값이 0으로 출력된다. unique index를 사용하는 쿼리에서는 unique_subquery 타입으로 출력된다. (출처: http://dev.mysql.com/doc/refman/5.7/en/explain-output.html#jointype_unique_subquery)<"employee"."e"."emp_no">( ("employee"."e"."emp_no", ( ( ("employee"."e"."emp_no") in salaries on PRIMARY where (("employee"."s1"."salary" > 66961) and ( ("employee"."e"."emp_no") = "employee"."s1"."emp_no")))))) +-------+------+--------------------------------------------------------------------------------------- 1 row in set (0.00 sec) analyze format=json select * from employees e where e.emp_no in (select s1.emp_no from salaries s1 where s1.salary > 66961); { "query_block": { "select_id": 1, "r_loops": 1, "r_total_time_ms": 937.37, "table": { "table_name": "e", "access_type": "ALL", "r_loops": 1, "rows": 299290, "r_rows": 300024, "r_total_time_ms": 101.75, "filtered": 100, "r_filtered": 52.163, "attached_condition": " (e.emp_no, (subquery#2))" }, "subqueries": [ { "query_block": { "select_id": 2, "table": { "table_name": "s1", "access_type": "index_subquery", "possible_keys": ["PRIMARY"], "key": "PRIMARY", "key_length": "4", "used_key_parts": ["emp_no"], "ref": ["func"], "r_loops": 0, "rows": 4, "r_rows": null, "r_total_time_ms": 690.43, "filtered": 100, "r_filtered": null } } } ] } }
# 메인쿼리의 데이터에 중복이 존재하고 서브쿼리의 데이터에 중복이 존재할 때
-- semijoin 기능 on MariaDB [employees]> set optimizer_switch='semijoin=on'; Query OK, 0 rows affected (0.00 sec) MariaDB [employees]> explain extended -> SELECT * -> FROM titles a -> where a.emp_no in (select s1.emp_no -> from salaries s1 -> where s1.salary > 66961); +------+--------------+-------------+--------+---------------+--------------+---------+------+---------+----------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +------+--------------+-------------+--------+---------------+--------------+---------+------+---------+----------+-------------+ | 1 | PRIMARY | a | ALL | PRIMARY | NULL | NULL | NULL | 443021 | 100.00 | | | 1 | PRIMARY |-- 서브쿼리를 임시테이블로 생성해서 메인쿼리와 조인을 한다. IN(Subquery) 형태의 쿼리는 유일한 데이터만 리턴해야하기 때문에 중복 데이터를 제거한 후(distinct_key) 조인을 시도한다.| eq_ref | distinct_key | distinct_key | 4 | func | 1 | 100.00 | | | 2 | MATERIALIZED | s1 | ALL | PRIMARY | NULL | NULL | NULL | 2838426 | 100.00 | Using where | +------+--------------+-------------+--------+---------------+--------------+---------+------+---------+----------+-------------+ 3 rows in set, 1 warning (0.00 sec) MariaDB [employees]> show warnings; +-------+------+------------------------------------------------------------------ select "employees"."a"."emp_no" AS "emp_no","employees"."a"."title" AS "title" ,"employees"."a"."from_date" AS "from_date","employees"."a"."to_date" AS "to_date" from "employees"."titles" "a" semi join ("employees"."salaries" "s1") where (("employees"."s1"."salary" > 66961)) | +-------+------+------------------------------------------------------------------ { "query_block": { "select_id": 1, "r_loops": 1, "r_total_time_ms": 1099.8, "table": { "table_name": "a", "access_type": "ALL", "possible_keys": ["PRIMARY"], "r_loops": 1, "rows": 442724, "r_rows": 443308, "r_total_time_ms": 97.457, "filtered": 100, "r_filtered": 100 }, "table": { "table_name": " ", "access_type": "eq_ref", "possible_keys": ["distinct_key"], "key": "distinct_key", "key_length": "4", "used_key_parts": ["emp_no"], "ref": ["func"], "r_loops": 443308, "rows": 1, "r_rows": 0.5698, "r_total_time_ms": 244.72, "filtered": 100, "r_filtered": 100, "materialized": { "unique": 1, "query_block": { "select_id": 2, "table": { "table_name": "s1", "access_type": "ALL", "possible_keys": ["PRIMARY"], "r_loops": 1, "rows": 2838426, "r_rows": 2.84e6, "r_total_time_ms": 491.01, "filtered": 100, "r_filtered": 37.64, "attached_condition": "(s1.salary > 66961)" } } } } } }
-- semijoin 기능 off MariaDB [employees]> set optimizer_switch='semijoin=off'; Query OK, 0 rows affected (0.00 sec) MariaDB [employees]> explain extended -> SELECT * -> FROM titles a -> where a.emp_no in (select s1.emp_no -> from salaries s1 -> where s1.salary > 66961); +------+--------------+-------+------+---------------+------+---------+------+---------+----------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +------+--------------+-------+------+---------------+------+---------+------+---------+----------+-------------+ | 1 | PRIMARY | a | ALL | NULL | NULL | NULL | NULL | 443021 | 100.00 | Using where | | 2 | MATERIALIZED | s1 | ALL | PRIMARY | NULL | NULL | NULL | 2838426 | 100.00 | Using where | +------+--------------+-------+------+---------------+------+---------+------+---------+----------+-------------+ 2 rows in set, 1 warning (0.00 sec) MariaDB [employees]> show warnings; +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- select "employees"."a"."emp_no" AS "emp_no","employees"."a"."title" AS "title","employees"."a"."from_date" AS "from_date","employees"."a"."to_date" AS "to_date" from "employees"."titles" "a" where--<"employees"."a"."emp_no">( ("employees"."a"."emp_no","employees"."a"."emp_no" in ( (select "employees"."s1"."emp_no" from "employees"."salaries" "s1" where ("employees"."s1"."salary" > 66961) ), ("employees"."a"."emp_no" in on distinct_key where (("employees"."a"."emp_no" = " "."emp_no")))))) +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- { "query_block": { "select_id": 1, "r_loops": 1, "r_total_time_ms": 1086.6, "table": { "table_name": "a", "access_type": "ALL", "possible_keys": ["PRIMARY"], "r_loops": 1, "rows": 442724, "r_rows": 443308, "r_total_time_ms": 98.412, "filtered": 100, "r_filtered": 100 }, "table": { "table_name": " ", "access_type": "eq_ref", "possible_keys": ["distinct_key"], "key": "distinct_key", "key_length": "4", "used_key_parts": ["emp_no"], "ref": ["func"], "r_loops": 443308, "rows": 1, "r_rows": 0.5698, "r_total_time_ms": 231.39, "filtered": 100, "r_filtered": 100, "materialized": { "unique": 1, "query_block": { "select_id": 2, "table": { "table_name": "s1", "access_type": "ALL", "possible_keys": ["PRIMARY"], "r_loops": 1, "rows": 2838426, "r_rows": 2.84e6, "r_total_time_ms": 490.89, "filtered": 100, "r_filtered": 37.64, "attached_condition": "(s1.salary > 66961)" } } } } } }
-- semijoin 기능 on MariaDB [employees]> set optimizer_switch='semijoin=on'; Query OK, 0 rows affected (0.00 sec) SELECT * FROM titles a where a.emp_no in (select s1.emp_no from salaries s1 where s1.salary > 66961 group by s1.emp_no); +------+--------------+-------------+--------+---------------+--------------+---------+------+---------+----------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +------+--------------+-------------+--------+---------------+--------------+---------+------+---------+----------+-------------+ | 1 | PRIMARY | a | ALL | PRIMARY | NULL | NULL | NULL | 443021 | 100.00 | | | 1 | PRIMARY |-- 서브쿼리의 데이터를 유일하게 만들어도 내부적으로 distinct_key작업을 진행한다.| eq_ref | distinct_key | distinct_key | 4 | func | 1 | 100.00 | | | 2 | MATERIALIZED | s1 | ALL | PRIMARY | NULL | NULL | NULL | 2838426 | 100.00 | Using where | +------+--------------+-------------+--------+---------------+--------------+---------+------+---------+----------+-------------+ 3 rows in set, 1 warning (0.00 sec) +-------+------+------------------------------------------------------------------ select "employees"."a"."emp_no" AS "emp_no","employees"."a"."title" AS "title" ,"employees"."a"."from_date" AS "from_date","employees"."a"."to_date" AS "to_date" from "employees"."titles" "a" semi join ("employees"."salaries" "s1") where (("employees"."s1"."salary" > 66961)) | +-------+------+------------------------------------------------------------------ { "query_block": { "select_id": 1, "r_loops": 1, "r_total_time_ms": 1095.1, "table": { "table_name": "a", "access_type": "ALL", "possible_keys": ["PRIMARY"], "r_loops": 1, "rows": 442724, "r_rows": 443308, "r_total_time_ms": 97.421, "filtered": 100, "r_filtered": 100 }, "table": { "table_name": " ", "access_type": "eq_ref", "possible_keys": ["distinct_key"], "key": "distinct_key", "key_length": "4", "used_key_parts": ["emp_no"], "ref": ["func"], "r_loops": 443308, "rows": 1, "r_rows": 0.5698, "r_total_time_ms": 241.28, "filtered": 100, "r_filtered": 100, "materialized": { "unique": 1, "query_block": { "select_id": 2, "table": { "table_name": "s1", "access_type": "ALL", "possible_keys": ["PRIMARY"], "r_loops": 1, "rows": 2838426, "r_rows": 2.84e6, "r_total_time_ms": 489.78, "filtered": 100, "r_filtered": 37.64, "attached_condition": "(s1.salary > 66961)" } } } } } }
'MariaDB > SQL Tuning' 카테고리의 다른 글
[MariaDB][성능] 멀티 레인지 리드(multi range read) 최적화 (0) | 2016.09.25 |
---|---|
[MariaDB][성능] LEFT OUTER JOIN과 스칼라 서브쿼리 실행계획 비교2 (0) | 2016.08.28 |
[MariaDB][성능] LEFT OUTER JOIN과 스칼라 서브쿼리 실행계획 비교1 (0) | 2016.08.24 |
[MariaDB][성능] 스칼라 서브쿼리 실행계획2 (0) | 2016.08.17 |
[MariaDB][성능]인라인뷰 처리 방식 분석 (0) | 2016.08.17 |