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における非親子コンポーネント通信の詳細な説明

推薦する

Linux システムでの nginx サーバーのインストールと負荷分散構成の詳細な説明

nginx (エンジン x) は、高性能な HTTP およびリバース プロキシ サーバー、メール プ...

ファイルのダウンロードを実現する javascript Blob オブジェクト

目次例示する1. ブロブオブジェクト2. フロントエンド3. バックエンド要約する例示する最近、ファ...

データベースのデフォルトパスを変更した後にmysqlが起動できない問題の解決策

序文mysql がデフォルトのデータベース パスを変更したため、サービスを開始できませんでした。ログ...

一般的なイベントを処理するための JavaScript の使用に関する詳細な説明

目次1. フォームイベント2. マウスイベント3. キーボードイベント4. 共通イベントメソッド(ウ...

jsフェッチ非同期リクエストの使用の詳細な例

目次非同期を理解するフェッチ(url)レスポンス.json() asyncとawaitを組み合わせる...

スクラッチ宝くじの例を実現する JavaScript キャンバス

この記事では、スクラッチ効果を実現するためのJavaScriptキャンバスの具体的なコードを参考まで...

MySQL の基本: グループ化関数、集計関数、グループ化クエリの詳細な説明

目次1. 使い方が簡単2. DISTINCTを使用して重複を削除する3. COUNT()の詳細な紹介...

Linux サーバー上で nvidia-docker 環境を設定するプロセスの詳細な説明

Docker はコンテナに相当し、必要な動作環境に応じて対応する動作環境を構築できます。このとき、各...

MySQL の group by と order by を一緒に使用する方法

テーブル:reward(報酬テーブル)があるとします。テーブル構造は次のようになります。 テーブルt...

MySQL スケジュールタスク例チュートリアル

序文MySQL 5.1.6 以降、非常にユニークな機能であるイベント スケジューラが追加されました。...

HTML テーブルタグチュートリアル (21): 行の境界線の色属性 BORDERCOLOR

テーブルを美しくするために、行ごとに異なる境界線の色を設定できます。基本的な構文<TR 境界線...

MySQL 8.0.15 のダウンロードとインストールの詳細なチュートリアルは初心者にとって必須です。

この記事では、MySQL 8.0.15をダウンロードしてインストールするための具体的な手順を参考まで...

MySQL データベースの基礎 SQL ウィンドウ関数の例の分析チュートリアル

目次導入導入集計関数 + over()ソート関数 + over() ntile() 関数 + ove...

リソースアップロード機能を実現するための SpringBoot+nginx の詳細な例

最近、画像、ビデオ、CSS/JS などの静的リソースを配置するために nginx を使用する方法を学...

ウェブページ内でウェブテーブルやdivレイヤーが引き伸ばされる問題の解決策

<br />Web ページをデザインするときには、いつも不快なことに遭遇します。最も一般...