MySQLのINサブクエリによってインデックスが使用できなくなる問題を解決する

MySQLのINサブクエリによってインデックスが使用できなくなる問題を解決する

今日は、MySQL IN サブクエリの最適化に関するケーススタディを見ました。

最初は少し懐疑的でした (SQL Server ではこれは不可能ですが、後で簡単なテストを実行します)。

その後、彼の言うとおりにテーブルを作ってテストして検証したところ、MySQLのINサブクエリがうまく実行されておらず、実際にインデックスが使用できない状況になっていることがわかりました(INサブクエリが使用できないため、シナリオはMySQLで、終了バージョンは5.7.18でした)

MySQL テスト環境

テストテーブルは以下のとおりです

テーブル test_table2 を作成する
(
  id int auto_increment 主キー、
  支払いID int、
  pay_time 日時、
  その他の列 varchar(100)
)

テスト データを挿入するためのストアド プロシージャを作成します。テスト データの特徴は、pay_id が繰り返し可能であることです。ここでは、ストアド プロシージャで、300 万のデータ項目のループ挿入中に、100 のデータ項目ごとに繰り返される pay_id が挿入されます。時間フィールドは、特定の範囲内でランダムです。

CREATE DEFINER=`root`@`%` PROCEDURE `test_insert`(IN `loopcount` INT)
  言語 SQL
  決定論的ではない
  SQLを含む
  SQL セキュリティ定義者
  コメント ''
始める
  cnt int を宣言します。
  cnt = 0 に設定します。
  while cnt< loopcount 実行
    test_table2 (pay_id、pay_time、other_col) に値 (cnt、date_add(now()、interval floor(300*rand()) day)、uuid() を挿入します。
    (cnt mod 100 = 0)の場合
      test_table2 (pay_id、pay_time、other_col) に値 (cnt、date_add(now()、interval floor(300*rand()) day)、uuid() を挿入します。
    終了の場合;
    cnt = cnt + 1 に設定します。  
  終了しながら;
終わり

test_insert(3000000) を実行して、303000行のデータを挿入します。

サブクエリを書く2つの方法

クエリは大まかに言うと、特定の期間内にビジネス ID が 1 より大きいデータを照会することを意味するため、記述方法は 2 つあります。

最初の書き方は次のとおりです。IN サブクエリには、一定期間内にビジネス統計が 1 より大きいビジネス ID が含まれます。外側のレイヤーは、IN サブクエリの結果に従ってクエリを実行します。ビジネス ID の pay_id 列にはインデックスがあり、ロジックは比較的単純です。この書き方は、データ量が多く、インデックスが必要ない場合には確かに非効率的です。

test_table2から*を選択、強制インデックス(idx_pay_id)
pay_idが(
  test_table2からpay_idを選択 
  ここで、pay_time>="2016-06-01 00:00:00" 
    かつ、支払い時間<="2017-07-03 12:59:59" 
  pay_idでグループ化 
  count(pay_id) > 1 である
);

実行結果: 2.23秒

2 番目の書き方は、サブクエリと結合することです。この書き方は、上記の IN サブクエリの書き方と同じです。次のテストでは、確かに効率が大幅に向上することがわかりました。

test_table2 tpp1からtpp1.*を選択します。 
(
   pay_idを選択 
   test_table2から 
   ここで、pay_time>="2016-07-01 00:00:00" 
   かつ、支払い時間<="2017-07-03 12:59:59" 
   pay_idでグループ化 
   count(pay_id) > 1 である
) tpp2 
ここで、tpp1.pay_id=tpp2.pay_id

実行結果: 0.48秒

サブクエリの実行プランでは、外部クエリがフルテーブルスキャンであり、pay_id のインデックスが使用されていないことがわかります。

結合セルフチェックの実行プランでは、外側のレイヤー (tpp1 エイリアスのクエリ) は pay_id のインデックスを使用します。

その後、最初のクエリメソッドに強制インデックスを使用したいと思いました。エラーは発生しませんでしたが、役に立たないことがわかりました。

サブクエリが直接値の場合、インデックスを通常どおり使用できます。

MySQL の IN サブクエリのサポートは実際にはあまり良くないことがわかります。

さらに、一時テーブルを使用するケースを追加します。これは、多くの結合クエリよりも効率的ですが、IN サブクエリを直接使用するよりも効率的です。この場合、インデックスも使用できますが、この単純なケースでは、一時テーブルを使用する必要はありません。

以下は、SQL Server 2014 での同様のケースのテストです。テスト テーブルの構造と数はまったく同じです。この場合、2 つの書き込み方法は SQL Server でまったく同じであると考えられます (実行プラン + 効率)。この点では、SQL Server は MySQL よりもはるかに優れています。

以下は、sqlserver のテスト環境スクリプトです。

テーブル test_table2 を作成する
(
  id int アイデンティティ(1,1) 主キー、
  支払いID int、
  pay_time 日時、
  その他の列 varchar(100)
)
翻訳を始める
@i int = 0 を宣言する
@i<300000の場合
始める
  test_table2に値(@i、getdate()-rand()* 300、newid())を挿入します。
  もし(@i%1000=0)
  始める
    test_table2に値(@i、getdate()-rand()* 300、newid())を挿入します。
  終わり
  @i = @i + 1 と設定する
終わり
専念
行く
test_table2(pay_id) にインデックス idx_pay_id を作成します。
test_table2(pay_time) にインデックス idx_time を作成します。
行く
test_table2から*を選択 
pay_idが(
          test_table2からpay_idを選択 
          支払時間>='2017-01-21 00:00:00' 
          かつ、支払い時間<='2017-07-03 12:59:59' 
          pay_idでグループ化 
          count(pay_id) > 1 である
        );
test_table2 tpp1からtpp1.*を選択します。 
(
   pay_idを選択 
   test_table2から 
   ここで、pay_time>='2017-01-21 00:00:00'
   かつ、支払い時間<='2017-07-30 12:59:59' 
   pay_idでグループ化 
   カウント(pay_id) > 1
) tpp2 
ここで、tpp1.pay_id=tpp2.pay_id

要約: MySQLデータでは、バージョン5.7.18以降、INサブクエリは依然として注意して使用する必要があります。

以下もご興味があるかもしれません:
  • MySQL のサブクエリの例
  • MySQL のサブクエリ操作の詳細な説明
  • MySQL サブクエリ (ネストされたクエリ)、結合テーブル、複合クエリの詳細な説明
  • MySQL IN ステートメントにおける低速クエリの効率を最適化する手法の例
  • MySQL の最適化: サブクエリの代わりに結合を使用する
  • LIMIT を使用した MySQL サブクエリ IN アプリケーションの例
  • MYSQL サブクエリとネストされたクエリの最適化例の分析
  • MySQLは複数テーブル関連統計(サブクエリ統計)の例を実装します
  • MySQL ノート: サブクエリの使用法の紹介
  • MySQLサブクエリでorder byが効かない問題の解決方法

<<:  WeChatアプレット+mqtt、esp8266温度と湿度の読み取り実装方法

>>:  nginxリバースプロキシのyum設定の詳細な手順

推薦する

CSSカスケーディングメカニズムについての簡単な説明

CSS にカスケード メカニズムがあるのはなぜですか? CSS では、同じ要素の特定のプロパティに同...

React 並行関数エクスペリエンス (フロントエンド並行モード)

React は、開発者が Web およびモバイルベースのアプリケーションを作成するために使用するオ...

トランザクションとロックを表示するための MySQL の一般的なステートメント

データベース内のトランザクションとロックを表示するための一般的なステートメントトランザクションの待機...

基本的なウェブページパフォーマンス最適化ルールの簡単な概要

ブラウザのウェブページを最適化するためのいくつかのルールページの最適化静的リソース圧縮ビルド ツール...

Windows での Nginx のインストールと環境設定 (nginx をサービスとして実行)

最初で最も重要なステップは、Windows 環境に Ngnix サービスをインストールする方法です。...

JavaScript のマクロタスクとマイクロタスクの詳細

目次1. マイクロタスクとは何ですか? 2. マクロタスクとは何ですか? 3. 事例3.1 結論4....

人気の宇宙飛行士ウォッチフェイスをJavaScriptで実装するための完全なコード

1. エフェクト表示JavaScript で書かれた宇宙飛行士のウォッチフェイス。 http://x...

MySQL のロックとトランザクションの簡単な分析

MySQL 自体はファイルシステムに基づいて開発されましたが、ロックの存在が異なります。データベース...

centos7.2 オフラインインストール mysql5.7.18.tar.gz

ネットワークが分離されているため、MySQL は yum を使用してインストールできません。ここでは...

MySQL での実行計画の explain コマンド例の詳細な説明

序文explain コマンドは、クエリ オプティマイザーがクエリの実行を決定した方法を確認する主な方...

JS WebSocket 切断理由とハートビートの仕組みの詳しい説明

1. 切断理由WebSocket が切断される理由は多数あります。WebSocket が切断されたと...

docker ログが取得できない問題の解決方法

毎日サービスをチェックしているときに、portainer からコンテナ ログを確認しようとしたところ...

VMware Workstation Pro 16 ライセンス キーと使用方法のチュートリアル

VMware Workstation は、開発、テスト、デモンストレーション、展開のために仮想マシン...

数十行のjsを使用してクールなキャンバスインタラクティブ効果を実現する方法を教えます

目次1. 円を描く2. マウスで動かした円3. マウスでドラッグした粒子4. カラーグラデーション粒...

SQL文でのgroup byの使用について簡単に説明します

1. 概要Group by は、by の後の規則に従ってデータをグループ化することを意味します。いわ...