SQL 最適化チュートリアル: IN クエリと RANGE クエリ

SQL 最適化チュートリアル: IN クエリと RANGE クエリ

序文

「High Performance MySQL」では、インデックスでは範囲フィールドの後の部分が無効であるため (ICP を考慮する必要がある)、in メソッドを使用すると、特定の範囲クエリを効果的に置き換えてクエリの効率を向上できると説明されています。 MySQL オプティマイザーは、クエリをクエリ用の n*m の組み合わせに変換し、最後に返された値をマージします。これは、union に似ていますが、より効率的です。

IN() の組み合わせ条件が多すぎると、MySQL では多くの問題が発生します。クエリの最適化には長い時間がかかり、大量のメモリを消費する可能性があります。新しいバージョンの MySQL では、組み合わせの数が一定数を超えるとプラン評価が実行されないため、MySQL がインデックスを有効に活用できない可能性があります。

ここでの特定の数は、MySQL 5.6.5 以降のバージョンではパラメータ eq_range_index_dive_limit によって制御されます。デフォルト設定は10で、バージョン5.7以降は200に変更されました。もちろん手動で設定することも可能です。 5.6 マニュアルには次のように記載されています。

eq_range_index_dive_limit システム変数を使用すると、オプティマイザが 1 つの行推定戦略から別の行推定戦略に切り替える値の数を設定できます。統計の使用を無効にして常にインデックス ダイブを使用するには、eq_range_index_dive_limit を 0 に設定します。最大 N 個の等価範囲の比較にインデックス ダイブの使用を許可するには、eq_range_index_dive_limit を N + 1 に設定します。eq_range_index_dive_limit は、MySQL 5.6.5 以降で使用できます。5.6.5 より前では、オプティマイザはインデックス ダイブを使用します。これは、eq_range_index_dive_limit=0 に相当します。

言い換えると、

eq_range_index_dive_limit = 0 インデックスダイブのみ使用可能

0 < eq_range_index_dive_limit <= N インデックス統計を使用する

eq_range_index_dive_limit > N: インデックスダイブのみ使用可能

MySQL 5.7 では、IN() リストの数が 10 を超えることが多いため、範囲等価演算 (IN()) の実行プランが可能な限り正確になるように、デフォルト値が 10 から 200 に変更されました。

MySQL の公式マニュアルには次のような一文があります。

オプティマイザーは、インデックスまたはインデックス統計を詳しく調べることで、各範囲の行数を推定できます。

本旨:

オプティマイザは、各範囲セグメントに含まれるタプルの数を推定します。たとえば、「a IN (10, 20, 30)」は等価比較と見なされ、3 つの範囲セグメントは実際には 3 つの単一値 10、20、30 に簡略化されます。範囲セグメントを使用する理由は、MySQL の「範囲」スキャンのほとんどが範囲スキャンであり、ここでの単一値は範囲セグメントの特殊なケースと見なすことができるからです。

推定方法は 2 つあります。

  1. インデックスをダイブするということは、インデックスを使用してタプルの数を推定することを意味し、インデックス ダイブと呼ばれます。
  2. インデックス統計: インデックスの統計値を使用して推定を行います。

これら2つの方法を比較する

  1. インデックスダイブ:遅いが、正確な値を取得できる(MySQLの実装はインデックスに対応するインデックス項目の数をカウントするため、正確です)
  2. インデックス統計:高速ですが、取得された値は正確ではない可能性があります

簡単に言うと、オプション eq_range_index_dive_limit の値は、IN リスト内の条件の数の上限を設定します。値を超えると、実行プランはインデックス ダイブからインデックス統計に変更されます。

なぜこれら 2 つの方法を区別する必要があるのでしょうか?

  1. クエリ オプティマイザーは、コスト見積もりモデルを使用して各プランのコストを計算し、コストが最も低いプランを選択します。
  2. 単一のテーブルをスキャンする場合、コストを計算する必要があるため、単一のテーブルのインデックススキャンでもコストを計算する必要がある。
  3. 単一テーブルの計算式は通常、コスト = タプル数 * 平均IO値です。
  4. したがって、どのスキャン方法を使用する場合でも、タプルの数を計算する必要があります。
  5. 「a IN (10, 20, 30)」のような式に遭遇し、列 a にインデックスがあることがわかった場合、このインデックスがスキャンできるタプルの数を確認し、インデックス スキャン コストを計算する必要があります。そのため、この記事で説明した「インデックス ダイブ」と「インデックス統計」という 2 つの方法を使用します。

ディスカッショントピック

  1. 範囲クエリとインデックスの使用
  2. eq_range_index_dive_limit の説明

範囲クエリとインデックスの使用

SQL は次のとおりです。

SELECT * FROM pre_forum_post WHERE tid=7932552 AND invisible IN('0','-2') ORDER BY dateline DESC LIMIT 10;

インデックスは次のとおりです。

PRIMARY(tid,位置),
pid(pid)、
fid(tid)、
表示順序(tid,非表示,日付行)
最初(tid,最初)
new_auth(authorid、invisible、tid)
idx_dt(日付行)
mul_test(tid,invisible,dateline,pid)

実行計画を見てみましょう:

root@localhost 16:08:27 [ultrax]> SELECT * FROM pre_forum_post WHERE tid=7932552 AND `invisible` IN('0','-2') を説明します 
 -> ORDER BY dateline DESC LIMIT 10;
+----+-------------+----------------+-------+--------------------------------------------------------+--------------+---------+------+------+---------------------------------------+
| id | select_type | テーブル | タイプ | possible_keys | key | key_len | ref | 行 | 追加 |
+----+-------------+----------------+-------+--------------------------------------------------------+--------------+---------+------+------+---------------------------------------+
| 1 | SIMPLE | pre_forum_post | range | PRIMARY,displayorder,first,mul_test,idx_1 | displayorder | 4 | NULL | 54 | インデックス条件の使用; filesort の使用 | 
+----+-------------+----------------+-------+--------------------------------------------------------+--------------+---------+------+------+---------------------------------------+
セット内の 1 行 (0.00 秒)

MySQL オプティマイザは、これは範囲クエリであると認識するため、(tid、invisible、dateline) インデックスの dateline フィールドは確実に使用されません。つまり、この SQL の最終的なソートでは、インデックス内で直接ソート アクションを完了するのではなく、一時的な結果セットが生成され、その後結果セット内でソートが完了します。そこで、インデックスを追加しようとしました。

root@localhost 16:09:06 [ultrax]> テーブル pre_forum_post を変更し、インデックス idx_1 (tid,dateline) を追加します。 
クエリは正常、20374596 行が影響を受け、警告は 0 件 (600.23 秒)
レコード: 0 重複: 0 警告: 0
root@localhost 16:20:22 [ultrax]> explain SELECT * FROM pre_forum_post force index (idx_1) WHERE tid=7932552 AND `invisible` IN('0','-2') ORDER BY dateline DESC LIMIT 10;
+----+-------------+----------------+------+---------------+--------+---------+---+-------------+-------------+
| id | select_type | テーブル | タイプ | possible_keys | key | key_len | ref | 行 | 追加 |
+----+-------------+----------------+------+---------------+--------+---------+---+-------------+-------------+
| 1 | SIMPLE | pre_forum_post | ref | idx_1 | idx_1 | 3 | const | 120646 | where の使用 | 
+----+-------------+----------------+------+---------------+--------+---------+---+-------------+-------------+
セット内の 1 行 (0.00 秒)
root@localhost 16:22:06 [ultrax]> SELECT sql_no_cache * FROM pre_forum_post WHERE tid=7932552 AND `invisible` IN('0','-2') ORDER BY dateline DESC LIMIT 10;
...
10 行セット (0.40 秒)
root@localhost 16:23:55 [ultrax]> SELECT sql_no_cache * FROM pre_forum_post force index (idx_1) WHERE tid=7932552 AND `invisible` IN('0','-2') ORDER BY dateline DESC LIMIT 10;
...
セット内の行数は 10 です (0.00 秒)

実験により、その効果は優れていることが証明されました。実際、理解するのは難しくありません。上で述べたように、in() は MySQL オプティマイザでさまざまな組み合わせでデータを取得します。ソートやグループが追加された場合、一時的な結果セットに対してのみ操作できます。つまり、インデックスにソートやグループ フィールドが含まれていても、それはまだ役に立ちません。唯一の不満は、MySQL オプティマイザーの選択がまだ十分に信頼できないことです。

要約すると、MySQL クエリで in() を使用する場合は、in() リストの数と eq_range_index_dive_limit の値 (詳細は下記を参照) に注意するだけでなく、SQL に並べ替え/グループ化/重複排除などが含まれている場合は、インデックスの使用にも注意する必要があります。

eq_range_index_dive_limit の説明

上記の例を引き続き使用する場合、idx_1 を直接使用できないのはなぜですか?このインデックスのみを強制的に使用するにはヒントを使用する必要がありますか?ここではまず、eq_range_index_dive_limit の値を確認します。

root@localhost 22:38:05 [ultrax]> 'eq_range_index_dive_limit' のような変数を表示します。
+---------------------------+-------+
| 変数名 | 値 |
+---------------------------+-------+
| 等価範囲インデックスダイブ制限 | 2 | 
+---------------------------+-------+
セット内の 1 行 (0.00 秒)

前述の状況によると、0 < eq_range_index_dive_limit <= N はインデックス統計を使用するので、OPTIMIZER_TRACE を使用して確認してみましょう。

{
 "インデックス": "表示順序",
 「範囲」: [
 「7932552 <= tid <= 7932552 かつ -2 <= 非表示 <= -2」、
 「7932552 <= tid <= 7932552 かつ 0 <= 非表示 <= 0」
 ]、
 "index_dives_for_eq_ranges": false,
 "rowid_ordered": 偽、
 "using_mrr": 偽、
 "index_only": 偽、
 「行」: 54,
 「コスト」: 66.81,
 「選択済み」: true
}
// インデックス ダイブは false で、最終的に選択されたものは true です
...
{
 "インデックス": "idx_1",
 「範囲」: [
 「7932552 <= tid <= 7932552」
 ]、
 "index_dives_for_eq_ranges": true、
 "rowid_ordered": 偽、
 "using_mrr": 偽、
 "index_only": 偽、
 「行数」: 120646,
 「コスト」: 144776,
 「選択」:偽、
 「原因」:「コスト」
}

displayorder インデックスのコストは 66.81 であるのに対し、idx_1 のコストは 120646 であり、最終的に MySQL オプティマイザーは displayorder インデックスを選択することがわかります。したがって、eq_range_index_dive_limit を > N に設定する場合、より正確な実行プランを取得するためにインデックス ダイブ計算方法を使用する必要がありますか?

root@localhost 22:52:52 [ultrax]> eq_range_index_dive_limit を 3 に設定します。
クエリは正常、影響を受けた行は 0 行 (0.00 秒)
root@localhost 22:55:38 [ultrax]> explain SELECT * FROM pre_forum_post WHERE tid=7932552 AND `invisible` IN('0','-2') ORDER BY dateline DESC LIMIT 10;
+----+-------------+----------------+------+------------------------------------------+--------+---------+---+-------------+-------------+
| id | select_type | テーブル | タイプ | possible_keys | key | key_len | ref | 行 | 追加 |
+----+-------------+----------------+------+------------------------------------------+--------+---------+---+-------------+-------------+
| 1 | SIMPLE | pre_forum_post | ref | PRIMARY,displayorder,first,mul_test,idx_1 | idx_1 | 3 | const | 120646 | where の使用 | 
+----+-------------+----------------+------+------------------------------------------+--------+---------+---+-------------+-------------+
セット内の 1 行 (0.00 秒)

optimize_traceの結果は次のとおりです。

{
 "インデックス": "表示順序",
 「範囲」: [
 「7932552 <= tid <= 7932552 かつ -2 <= 非表示 <= -2」、
 「7932552 <= tid <= 7932552 かつ 0 <= 非表示 <= 0」
 ]、
 "index_dives_for_eq_ranges": true、
 "rowid_ordered": 偽、
 "using_mrr": 偽、
 "index_only": 偽、
 「行数」: 188193,
 「コスト」: 225834,
 「選択済み」: true
}
...
{
 "インデックス": "idx_1",
 「範囲」: [
 「7932552 <= tid <= 7932552」
 ]、
 "index_dives_for_eq_ranges": true、
 "rowid_ordered": 偽、
 "using_mrr": 偽、
 "index_only": 偽、
 「行数」: 120646,
 「コスト」: 144776,
 「選択済み」: true
}
...
 「プランの費用」: 144775,
 "計画行数": 120646,
 「選択済み」: true

代替インデックス選択では、両方のインデックスが選択され、最終的な論理最適化では、コストが最も低いインデックス idx_1 が選択されます。上記は、等範囲クエリの eq_range_index_dive_limit の値が MySQL オプティマイザの計算オーバーヘッドに影響し、それによってインデックス選択に影響を与えることを示しています。さらに、プロファイリングを使用して、オプティマイザの統計的な時間消費を確認することもできます。

インデックスダイブ

+----------------------+----------+
| ステータス | 期間 |
+----------------------+----------+
| 開始 | 0.000048 | 
| 権限を確認中 | 0.000004 | 
| オープニングテーブル | 0.000015 | 
| 初期化 | 0.000044 | 
| システムロック | 0.000009 | 
| 最適化 | 0.000014 | 
| 統計 | 0.032089 | 
| 準備中 | 0.000022 | 
| ソート結果 | 0.000003 | 
| 実行中 | 0.000003 | 
| データ送信中 | 0.000101 | 
| 終了 | 0.000004 | 
| クエリ終了 | 0.000002 | 
| テーブルのクローズ | 0.000009 | 
| アイテムを解放 | 0.000013 | 
| クリーンアップ | 0.000012 | 
+----------------------+----------+

インデックス統計

+----------------------+----------+
| ステータス | 期間 |
+----------------------+----------+
| 開始 | 0.000045 | 
| 権限を確認中 | 0.000003 | 
| オープニングテーブル | 0.000014 | 
| 初期化 | 0.000040 | 
| システムロック | 0.000008 | 
| 最適化 | 0.000014 | 
| 統計 | 0.000086 | 
| 準備中 | 0.000016 | 
| ソート結果 | 0.000002 | 
| 実行中 | 0.000002 | 
| データ送信中 | 0.000016 | 
| ソートインデックスを作成しています | 0.412123 | 
| 終了 | 0.000012 | 
| クエリ終了 | 0.000004 | 
| テーブルのクローズ | 0.000013 | 
| アイテムを解放 | 0.000023 | 
| クリーンアップ | 0.000015 | 
+----------------------+----------+

eq_range_index_dive_limit を増やしてインデックス ダイブを使用すると、オプティマイザ統計は ndex 統計方法よりも大幅に時間がかかりますが、最終的にはより合理的な実行プランが使用されることがわかります。統計時間は 0.032089 秒対 0.000086 秒ですが、SQL 実行時間は 0.41 秒対 0.03 秒程度です。

添付ファイル:

optimize_traceの使い方

optimizer_trace を 'enabled=on' に設定します。

information_schema.optimizer_trace\G から * を選択

注: optimizer_trace では、セッション モードでのみデバッグを有効にすることを推奨しています。

プロフィールの使い方

プロファイリングをONに設定します。
SQLを実行します。
プロフィールを表示します。
クエリ2のプロファイルを表示します。
クエリ 2 のプロファイル ブロック io、cpu を表示します。

メモリ、スワップ、コンテキストスイッチ、ソースなどの情報も表示できます。

参考文献

[1]MySQL SQL最適化シリーズ: INクエリとRANGEクエリ

https://www.jb51.net/article/201251.htm

[2]MySQL物理クエリ最適化技術---インデックスダイブ分析

http://blog.163.com/li_hx/blog/static/18399141320147521735442/

これで、インクエリと範囲クエリに関する SQL 最適化チュートリアルに関するこの記事は終了です。インクエリと範囲クエリに関するより関連性の高い SQL 最適化コンテンツについては、123WORDPRESS.COM で以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • MySQLでdistinctを最適化するためのテクニックを分析する
  • MySQL IN ステートメントにおける低速クエリの効率を最適化する手法の例
  • MySQL クエリの最適化: 結合クエリのソート制限の概要 (結合、順序、制限ステートメント)
  • MySQL の最適化: サブクエリの代わりに結合を使用する
  • SQL 文の最適化: JOIN、LEFT JOIN、RIGHT JOIN 文の最適化
  • カウント、テーブル結合順序、条件順序、in および exit の SQL 最適化
  • MySQL の not in と minus の最適化
  • MySQL の InnoDB におけるカウント最適化の問題の共有
  • MySQL での挿入ステートメントの使用と最適化に関するチュートリアル

<<:  Docker クリーンアップ環境操作

>>:  Vue3における非親子コンポーネント通信の詳細な説明

推薦する

Samba を使用して Linux サーバー上で共有ファイル サービスを構築する方法

最近、私たちの小さなチームは、サーバー上の共有フォルダーを共有して、全員がパブリックリソースドキュメ...

Linux のファイル権限とグループ変更コマンドの詳細な説明

Linux では、すべてがファイルであり (ディレクトリもファイルです)、各ファイルにはユーザーに対...

同じ IP のアクセス頻度を制限するように nginx を設定する方法

1. nginx.conf の http{} に次のコードを追加します。 limit_conn_zo...

Mariadb リモート ログイン構成と問題解決

序文:インストール プロセスについては詳しく説明しません。問題に直接触れましょう。MySQL のリモ...

Windows Server 2008 R2 マルチユーザー リモート デスクトップ接続ライセンス

仕事ではリモート サーバーが必要になることが多く、次の 2 つの問題に遭遇することがよくあります。 ...

IE 8/Chrome/Firefox と互換性のあるコメント返信ポップアップマスク効果実装アイデア

平日はニュースに注目して、テンセントをよく閲覧しています。しかし、コメントへの返信はほとんど見られま...

MySQL で高性能なインデックスを作成するための完全な手順

目次1. インデックスの基本1. インデックスの種類1.1 Bツリーインデックス1.2 ハッシュイン...

MySQL 8.0.16 winx64 のインストールと設定方法のグラフィックチュートリアル (win10 の場合)

この記事では、MySQL 8.0.16 winx64のインストールと設定の具体的な方法を記載します。...

JSはBaidu Newsナビゲーションバーの効果を実現

この記事では、Baidu News Navigation Barの効果を実現するための具体的なJSコ...

Vue.set() と this.$set() の使い方と違い

開発に Vue を使用する場合、次のような状況に遭遇することがあります。Vue インスタンスを生成し...

Vue で配列パラメータを渡すための get / delete メソッド

フロントエンドとバックエンドがやり取りする場合、get または delete を介してバックエンドに...

Angularコンポーネントライフサイクルの詳細説明(I)

目次概要1. フックの呼び出し順序2. onChangesフック3. 変更検出メカニズムとDoChe...

Dockerイメージ送信コマンドcommitの動作原理と使い方の詳細な説明

ローカルでコンテナを作成した後、このコンテナに基づいてローカル イメージを作成し、このイメージを D...

JavaScriptの再帰の詳細

目次1. 再帰とは何ですか? 2. 再帰を使って数学の問題を解く1. 1 * 2 * 3 * 4 …...

Docker を使用して ELK ログ システムを構築する例

以下のインストールではすべて、インストール ルート ディレクトリとして ~/ ディレクトリが使用され...