以下のデモはMySQLバージョン5.7.27に基づいています。 1. MySQLサブクエリ最適化戦略の概要: サブクエリ最適化戦略 オプティマイザーは、サブクエリの種類に応じて異なる戦略を選択します。 1. IN および =ANY サブクエリの場合、オプティマイザには次の戦略の選択肢があります。
2. NOT IN および <> ALL サブクエリの場合、オプティマイザには次の戦略の選択肢があります。
3. 派生テーブルの場合、オプティマイザには次の戦略オプションがあります。 2. シミュレーションデモンストレーション用のデータを作成する 問題の分析を容易にするために、2 つのテーブルを作成し、シミュレートされたデータを挿入します。 テーブル「test02」を作成します( `id` int(11) NULLではない、 `a` int(11) デフォルト NULL, `b` int(11) デフォルト NULL, 主キー (`id`)、 キー `a` (`a`) )ENGINE=InnoDB; 手順 idata を削除します。 デリミタ;; プロシージャ idata() を作成する 始める iをintとして宣言します。 i=1 に設定します。 i<=10000の間、 test02に値(i, i, i)を挿入します。 i=i+1 と設定します。 終了しながら; 終わり;; 区切り文字 ; idata() を呼び出す。 test02 のようなテーブル test01 を作成します。 test01 に挿入 (test02 から id<=1000 の * を選択) 3. SQLインスタンスの分析例 サブクエリの例: SELECT * FROM test01 WHERE test01.a IN (SELECT test02.b FROM test02 WHERE id < 10) ほとんどの人は、この SQL が次のように実行されると考えるでしょう。 SELECT test02.b FROM test02 WHERE id < 10 結果: 1、2、3、4、5、6、7、8、9 test01 から * を選択 WHERE test01.a IN (1,2,3,4,5,6,7,8,9); しかし、実際には MySQL はそうは動作しません。 MySQL は関連する外部テーブルをサブクエリにプッシュし、オプティマイザはこれがより効率的であると判断します。つまり、オプティマイザーは上記の SQL を次のように書き換えます。 存在する場合は test01 から * を選択します (ID < 10 かつ test01.a=test02.b の場合は test02 から b を選択します)。 ヒント: MySQL 5.5以前のバージョンの場合 次のように実行プランを確認すると、この SQL がテーブル test01 に対して完全なテーブル スキャンを 1000 回実行していることがわかります。これは非効率的です。 root@localhost [dbtest01]>desc select * from test01 where exists(select b from test02 where id < 10 and test01.a=test02.b); +----+--------------------+---------+-----------+---------+---------------+----------+---------+----------+-----------+------------+ | id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 | +----+--------------------+---------+-----------+---------+---------------+----------+---------+----------+-----------+------------+ | 1 | PRIMARY | test01 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | where の使用 | | 2 | 従属サブクエリ | test02 | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 9 | 10.00 | where の使用 | +----+--------------------+---------+-----------+---------+---------------+----------+---------+----------+-----------+------------+ セットに 2 行、警告 2 件 (0.00 秒) しかし、実際に次の SQL を実行すると、まったく遅くないことがわかります。これは矛盾ではありませんか? 心配しないでください。分析を続けましょう。 SELECT * FROM test01 WHERE test01.a IN (SELECT test02.b FROM test02 WHERE id < 10) この SQL の実行プランを次のように確認します。 root@localhost [dbtest01]>desc SELECT * FROM test01 WHERE test01.a IN (SELECT test02.b FROM test02 WHERE id < 10); +----+--------------+--------------+------------+----------+---------------+---------+----------+------------+-------------+ | id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 | +----+--------------+--------------+------------+----------+---------------+---------+----------+------------+-------------+ | 1 | SIMPLE | <subquery2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | 100.00 | where の使用 | | 1 | SIMPLE | test01 | NULL | ref | a | a | 5 | <サブクエリ2>.b | 1 | 100.00 | NULL | | 2 | MATERIALIZED | test02 | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 9 | 100.00 | where の使用 | +----+--------------+--------------+------------+----------+---------------+---------+----------+------------+-------------+ セットに 3 行、警告 1 件 (0.00 秒) オプティマイザーは MATERIALIZED 戦略を使用していることがわかります。そこで私は情報を検索し、この戦略を研究しました。 その理由は、MySQL 5.6 以降、オプティマイザが新しい最適化戦略を導入したためです: マテリアライゼーション = [オフ|オン]、セミジョイン = [オフ|オン] (オフは、この戦略をオフにすることを意味し、オンは、この戦略をオンにすることを意味します) MySQL 5.7.27 のデフォルトのオプティマイザ戦略は次のとおりです。 root@localhost [dbtest01]> 'optimizer_switch' のような変数を表示します。 +------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 変数名 | 値 | +------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 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 | +------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- MySQL 5.6以降では 次の SQL を実行しても遅くなりません。 MySQLのオプティマイザ戦略マテリアライゼーションとセミジョインがこのSQLを最適化するため SELECT * FROM test01 WHERE test01.a IN (SELECT test02.b FROM test02 WHERE id < 10) ただし、テストのために MySQL オプティマイザー戦略のマテリアライゼーションとセミ結合をオフにしたところ、SQL が test01 (1000) のテーブル全体をスキャンしていることがわかりました。 グローバル optimizer_switch='materialization=off,semijoin=off' を設定します。 実行プランは次のようになります。test01 テーブルは実際に完全にスキャンされます。 root@localhost [dbtest01]>desc SELECT * FROM test01 WHERE test01.a IN (SELECT test02.b FROM test02 WHERE id < 10); +----+--------------------+---------+-----------+---------+---------------+----------+---------+----------+-----------+------------+ | id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 | +----+--------------------+---------+-----------+---------+---------------+----------+---------+----------+-----------+------------+ | 1 | PRIMARY | test01 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | where の使用 | | 2 | 従属サブクエリ | test02 | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 9 | 10.00 | where の使用 | +----+--------------------+---------+-----------+---------+---------------+----------+---------+----------+-----------+------------+ セットに 2 行、警告 1 件 (0.00 秒) この実行計画を分析してみましょう。 ! ! ! !再度のヒント: MySQL 5.5 以前のバージョン、または MySQL 5.6 以降のバージョンを使用し、オプティマイザ戦略の materialization=off、semijoin=off をオフにすると、取得する SQL 実行プランは次のようになります。 root@localhost [dbtest01]>desc select * from test01 where exists(select b from test02 where id < 10 and test01.a=test02.b); +----+--------------------+---------+-----------+---------+---------------+----------+-------+----------+------------+-------------+ | id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 | +----+--------------------+---------+-----------+---------+---------------+----------+-------+----------+------------+-------------+ | 1 | PRIMARY | test01 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | where の使用 | | 2 | 従属サブクエリ | test02 | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 9 | 10.00 | where の使用 | +----+--------------------+---------+-----------+---------+---------------+----------+-------+----------+------------+-------------+ セットに 2 行、警告 2 件 (0.00 秒) 非相関サブクエリは相関サブクエリ (select_type:DEPENDENT SUBQUERY) になります。サブクエリは、b に基づいて外部テーブル test01 に関連付ける必要があります。外部テーブルの test01 フィールドは必須であるため、サブクエリを最初に実行することはできません。実行プロセスは次のとおりです。
スキャンされた行の合計数は 1000+1000*9=10000 です (これは理論値ですが、実際の値は 10000 未満です。この値がどのようにして得られたのかはわかりません。ルールとしては、サブクエリの結果セットに行が追加されるたびに、スキャンされた行の合計数が数行ずつ減少します)。 セミ結合オプティマイザー: これにより問題が発生します。外部テーブルが非常に大きいテーブルの場合、サブクエリは外部クエリの各行に対して 1 回実行される必要があり、このクエリのパフォーマンスは非常に低下します。効率を上げるために、これを join に書き直すことは簡単に考えられます。 test01 から test01.* を選択し、test01.a=test02.b かつ test02.id<10 で test02 を結合します。 # この SQL の実行プランを表示します。 desc test01 から test01.* を選択し、test01.a=test02.b かつ test02.id<10 で test02 を結合します。 root@localhost [dbtest01]>EXPLAIN 拡張選択 test01.* から test01 を結合 test02 on test01.a=test02.b and test02.id<10; +----+-------------+---------+-----------+----------+---------------+---------+----------+-------------------+------------+-------------+ | id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 | +----+-------------+---------+-----------+----------+---------------+---------+----------+-------------------+------------+-------------+ | 1 | SIMPLE | test02 | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 9 | 100.00 | where の使用 | | 1 | SIMPLE | test01 | NULL | ref | a | a | 5 | dbtest01.test02.b | 1 | 100.00 | NULL | +----+-------------+---------+-----------+----------+---------------+---------+----------+-------------------+------------+-------------+ セットに 2 行、警告 2 件 (0.00 秒) この最適化により、t2 テーブルが駆動テーブルとなり、t1 テーブルの関連フィールドにインデックスが付けられるため、検索効率が非常に高くなります。 しかし、ここで問題があります。結合では重複した結果が取得される可能性がありますが、(select ...) サブクエリのセマンティクスでは重複した値は取得されません。 セミ結合最適化後の SQL と実行プランは次のとおりです。 root@localhost [dbtest01]>desc SELECT * FROM test01 WHERE test01.a IN (SELECT test02.b FROM test02 WHERE id < 10); +----+--------------+--------------+------------+----------+---------------+---------+----------+------------+-------------+ | id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 | +----+--------------+--------------+------------+----------+---------------+---------+----------+------------+-------------+ | 1 | SIMPLE | <subquery2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | 100.00 | where の使用 | | 1 | SIMPLE | test01 | NULL | ref | a | a | 5 | <サブクエリ2>.b | 1 | 100.00 | NULL | | 2 | MATERIALIZED | test02 | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 9 | 100.00 | where の使用 | +----+--------------+--------------+------------+----------+---------------+---------+----------+------------+-------------+ セットに 3 行、警告 1 件 (0.00 秒) 選択 `test01`.`id`、`test01`.`a`、`test01`.`b` `test01` から `test02` にセミ結合 どこ ((`test01`.`a` = `<サブクエリ2>`.`b`) そして (`test02`.`id` < 10)); ##これはオプティマイザによって書き換えられた SQL であることに注意してください。セミ結合構文はクライアントでは使用できません。 セミ結合の最適化実装は比較的複雑で、FirstMatch や Materialize などの戦略に分かれています。上記の実行プランでは、select_type=MATERIALIZED は、セミ結合が Materialize 戦略を使用して実装されていることを意味します。 まずサブクエリを実行し、結果を一時テーブルに保存します。この一時テーブルには重複排除用の主キーがあります。 MySQL 5.6 で追加されたもう 1 つの最適化機能はマテリアライゼーションです。これは、サブクエリの結果を一時テーブルにマテリアライズし、それを検索用の外部クエリに代入して、クエリの実行を高速化します。メモリ内の一時テーブルには主キー (ハッシュ インデックス) が含まれ、重複行が削除され、テーブルが小さくなります。 セミ結合とマテリアライゼーションの開始は、optimizer_switch パラメータの semijoin={on|off}、materialization={on|off} フラグによって制御されます。 削除関連のサブクエリの例を次に示します。 上記の2つのテストテーブルにそれぞれ350万件のデータと50万件のデータを入力して、削除ステートメントをテストします。 root@localhost [dbtest01]>test02からcount(*)を選択します。 +----------+ | カウント(*) | +----------+ |3532986| +----------+ セット内1列(0.64秒) root@localhost [dbtest01]>test02のようなテーブルtest01を作成します。 クエリは正常、影響を受けた行は 0 行 (0.01 秒) root@localhost [dbtest01]>test01 に挿入 (test02 から * を選択、ID <= 500000) root@localhost [dbtest01]>test01からcount(*)を選択します。 +----------+ | カウント(*) | +----------+ | 500000 | 削除ステートメントの実行には 4 秒かかりました。 root@localhost [dbtest01]> test01 から test01.a を削除します (test02 から test02.b を選択します)。 クエリは正常、9 行が影響を受けました (4.86 秒) 実行プランを見ると、test01 テーブルがほぼ完全にスキャンされていることがわかります。 root@localhost [dbtest01]>desc delete FROM test01 WHERE test01.a IN (SELECT test02.b FROM test02 WHERE id < 10); +----+--------------------+---------+-----------+---------+---------------+----------+---------+----------+-----------+------------+ | id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 | +----+--------------------+---------+-----------+---------+---------------+----------+---------+----------+-----------+------------+ | 1 | DELETE | test01 | NULL | ALL | NULL | NULL | NULL | NULL | 499343 | 100.00 | where の使用 | | 2 | 従属サブクエリ | test02 | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 9 | 10.00 | where の使用 | +----+--------------------+---------+-----------+---------+---------------+----------+---------+----------+-----------+------------+ セット内の 2 行 (0.00 秒) したがって、上記の削除SQL文を疑似結合文に変更します。 root@localhost [dbtest01]>desc test01 から test01.* を削除し、test01.a=test02.b および test02.id<10 で test02 を結合します。 +----+-------------+---------+-----------+----------+---------------+---------+----------+-------------------+------------+-------------+ | id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 | +----+-------------+---------+-----------+----------+---------------+---------+----------+-------------------+------------+-------------+ | 1 | SIMPLE | test02 | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 9 | 100.00 | where の使用 | | 1 | 削除 | test01 | NULL | ref | a | a | 5 | dbtest01.test02.b | 1 | 100.00 | NULL | +----+-------------+---------+-----------+----------+---------------+---------+----------+-------------------+------------+-------------+ セットに2行(0.01秒) 実行は非常に高速です。root@localhost [dbtest01]>delete test01.* from test01 join test02 on test01.a=test02.b and test02.id<10; クエリは正常、9 行が影響を受けました (0.01 秒) root@localhost [dbtest01]>test01 から test01.* を選択し、test01.a=test02.b および test02.id<10 で test02 を結合します。 空のセット (0.00 秒) 次のテーブル実行では、非常に遅いフルテーブルスキャンが必要です。基本的には、テーブル test01 でフルテーブルスキャンを実行します。 root@lcalhost [dbtest01]>desc delete FROM test01 WHERE id IN (SELECT id FROM test02 WHERE id='350000'); +----+--------------------+---------+-----------+---------+---------------+----------+---------+----------+-----------+------------+-------------+ | id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 | +----+--------------------+---------+-----------+---------+---------------+----------+---------+----------+-----------+------------+-------------+ | 1 | DELETE | test01 | NULL | ALL | NULL | NULL | NULL | NULL | 499343 | 100.00 | where の使用 | | 2 | 従属サブクエリ | test02 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | インデックスを使用 | +----+--------------------+---------+-----------+---------+---------------+----------+---------+----------+-----------+------------+-------------+ セット内の 2 行 (0.00 秒) ただし、join を使用すると、効率は非常に高くなります。 root@localhost [dbtest01]>desc delete test01.* FROM test01 inner join test02 WHERE test01.id=test02.id and test02.id=350000; +----+-------------+---------+-----------+---------+---------------+---------+--------+----------+-----------+-------------+ | id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 | +----+-------------+---------+-----------+---------+---------------+---------+--------+----------+-----------+-------------+ | 1 | 削除 | test01 | NULL | const | プライマリ | プライマリ | 4 | const | 1 | 100.00 | NULL | | 1 | SIMPLE | test02 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | インデックスを使用 | +----+-------------+---------+-----------+---------+---------------+---------+--------+----------+-----------+-------------+ セットに2行(0.01秒) root@localhost [dbtest01]> desc test01 から test01.* を削除し、test01.a=test02.b および test02.id=350000 で test02 を結合します。 +----+-------------+---------+-----------+--------+---------------+---------+-------+-------+------+------+------+------+ | id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 | +----+-------------+---------+-----------+--------+---------------+---------+-------+-------+------+------+------+------+ | 1 | SIMPLE | test02 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL | | 1 | 削除 | test01 | NULL | ref | a | a | 5 | const | 1 | 100.00 | NULL | +----+-------------+---------+-----------+--------+---------------+---------+-------+-------+------+------+------+------+ セット内の 2 行 (0.00 秒) 参考資料: https://www.cnblogs.com/zhengyun_ustc/p/slowquery1.html これで、MySQL のサブクエリの最適化における選択の実装に関するこの記事は終了です。MySQL のサブクエリの最適化における選択に関するより関連性の高いコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。 以下もご興味があるかもしれません:
|
<<: Dockerデータのバックアップとリカバリプロセスの詳細な説明
目次1. ソート機能2. データベースを準備する3. データベースに関連するエンティティクラスの構築...
場合によっては、MySQL が公式に提供しているテスト ライブラリに基づいてテスト データを作成し、...
目次序文1. 需要と効果必要効果2. コードの実装index.vue(html)日付方法テスト結果3...
質問ガイド1. Hadoop 3.x はどのようにして障害を許容するのでしょうか? 2. Hadoo...
1. Nginx サービス基盤Nginx (エンジン x) は、パフォーマンスの最適化のために特別...
SSDストレージを有効にしたMySQLインスタンスの詳細な説明特に OS と MySQL が同じディ...
目次MySQL 制約操作1. 非ヌル制約2. ユニーク制約3. 主キー制約4. 外部キー制約5. カ...
システム環境: centos7.4 1. データベースがインストールされているかどうかを確認します。...
vsCode のバージョンは最近更新され、現在のバージョン番号は 1.43 です。実際、vsCode...
HTML メールを送信するためのヒント: スタイルを使用してインライン CSS を記述する、使用する...
インストール プロセスは、コンパイル手順を除いて、基本的にソース バージョンと同じです。この記事では...
文章さて、次はレンダリングを見せましょう。画像を見て初めて理解することに興味が湧くでしょう。そうでな...
序文これは fastdfs を使用してイントラネット外部に展開された分散ファイルシステムであるためで...
ナレッジポイントをプレビューします。アニメーションフレーム背景グラデーションvar() と calc...
目次Portainerは複数のDockerコンテナ環境を管理します2. Dockerを管理する2.1...