MariaDB/SQL Tuning

[MariaDB][성능] 서브쿼리 최적화

알 수 없는 사용자 2016. 9. 25. 19:23
[테스트 환경] 
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'인 데이터를 파라미터 값

<"employee"."e"."emp_no"> 에 따라서 에 출력되는 결과 값을 캐싱해두어 사용한다. 서브쿼리의 r_loops가 1인 것을 확인한다.


메인쿼리의 데이터가 유일하고 서브쿼리의 데이터에 중복이 존재할 때
-- 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으로 쿼리변환을 하면 변환하기 전의 데이터와 동일하지 않은 결과를 리턴한다. 
 따라서  semi join으로 쿼리를 변환해서 salary > 66961를 만족하는 데이터가 존재하면 리턴하는 방식으로 진행한다.


-- 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 <"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
          }
        }
      }
    ]
  }
} 
-- 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) 
서브쿼리 내의 데이터가 유일하지 않기 때문에 캐싱이 exists로 나타난 것을 확인할 수 있다.



메인쿼리의 데이터에 중복이 존재하고 서브쿼리의 데이터에 중복이 존재할 때


-- 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      |  | 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)"
          }
        }
      }
    }
  }
}
-- 서브쿼리를 임시테이블로 생성해서 메인쿼리와 조인을 한다. IN(Subquery) 형태의 쿼리는 유일한 데이터만 리턴해야하기 때문에 중복 데이터를 제거한 후(distinct_key) 조인을 시도한다.

 
-- 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)"
          }
        }
      }
    }
  }
}
-- titles 테이블의 pk 인덱스를 통해서 임시 테이블을 검색한다.(출처 :http://dev.mysql.com/doc/refman/5.7/en/explain-extended.html) -- 서브쿼리의 데이터를 group by 로 유일하게 만든 후 진행.

-- 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      |  | 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)"
          }
        }
      }
    }
  }
}
-- 서브쿼리의 데이터를 유일하게 만들어도 내부적으로 distinct_key작업을 진행한다.