今日はなにの日。

気になったこと勉強になったことのメモ。

今日は、MySQL8.0.24の変更点オプティマイザーノートについての日。

目次

とある日

MySQL8.0.24がリリースされて追加された機能についてツイッターで見かけました。

8.0.24 と記載されていた記述は、optimizer_switch フラグについてでした。

1.3 MySQL8.0 の新機能

MySQL 8.0.24 以降、この最適化は、相関スカラーサブクエリに追加のグループ化を適用し、次に解除された述語に外部結合を適用することによって、相関スカラーサブクエリに適用することもできます。たとえば、などのクエリ SELECT * FROM t1 WHERE (SELECT a FROM t2 WHERE t2.a=t1.a) > 0SELECT 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) > 0SELECT 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=offonに変更して挙動を確認しようと思います。

subquery_to_derived(本題)

MySQL の 8.0.21 以降では、オプティマイザは、スカラー副問合せを変換するために、多くのケースでできている SELECTWHEREJOIN、または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)

クエリの書き換えは確認できましたが、最適なクエリの変換かどうかは判定するのが難しそうなので今回は割愛します。

事前チェックで速度が遅くなることの確認や、リファレンスでも速度に期待できないとあったので普段遣いはしなさそうですね。