MySQL IN ステートメントにおける低速クエリの効率を最適化する手法の例

MySQL IN ステートメントにおける低速クエリの効率を最適化する手法の例

表の構造は以下のとおりです。記事数は690件のみです。

記事テーブル article(id,title,content)
タグテーブル tag(tid,tag_name)
タグ記事中間テーブル article_tag (id, tag_id, article_id)

tidが135のタグがあります。タグtidが135の記事リストを検索してください。

690 件の記事、次のクエリを使用、非常に遅い:

id が次の条件に該当する記事から id、title を選択します (
tag_id=135 の article_tag から article_id を選択
)

これは非常に高速です:

tag_id=135 の article_tag から article_id を選択

クエリの結果は、ID 428、429、430、431、432 の 5 つの記事です。

次の SQL を使用して記事を検索するのもおすすめです。

id が次の条件に該当する記事から id、title を選択します (
428,429,430,431,432
)

解決:

id が次の条件に該当する記事から id、title を選択します (
(tag_id=135 の article_tag から article_id を選択) から article_id を tbt として選択します。
)

その他の解決策: (例)

mysql> select * from abc_number_prop where number_id in (select number_id from abc_number_phone where phone = '82306839');

スペースを節約するため、出力内容は省略します。以下も同様です。

セット内67行(12.00秒)

返されたデータは 67 行だけでしたが、12 秒かかりました。システムには同時にこのようなクエリが多数存在する可能性があり、システムは間違いなくそれを処理できないでしょう。 desc を使用して表示します (注: explain も OK です)

mysql> desc select * from abc_number_prop where number_id in (select number_id from abc_number_phone where phone = '82306839');
+----+--------------------+------------------+--------+-----------------+---------+-----------+----------+--------------------------+
| id | select_type | テーブル | タイプ | possible_keys | key | key_len | ref | 行 | 追加 |
+----+--------------------+------------------+--------+-----------------+---------+-----------+----------+--------------------------+
| 1 | PRIMARY | abc_number_prop | ALL | NULL | NULL | NULL | NULL | 2679838 | where の使用 |
| 2 | 従属サブクエリ | abc_number_phone | eq_ref | phone,number_id | phone | 70 | const,func | 1 | where の使用; index の使用 |
+----+--------------------+------------------+--------+-----------------+---------+-----------+----------+--------------------------+
セット内の 2 行 (0.00 秒)

このクエリを実行すると、200万行以上がスキャンされることがわかります。これはインデックスが作成されていないためでしょうか?見てみましょう

mysql>abc_number_phone からインデックスを表示します。
+------------------+------------+-------------+--------------+-----------------+-------------+----------+--------+-------+-----------+--------------+
| テーブル | 非一意 | キー名 | インデックス内のシーケンス | 列名 | 照合 | カーディナリティ | サブパート | パック | Null | インデックス タイプ | コメント | インデックス コメント |
+------------------+------------+-------------+--------------+-----------------+-------------+----------+--------+-------+-----------+--------------+
| abc_number_phone | 0 | PRIMARY | 1 | number_phone_id | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 0 | 電話 | 1 | 電話 | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 0 | 電話 | 2 | number_id | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 1 | number_id | 1 | number_id | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 1 | created_by | 1 | created_by | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 1 | modified_by | 1 | modified_by | A | 36879 | NULL | NULL | YES | BTREE | | |
+------------------+------------+-------------+--------------+-----------------+-------------+----------+--------+-------+-----------+--------------+
セット内の6行(0.06秒)
mysql>abc_number_prop からインデックスを表示します。
+-----------------+-------------+--------------+--------------+----------------+-------------+-----------+---------+----------+----------+----------+---------------+
| テーブル | 非一意 | キー名 | インデックス内のシーケンス | 列名 | 照合 | カーディナリティ | サブパート | パック | Null | インデックス タイプ | コメント | インデックス コメント |
+-----------------+-------------+--------------+--------------+----------------+-------------+-----------+---------+----------+----------+----------+---------------+
| abc_number_prop | 0 | PRIMARY | 1 | number_prop_id | A | 311268 | NULL | NULL | | BTREE | | |
| abc_number_prop | 1 | number_id | 1 | number_id | A | 311268 | NULL | NULL | | BTREE | | |
| abc_number_prop | 1 | created_by | 1 | created_by | A | 311268 | NULL | NULL | | BTREE | | |
| abc_number_prop | 1 | modified_by | 1 | modified_by | A | 311268 | NULL | NULL | YES | BTREE | | |
+-----------------+-------------+--------------+--------------+----------------+-------------+-----------+---------+----------+----------+----------+---------------+
4 行セット (0.15 秒)

上記の出力からわかるように、これら 2 つのテーブルには number_id フィールドにインデックスが作成されています。
サブクエリ自体に問題がないか確認してください。

mysql> desc select number_id from abc_number_phone where phone = '82306839';
+----+-------------+------------------+-------+---------------+--------+-------+------+--------------------------+
| id | select_type | テーブル | タイプ | possible_keys | key | key_len | ref | 行 | 追加 |
+----+-------------+------------------+-------+---------------+--------+-------+------+--------------------------+
| 1 | SIMPLE | abc_number_phone | ref | phone | phone | 66 | const | 6 | where の使用; index の使用 |
+----+-------------+------------------+-------+---------------+--------+-------+------+--------------------------+
セット内の 1 行 (0.00 秒)

問題ありません。数行のデータをスキャンするだけで、インデックスが機能します。

それをチェックしてください:

mysql> abc_number_phone から number_id を選択します。ここで、phone = '82306839' です。
+-----------+
| 番号ID |
+-----------+
| 8585 |
| 10720 |
|148644|
|151307|
|170691|
|221897|
+-----------+
セット内の 6 行 (0.00 秒)

サブクエリから取得したデータを上記のクエリに直接入力します

mysql> abc_number_prop から * を選択します。ここで、 number_id は (8585, 10720, 148644, 151307, 170691, 221897) です。
セット内の行数は 67 行 (0.03 秒)

速度も速いです。サブクエリを扱う場合、MySQL では不十分なようです。 MySQL 5.1.42 と MySQL 5.5.19 の両方でこれを試しましたが、どちらでもこの問題が発生しました。

インターネットで検索したところ、多くの人がこの問題に遭遇していることがわかりました。

参考 1: MySQL の最適化: サブクエリの代わりに結合を使用する

参考2: MYSQLサブクエリとネストされたクエリの最適化例の分析

これらのオンライン資料の提案に従って、代わりに join を使用してみてください。
変更前:

abc_number_prop から * を選択します。ここで number_id は (abc_number_phone から number_id を選択します。ここで phone = '82306839')。

変更後:

abc_number_prop から a.* を選択します。a.number_id = b.number_id で、phone = '82306839' である abc_number_phone b を内部結合します。
mysql> abc_number_prop から a.* を選択します。 abc_number_phone b は a.number_id = b.number_id で、phone = '82306839' になります。
セット内の行数は 67 です (0.00 秒)

効果は良好で、クエリ時間はほぼ0です。 MySQL がこのクエリをどのように実行するかを見てみましょう。

mysql>desc select a.* from abc_number_prop a inner join abc_number_phone b on a.number_id = b.number_id where phone = '82306839';
+----+-------------+-------+-------+-----------------+-----------+---------+----------------+------+---------------------------+
| id | select_type | テーブル | タイプ | possible_keys | key | key_len | ref | 行 | 追加 |
+----+-------------+-------+-------+-----------------+-----------+---------+----------------+------+---------------------------+
| 1 | SIMPLE | b | ref | phone,number_id | phone | 66 | const | 6 | where の使用; index の使用 |
| 1 | シンプル | a | ref | number_id | number_id | 4 | eap.b.number_id | 3 | |
+----+-------------+-------+-------+-----------------+-----------+---------+----------------+------+---------------------------+
セット内の 2 行 (0.00 秒)

概要: サブクエリが遅い場合は、JOIN を使用してクエリを書き換えて最適化することができます。

JOIN ステートメントを使用するクエリは、必ずしもサブクエリを使用するクエリよりも高速であるとは限らないというオンライン記事もあります。

MySQL マニュアルにもこれについて記載されており、具体的な元のテキストは MySQL ドキュメントのこの章にあります。
I.3. サブクエリの制限
13.2.8. サブクエリ構文

抜粋:

1) IN を使用したサブクエリについて:

IN のサブクエリの最適化は、= 演算子や IN(value_list) 構造の場合ほど効果的ではありません。

IN サブクエリのパフォーマンスが低下する典型的なケースは、サブクエリが少数の行を返すのに対し、外部クエリがサブクエリの結果と比較する多数の行を返す場合です。

問題は、IN サブクエリを使用するステートメントの場合、オプティマイザがそれを相関サブクエリとして書き換えることです。相関のないサブクエリを使用する次のステートメントを考えてみましょう。

SELECT ... FROM t1 WHERE t1.a IN (SELECT b FROM t2);

オプティマイザーは、ステートメントを相関サブクエリに書き換えます。

SELECT ... FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.b = t1.a);

内部クエリと外部クエリがそれぞれ M 行と N 行を返す場合、実行時間は、相関のないサブクエリの場合の O(M+N) ではなく、O(M×N) のオーダーになります。

つまり、IN サブクエリは、サブクエリが返すのと同じ値をリストする IN(value_list) 構造を使用して記述されたクエリよりもはるかに遅くなる可能性があります。

2) サブクエリを結合に変換する方法について:

オプティマイザーはサブクエリよりも結合に対して成熟しているため、多くの場合、サブクエリを使用するステートメントは結合として書き換えるとより効率的に実行できます。

IN サブクエリを SELECT DISTINCT 結合として書き換えることができる場合は例外が発生します。例:

SELECT col FROM t1 WHERE id_col IN (SELECT id_col2 FROM t2 WHERE condition);

この文は次のように書き直すことができます。

SELECT DISTINCT col FROM t1, t2 WHERE t1.id_col = t2.id_col AND condition;

しかし、この場合、結合には追加のDISTINCT操作が必要となり、サブクエリよりも効率的ではありません。

要約する

以上が、MySQL のステートメント サブクエリの効率低下に対する最適化手法に関するこの記事の内容のすべてです。ご興味がある方は、「MySQL のサブクエリ ユニオンの効率に関する簡単な説明」、「エンタープライズ プロダクション MySQL の最適化の紹介」などを参照してください。ご質問がある場合は、メッセージを残してください。どなたでもコミュニケーションして参照できます。

この記事がお役に立てば幸いです。

以下もご興味があるかもしれません:
  • MySQL のサブクエリの例
  • MySQL サブクエリ (ネストされたクエリ)、結合テーブル、複合クエリの詳細な説明
  • MySQL の最適化: サブクエリの代わりに結合を使用する
  • MySQL のテーブル サブクエリと相関サブクエリの基本学習チュートリアル
  • MySQL ノート: サブクエリの使用法の紹介
  • MySQL サブクエリの一般的な形式をいくつか紹介します
  • MySQL ネストクエリでサブクエリを実装する方法

<<:  Reactイベントスロットリング効果が失敗する理由と解決策

>>:  高並列処理 nginx サーバー向け Linux カーネル最適化構成の説明

推薦する

MySQLは既存のコンテンツを保持し、後でコンテンツを追加します

このコマンドは、データ テーブル ff_vod を変更し、vod_url フィールドの内容の後に 9...

Linuxコマンド履歴の調整方法の詳細な説明

Linux システムの bash history コマンドは、以前に実行したコマンドを記憶し、再入力...

スライドボタン効果を実現するネイティブJS

Jsで作ったスライドボタンの具体的なコードは参考までに。具体的な内容は以下のとおりですまずエフェク...

VMware に CentOS7 をインストールし (静的 IP アドレスを設定)、Docker コンテナ経由で mySql データベースをインストールする (非常に詳細なチュートリアル)

2 年生から、これらのインストールと設定の仕方を尋ねられました。簡単なチュートリアルを作成し、ここ...

Linux で完全な Samba サーバーを構築する方法 (CentOS バージョン)

序文smb は、クライアントとサーバー間の Web 接続および情報通信に使用できるプロトコルの名前で...

aタグ疑似クラスの機能と記述順序は何ですか?

a タグ疑似クラスの役割: 「:link」: 訪問されていないタグの状態。 「:visited」: ...

9999px に別れを告げる新しい CSS 画像置換テクニック (背景表示と画面外へのテキストの移動)

-9999 ピクセルの画像置換技術は、ここ 10 年近く人気があります。テキスト要素を画像に置き換え...

Linux ログ表示方法 6 つのまとめ

バックエンド プログラマーは、さまざまな場所で Linux を扱います。Linux ログの読み方がわ...

CSS マスクを使用して PNG 画像のサイズを大幅に最適化します (推奨)

この記事は共有および集約することを歓迎します。全文を転載する必要はありません。著作権を尊重してくださ...

Linux での UDP について学ぶ

目次1. UDPとLinuxの基礎の紹介2. 各機能の使い方1. ソケット機能の使用2. バインド機...

CSS 複数 3 列適応レイアウト実装の詳細な説明

序文従来のWEBレイアウトに沿うため、すべてヘッダーとフッターモードの左・中央・右レイアウトで書かれ...

MySQLインスタンスが起動できない問題の分析と解決

目次序文シナリオ分析要約する序文数日前、友人がWeChatで私に連絡してきて、マシンがダウンタイムか...

MySQL 1対多関連クエリのページングエラー問題の解決方法

XML価格照会のクエリデータにはリストが含まれているため、コレクションが必要です <結果マップ...

MySQL 5.7 および 8.0 データベースのルート パスワードを忘れた場合の解決策

注: MySQL5.7 で root パスワードをクラックするには、パスワード認証をスキップしてデー...

ウェブデザイナーは3つの側面からウェブページを最適化する必要がある

<br />帯域幅の増加に伴い、Web ページ上のオブジェクトも増えているため、Web ...