目次
とある日
MySQL8.0.24がリリースされて追加された機能についてツイッターで見かけました。
1箇所しかない…? (つд⊂)ゴシゴシ
— yoku0825 (@yoku0825) 2021年4月20日
MySQL :: MySQL 8.0 Reference Manual :: 1.3 What Is New in MySQL 8.0 https://t.co/4IWK3jwRxz
8.0.24 と記載されていた記述は、optimizer_switch フラグについてでした。
MySQL 8.0.24 以降、この最適化は、相関スカラーサブクエリに追加のグループ化を適用し、次に解除された述語に外部結合を適用することによって、相関スカラーサブクエリに適用することもできます。たとえば、などのクエリ
SELECT * FROM t1 WHERE (SELECT a FROM t2 WHERE t2.a=t1.a) > 0
はSELECT t1.* FROM t1 LEFT OUTER JOIN (SELECT a, COUNT(*) AS ct FROM t2 GROUP BY a) AS derived ON t1.a = derived.a WHERE derived.a > 0
。として書き換えることができます 。MySQL はカーディナリティチェックを実行して、サブクエリが複数の行を返さないことを確認します(ER_SUBQUERY_NO_1_ROW
)。詳細については、セクション 13.2.11.7「相関サブクエリ」を参照してください。
8.0.24 の追加機能optimizer_switch フラグについて確認しつつ、クエリが書き換えられたかどうかの動作を確認してみたいと思います。
オプティマイザについてあまり詳しくないので寄り道しながら、検証したいと思います。
オプティマイザーノート
MySQL 8.0.24 での変更(2021-04-20、一般提供)
MySQL クエリオプティマイザは、派生テーブルの最適化を相関スカラーサブクエリに適用できるようになりました。これは、追加のグループ化を適用してから、持ち上げられた述部に外部結合を適用することによって行われます。たとえば、などのクエリ
SELECT * FROM t1 WHERE (SELECT a FROM t2 WHERE t2.a=t1.a) > 0
はSELECT t1.* FROM t1 LEFT OUTER JOIN (SELECT a, COUNT(*) AS ct FROM t2 GROUP BY a) AS derived ON t1.a = derived.a WHERE derived.a > 0
。として書き換えることができます。サブクエリにすでに明示的なグループ化がある場合、MySQL は既存のグループ化リストの最後に追加のグループ化を追加します。
MySQL はカーディナリティチェックを実行して、サブクエリが複数の行を返さないことを確認し、返す
ER_SUBQUERY_NO_1_ROW
場合は発生します。チェックは、リフトされた述語を評価する前に、書き換えられたクエリの anyWHERE
またはJOIN
句の評価の一部として実行さ れます。
オプティマイザが最適なクエリに変換を行う過程でサブクエリを外部結合を適用するクエリに変換することができる事になった。
SELECT * FROM t1 WHERE ( SELECT a FROM t2 WHERE t2.a=t1.a ) > 0
↓ オプティマイザが変換する。
SELECT t1.* FROM t1 LEFT OUTER JOIN ( SELECT a, COUNT(*) AS ct FROM t2 GROUP BY a ) AS derived ON t1.a = derived.a WHERE derived.a > 0
SQL 変換検証
sort, sort10 の 2 つのテーブルを使用します。
過去に別で検証してたときのテーブルです。
構造等に意図は特にないです。
件数量が違うだけで基本同じテーブルです。
mysql> show create table sort\G; *************************** 1. row *************************** Table: sort Create Table: CREATE TABLE `sort` ( `id` bigint NOT NULL AUTO_INCREMENT, `str` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci 1 row in set (0.00 sec) ERROR: No query specified mysql> select count(*) from sort10; +----------+ | count(*) | +----------+ | 12 | +----------+ 1 row in set (0.00 sec) mysql> select count(*) from sort; +----------+ | count(*) | +----------+ | 100 | +----------+ 1 row in set (0.00 sec) mysql> select * from sort10; +----+------------+ | id | name | +----+------------+ | 1 | c65356e6ff | | 2 | cb41b32a8e | | 3 | 45fccb2a00 | | 4 | 8fb650cb2f | | 8 | 83a6747484 | | 9 | b7e63e81ad | | 10 | 2f28f615e6 | | 16 | bc3189e0b9 | | 17 | bfb0cde0fa | | 18 | e9d8b754df | | 19 | 117ebc3e67 | | 20 | 05f47f1bb1 | +----+------------+ 12 rows in set (0.00 sec)
オプティマイザ変換前サブクエリ
mysql> select * from sort as t1 where ( select id from sort10 as t2 where t1.id = t2.id ) > 0; +----+------------+ | id | str | +----+------------+ | 1 | abd5525855 | | 2 | f0e3869b76 | | 3 | cf977e2348 | | 4 | 497b8acf05 | | 8 | a9b7fcba45 | | 9 | e1b5df235e | | 10 | 4d42ada21d | | 16 | 75db54fe15 | | 17 | bd5f5154f3 | | 18 | 018ca2f453 | | 19 | c5e6caaa1c | | 20 | 0af66df725 | +----+------------+ 12 rows in set (0.01 sec)
実行計画
mysql> explain select * from sort10000 as t1 where ( select id from sort100000 as t2 where t1.id = t2.id ) > 0\G; *************************** 1. row *************************** id: 1 select_type: PRIMARY table: t1 partitions: NULL type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 9986 filtered: 100.00 Extra: Using where *************************** 2. row *************************** id: 2 select_type: DEPENDENT SUBQUERY table: t2 partitions: NULL type: eq_ref possible_keys: PRIMARY key: PRIMARY key_len: 8 ref: hobby.t1.id rows: 1 filtered: 100.00 Extra: Using index 2 rows in set, 2 warnings (0.00 sec)
オプティマイザ変換後外部結合クエリ
mysql> SELECT -> t1.* -> FROM -> sort as t1 -> LEFT OUTER JOIN ( -> SELECT -> id, -> COUNT(*) AS ct -> FROM -> sort10 as t2 -> GROUP BY -> id -> ) AS derived -> ON -> t1.id = derived.id -> WHERE -> derived.id > 0; +----+------------+ | id | str | +----+------------+ | 1 | abd5525855 | | 2 | f0e3869b76 | | 3 | cf977e2348 | | 4 | 497b8acf05 | | 8 | a9b7fcba45 | | 9 | e1b5df235e | | 10 | 4d42ada21d | | 16 | 75db54fe15 | | 17 | bd5f5154f3 | | 18 | 018ca2f453 | | 19 | c5e6caaa1c | | 20 | 0af66df725 | +----+------------+ 12 rows in set (0.00 sec)
実行計画
mysql> explain SELECT t1.* FROM sort10000 as t1 LEFT OUTER JOIN ( SELECT id, COUNT(*) AS ct FROM sort100000 as t2 GROUP BY id ) AS derived ON t1.id = derived.id WHERE derived.id > 0\G; *************************** 1. row *************************** id: 1 select_type: PRIMARY table: <derived2> partitions: NULL type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 50038 filtered: 100.00 Extra: Using where *************************** 2. row *************************** id: 1 select_type: PRIMARY table: t1 partitions: NULL type: eq_ref possible_keys: PRIMARY key: PRIMARY key_len: 8 ref: derived.id rows: 1 filtered: 100.00 Extra: NULL *************************** 3. row *************************** id: 2 select_type: DERIVED table: t2 partitions: NULL type: range possible_keys: PRIMARY key: PRIMARY key_len: 8 ref: NULL rows: 50038 filtered: 100.00 Extra: Using where; Using index 3 rows in set, 1 warning (0.01 sec)
おまけ速度計測
テーブル件数が多い物があったので実験し見ました。
出力件数が多いので limit してます。
quesry | 10 rows | 10000 rows |
---|---|---|
オプティマイザ変換前サブクエリ | 0.00 sec | 0.02 sec |
オプティマイザ変換後外部結合クエリ | 0.08 sec | 0.52 sec |
変換しないほうが早かった....
mysql> select * from sort10000 as t1 where ( select id from sort100000 as t2 where t1.id = t2.id ) > 0 limit 10; +----+------------+ | id | str | +----+------------+ | 1 | 10c0d80761 | | 2 | 4a3a304a4d | | 3 | 0de5845426 | | 4 | b23b5f308f | | 5 | fb95b6ab20 | | 6 | c3780d2cfb | | 7 | e7af555de5 | | 8 | 21e79e573f | | 9 | 374c9f3b18 | | 10 | 860109ed03 | +----+------------+ 10 rows in set (0.00 sec) mysql> SELECT t1.* FROM sort10000 as t1 LEFT OUTER JOIN ( SELECT id, COUNT(*) AS ct FROM sort100000 as t2 GROUP BY id ) AS derived ON t1.id = derived.id WHERE derived.id > 0 limit 10; +----+------------+ | id | str | +----+------------+ | 1 | 10c0d80761 | | 2 | 4a3a304a4d | | 3 | 0de5845426 | | 4 | b23b5f308f | | 5 | fb95b6ab20 | | 6 | c3780d2cfb | | 7 | e7af555de5 | | 8 | 21e79e573f | | 9 | 374c9f3b18 | | 10 | 860109ed03 | +----+------------+ 10 rows in set (0.08 sec)
optimizer_switch flag
今回変更するoptimizer_switct flagについて少しだけリファレンス引用。
optimizer_switch
システム変数は、オプティマイザの動作を制御することができます。この変数の値はフラグのセットであり、各フラグには、対応するオプティマイザーの動作が有効か無効かを示す、on
またはの値がありoff
ます。この変数にはグローバル値とセッション値があり、実行時に変更できます。グローバルデフォルトは、サーバーの起動時に設定できます。
mysql> select @@optimizer_switch\G; *************************** 1. row *************************** @@optimizer_switch: index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=on,condition_fanout_filter=on,derived_merge=on,use_invisible_indexes=off,skip_scan=on,hash_join=on,subquery_to_derived=off,prefer_ordering_index=on,hypergraph_optimizer=off,derived_condition_pushdown=on 1 row in set (0.00 sec)
subquery_to_derived=offをon
に変更して挙動を確認しようと思います。
subquery_to_derived(本題)
MySQL の 8.0.21 以降では、オプティマイザは、スカラー副問合せを変換するために、多くのケースでできている
SELECT
、WHERE
、JOIN
、またはHAVING
左の外側の派生テーブルに参加するに句。(派生テーブルの null 可能性によっては、これをさらに内部結合に簡略化できる場合があります。)これは、次の条件を満たすサブクエリに対して実行できます。
- サブクエリは、などの非決定論的関数を使用しません
RAND()
。- サブクエリはない
ANY
か、ALL
使用するように書き換えることができますサブクエリMIN()
かMAX()
。- 親クエリはユーザー変数を設定しません。これを書き換えると実行順序に影響する可能性があり、同じクエリで変数に複数回アクセスすると予期しない結果が生じる可能性があるためです。
- サブクエリは相関させてはなりません。つまり、外部クエリのテーブルの列を参照したり、外部クエリで評価される集計を含めたりしないでください。
MySQL 8.0.22 より前は、サブクエリに
GROUP BY
句を含めることはできませんでした 。
デフォルト値
この最適化は、ほとんどの場合、目立ったパフォーマンス上の利点が得られないため、通常は無効になっています。フラグは
off
デフォルトでに設定されています。
悲しい現実。
クエリ変換検証
クエリが変換されたかどうかをオプティマイザトレースを使って検証します。
詳しい解説は下記の記事を参照してください。
1. 現在の subquery_to_derived 確認
mysql> SELECT @@optimizer_switch LIKE '%subquery_to_derived=off%'; +-----------------------------------------------------+ | @@optimizer_switch LIKE '%subquery_to_derived=off%' | +-----------------------------------------------------+ | 1 | +-----------------------------------------------------+
2. subquery_to_derived 設定変更
mysql> SET optimizer_switch='subquery_to_derived=on'; Query OK, 0 rows affected (0.00 sec)
3.オプティマイザトレース有効化
mysql> SET optimizer_trace='enabled=on'; Query OK, 0 rows affected (0.00 sec)
4.クエリ発行
mysql> select * from sort10 where sort10.id < (select count(id) from sort); +----+------------+ | id | name | +----+------------+ | 1 | c65356e6ff | | 2 | cb41b32a8e | | 3 | 45fccb2a00 | | 4 | 8fb650cb2f | | 8 | 83a6747484 | | 9 | b7e63e81ad | | 10 | 2f28f615e6 | | 16 | bc3189e0b9 | | 17 | bfb0cde0fa | | 18 | e9d8b754df | | 19 | 117ebc3e67 | | 20 | 05f47f1bb1 | +----+------------+ 12 rows in set (0.00 sec)
5. オプティマイザトレース・オン
mysql> SELECT * FROM information_schema.optimizer_trace\G; *************************** 1. row *************************** QUERY: select * from sort10 where sort10.id < (select count(id) from sort) TRACE: { "steps": [ { "join_preparation": { "select#": 1, "steps": [ { "join_preparation": { "select#": 2, "steps": [ { "expanded_query": "/* select#2 */ select count(`sort`.`id`) from `sort`" } ] } }, { "expanded_query": "/* select#1 */ select `sort10`.`id` AS `id`,`sort10`.`name` AS `name` from `sort10` where (`sort10`.`id` < (/* select#2 */ select count(`sort`.`id`) from `sort`))" }, { "derived": { "table": " `derived_1_2`", "select#": 2, "materialized": true } }, { "transformation": { "select#": 2, "from": "scalar subquery", "to": "derived table", "expanded_query": "/* select#1 */ select `sort10`.`id` AS `id`,`sort10`.`name` AS `name` from (`sort10` left join (/* select#2 */ select count(`sort`.`id`) AS `count(id)` from `sort`) `derived_1_2` on(true)) where (`sort10`.`id` < `derived_1_2`.`count(id)`)" } }, { "transformations_to_nested_joins": { "transformations": [ "outer_join_to_inner_join", "JOIN_condition_to_WHERE", "parenthesis_removal" ], "expanded_query": "/* select#1 */ select `sort10`.`id` AS `id`,`sort10`.`name` AS `name` from `sort10` join (/* select#2 */ select count(`sort`.`id`) AS `count(id)` from `sort`) `derived_1_2` where ((`sort10`.`id` < `derived_1_2`.`count(id)`))" } }, { "condition_pushdown_to_derived": { "table": " `derived_1_2`", "original_condition": "((`sort10`.`id` < `derived_1_2`.`count(id)`))", "steps": [ { "condition_pushdown": "checking_for_columns_in_derived_table", "remaining_condition": "((`sort10`.`id` < `derived_1_2`.`count(id)`))" } ] } } ] } }, { "join_optimization": { "select#": 1, "steps": [ { "join_optimization": { "select#": 2, "steps": [ { "table_dependencies": [ { "table": "`sort`", "row_may_be_null": false, "map_bit": 0, "depends_on_map_bits": [ ] } ] }, { "rows_estimation": [ { "table": "`sort`", "table_scan": { "rows": 100, "cost": 0.25 } } ] }, { "considered_execution_plans": [ { "plan_prefix": [ ], "table": "`sort`", "best_access_path": { "considered_access_paths": [ { "rows_to_scan": 100, "access_type": "scan", "resulting_rows": 100, "cost": 10.25, "chosen": true } ] }, "condition_filtering_pct": 100, "rows_for_plan": 100, "cost_for_plan": 10.25, "chosen": true } ] }, { "attaching_conditions_to_tables": { "original_condition": null, "attached_conditions_computation": [ ], "attached_conditions_summary": [ { "table": "`sort`", "attached": null } ] } }, { "optimizing_distinct_group_by_order_by": { } }, { "finalizing_table_conditions": [ ] }, { "refine_plan": [ { "table": "`sort`" } ] }, { "considering_tmp_tables": [ ] } ] } }, { "creating_tmp_table": { "tmp_table_info": { "table": " `derived_1_2`", "columns": 1, "row_length": 9, "key_length": 0, "unique_constraint": false, "makes_grouped_rows": false, "cannot_insert_duplicates": false, "location": "TempTable" } } }, { "join_execution": { "select#": 2, "steps": [ ] } }, { "condition_processing": { "condition": "WHERE", "original_condition": "((`sort10`.`id` < `derived_1_2`.`count(id)`))", "steps": [ { "transformation": "equality_propagation", "subselect_evaluation": [ ], "resulting_condition": "((`sort10`.`id` < `derived_1_2`.`count(id)`))" }, { "transformation": "constant_propagation", "subselect_evaluation": [ ], "resulting_condition": "((`sort10`.`id` < `derived_1_2`.`count(id)`))" }, { "transformation": "trivial_condition_removal", "subselect_evaluation": [ ], "resulting_condition": "(`sort10`.`id` < `derived_1_2`.`count(id)`)" } ] } }, { "substitute_generated_columns": { } }, { "table_dependencies": [ { "table": "`sort10`", "row_may_be_null": false, "map_bit": 0, "depends_on_map_bits": [ ] }, { "table": " `derived_1_2`", "row_may_be_null": true, "map_bit": 1, "depends_on_map_bits": [ ] } ] }, { "ref_optimizer_key_uses": [ ] }, { "rows_estimation": [ { "table": "`sort10`", "table_scan": { "rows": 12, "cost": 0.25 } }, { "table": " `derived_1_2`", "table_scan": { "rows": 1, "cost": 2.5125 } } ] }, { "considered_execution_plans": [ { "plan_prefix": [ ], "table": " `derived_1_2`", "best_access_path": { "considered_access_paths": [ { "rows_to_scan": 1, "filtering_effect": [ ], "final_filtering_effect": 1, "access_type": "scan", "resulting_rows": 1, "cost": 2.6125, "chosen": true } ] }, "condition_filtering_pct": 100, "rows_for_plan": 1, "cost_for_plan": 2.6125, "rest_of_plan": [ { "plan_prefix": [ " `derived_1_2`" ], "table": "`sort10`", "best_access_path": { "considered_access_paths": [ { "rows_to_scan": 12, "filtering_effect": [ ], "final_filtering_effect": 1, "access_type": "scan", "using_join_cache": true, "buffers_needed": 1, "resulting_rows": 12, "cost": 1.45001, "chosen": true } ] }, "condition_filtering_pct": 100, "rows_for_plan": 12, "cost_for_plan": 4.06251, "chosen": true } ] }, { "plan_prefix": [ ], "table": "`sort10`", "best_access_path": { "considered_access_paths": [ { "rows_to_scan": 12, "filtering_effect": [ ], "final_filtering_effect": 1, "access_type": "scan", "resulting_rows": 12, "cost": 1.45, "chosen": true } ] }, "condition_filtering_pct": 100, "rows_for_plan": 12, "cost_for_plan": 1.45, "rest_of_plan": [ { "plan_prefix": [ "`sort10`" ], "table": " `derived_1_2`", "best_access_path": { "considered_access_paths": [ { "rows_to_scan": 1, "filtering_effect": [ ], "final_filtering_effect": 1, "access_type": "scan", "using_join_cache": true, "buffers_needed": 1, "resulting_rows": 1, "cost": 3.75931, "chosen": true } ] }, "condition_filtering_pct": 100, "rows_for_plan": 12, "cost_for_plan": 5.20931, "pruned_by_cost": true } ] } ] }, { "attaching_conditions_to_tables": { "original_condition": "(`sort10`.`id` < `derived_1_2`.`count(id)`)", "attached_conditions_computation": [ { "table": "`sort10`", "rechecking_index_usage": { "recheck_reason": "not_first_table", "range_analysis": { "table_scan": { "rows": 12, "cost": 3.55 }, "potential_range_indexes": [ { "index": "PRIMARY", "usable": true, "key_parts": [ "id" ] } ], "setup_range_conditions": [ ], "group_index_range": { "chosen": false, "cause": "not_single_table" }, "skip_scan_range": { "chosen": false, "cause": "not_single_table" }, "analyzing_range_alternatives": { "range_scan_alternatives": [ { "index": "PRIMARY", "chosen": false, "cause": "depends_on_unread_values" } ], "analyzing_roworder_intersect": { "usable": false, "cause": "too_few_roworder_scans" } } } } } ], "attached_conditions_summary": [ { "table": " `derived_1_2`", "attached": null }, { "table": "`sort10`", "attached": "(`sort10`.`id` < `derived_1_2`.`count(id)`)" } ] } }, { "finalizing_table_conditions": [ { "table": "`sort10`", "original_table_condition": "(`sort10`.`id` < `derived_1_2`.`count(id)`)", "final_table_condition ": "(`sort10`.`id` < `derived_1_2`.`count(id)`)" } ] }, { "refine_plan": [ { "table": " `derived_1_2`" }, { "table": "`sort10`" } ] } ] } }, { "join_execution": { "select#": 1, "steps": [ { "rows_estimation_per_outer_row": { "table": "`sort10`", "range_analysis": { "table_scan": { "rows": 12, "cost": 3.55 }, "potential_range_indexes": [ { "index": "PRIMARY", "usable": true, "key_parts": [ "id" ] } ], "setup_range_conditions": [ ], "group_index_range": { "chosen": false, "cause": "not_single_table" }, "skip_scan_range": { "chosen": false, "cause": "not_single_table" }, "analyzing_range_alternatives": { "range_scan_alternatives": [ { "index": "PRIMARY", "ranges": [ "id < 100" ], "index_dives_for_eq_ranges": true, "rowid_ordered": true, "using_mrr": false, "index_only": false, "rows": 12, "cost": 1.46229, "chosen": true } ], "analyzing_roworder_intersect": { "usable": false, "cause": "too_few_roworder_scans" } }, "chosen_range_access_summary": { "range_access_plan": { "type": "range_scan", "index": "PRIMARY", "rows": 12, "ranges": [ "id < 100" ] }, "rows_for_plan": 12, "cost_for_plan": 1.46229, "chosen": true } } } } ] } } ] } MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0 INSUFFICIENT_PRIVILEGES: 0 1 row in set (0.00 sec) ERROR: No query specified mysql>
7.クエリ書き換え確認
重要なところだけ抜き出しました。
オプティマイザが変形したクエリとして、left
が使用されたクエリが生成される。
"transformation": { "select#": 2, "from": "scalar subquery", "to": "derived table", "expanded_query": "/* select#1 */ select `sort10`.`id` AS `id`,`sort10`.`name` AS `name` from (`sort10` left join (/* select#2 */ select count(`sort`.`id`) AS `count(id)` from `sort`) `derived_1_2` on(true)) where (`sort10`.`id` < `derived_1_2`.`count(id)`)" } },
お試し実行。
mysql> select `sort10`.`id` AS `id`,`sort10`.`name` AS `name` from (`sort10` left join (/* select#2 */ select count(`sort`.`id`) AS `count(id)` from `sort`) `derived_1_2` on(true)) where (`sort10`.`id` < `derived_1_2`.`count(id)`) ; +----+------------+ | id | name | +----+------------+ | 1 | c65356e6ff | | 2 | cb41b32a8e | | 3 | 45fccb2a00 | | 4 | 8fb650cb2f | | 8 | 83a6747484 | | 9 | b7e63e81ad | | 10 | 2f28f615e6 | | 16 | bc3189e0b9 | | 17 | bfb0cde0fa | | 18 | e9d8b754df | | 19 | 117ebc3e67 | | 20 | 05f47f1bb1 | +----+------------+ 12 rows in set (0.00 sec)
もとのクエリと同じ結果が表示された。
さらについでに、EXPLAIN で実行計画確認。
mysql> explain select `sort10`.`id` AS `id`,`sort10`.`name` AS `name` from (`sort10` left join (/* select#2 */ select count(`sort`.`id`) AS `count(id)` from `sort`) `derived_1_2` on(true)) where (`sort10`.`id` < `derived_1_2`.`count(id)`)\G ; *************************** 1. row *************************** id: 1 select_type: PRIMARY table: <derived2> partitions: NULL type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 1 filtered: 100.00 Extra: NULL *************************** 2. row *************************** id: 1 select_type: PRIMARY table: sort10 partitions: NULL type: ALL possible_keys: PRIMARY key: NULL key_len: NULL ref: NULL rows: 12 filtered: 33.33 Extra: Range checked for each record (index map: 0x1) *************************** 3. row *************************** id: 2 select_type: DERIVED table: sort partitions: NULL type: index possible_keys: NULL key: PRIMARY key_len: 8 ref: NULL rows: 100 filtered: 100.00 Extra: Using index 3 rows in set, 1 warning (0.00 sec)
〆
クエリの書き換えは確認できましたが、最適なクエリの変換かどうかは判定するのが難しそうなので今回は割愛します。
事前チェックで速度が遅くなることの確認や、リファレンスでも速度に期待できないとあったので普段遣いはしなさそうですね。