web-dev-qa-db-ja.com

MySQLがこの実行プランを選択するのはなぜですか?

2つのクエリがあります。

select some_other_column 
from `table` 
order by primary_index_column asc 
limit 4000000, 10;

そして

select some_other_column 
from `table` 
order by secondary_index_column asc 
limit 4000000, 10;

どちらも10行を返します。最初は2.74秒かかり、2番目は7.07秒かかります。 some_other_columnはインデックスの一部ではありません。 primary_index_columnは主キー列です。 secondary_index_columnには、Bツリーインデックスと200のカーディナリティ(MySQLによる)があります。

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

mysql> explain select some_other_column from `table` order by primary_index_column limit 4000000, 10;
+----+-------------+---------+-------+---------------+---------+---------+------+---------+-------+
| id | select_type | table   | type  | possible_keys | key     | key_len | ref  | rows    | Extra |
+----+-------------+---------+-------+---------------+---------+---------+------+---------+-------+
|  1 | SIMPLE      | table   | index | NULL          | PRIMARY | 4       | NULL | 4000010 |       |
+----+-------------+---------+-------+---------------+---------+---------+------+---------+-------+

mysql> explain select some_other_column from `table` order by secondary_index_column limit 4000000, 10;
+----+-------------+---------+------+---------------+------+---------+------+---------+----------------+
| id | select_type | table   | type | possible_keys | key  | key_len | ref  | rows    | Extra          |
+----+-------------+---------+------+---------------+------+---------+------+---------+----------------+
|  1 | SIMPLE      | table   | ALL  | NULL          | NULL | NULL    | NULL | 4642945 | Using filesort |
+----+-------------+---------+------+---------------+------+---------+------+---------+----------------+

MySQLが2番目のクエリに特定の実行プランを選択するのはなぜですか?最初のクエリにインデックスを使用でき、2番目のクエリにはインデックスを使用できない理由がわかりません。

7
Matt Fenwick

InnoDBのインデックス付きカラムには、常に gen_clust_index(aka Clustered Index) への追加キーが付加されています。これは、インデックスの順序で行4000000に到達する最初のクエリによってトラバースされます。これは要求されている唯一の列であるため、テーブルへのアクセスは不要です。

2番目のクエリは、インデックスの付いていない列をテーブルからインデックス付きの列と共に一時テーブルに収集する必要があります。次に、一時テーブルがソートされてから、インデックス付けされていない列がSELECT出力として表示されます。

別のコントラストに注意してください

  • テーブル数は4636881です。
  • 最初のクエリのEXPLAINプランは、4000010のindexed_columnキーを通過しました。最後の636871キーを読み取る必要はありません。
  • 2番目のクエリのEXPLAINプランは、indexed_columnで順序付けされた4636881行をトラバースしました。テーブルからインデックスなしの列を取得するすべての行について、インデックス付きの列(既にインデックスで並べ替えられています)が検索され、実行されます。 tmpテーブルはインデックス付けされた列によって順序付けられ、mysqldは最初の4000000行を閉じて10行を残します。たった10行のテーブルとインデックス間の相互作用すべてがボトルネックです。

一般的なこと

どちらの場合も、クエリは横断する行数を指定します。テーブルの行数は4636881であるため、フルスキャンが容易に期待できます。 MySQLクエリオプティマイザがフルスキャンを実行する場所を決定すると、対照が明らかになります。

  • 最初のクエリは、SELECTリストとWHERE句でのみインデックス付きの列を参照しています。 MySQLクエリオプティマイザーは、必要なものがすべてインデックスにあるため、テーブルにアクセスする必要なく、フルインデックススキャンを実行することを選択します。
  • 2番目のクエリは、WHERE句のインデックス付きの列を参照しています。ただし、対応するインデックスのない列を取得するには、テーブルに到達する必要があります。 MySQL Query Optmizerはクエリによって通知されましたそれはインデックスを使用してはなりませんそれがあった行数のため読むことが期待されています。 RDBMSの経験則として、クエリを実行するためにテーブルの5%以上を読み取る必要がある場合、MySQL Query Optimizerは「バスの下」にインデックスをスローし、フルテーブルスキャンを実行します

計算を行うと、MySQLクエリオプティマイザが計算するものは次のとおりです。

  • 4636881の5%は231844です
  • 2番目のクエリは、231844よりもはるかに高い4000000行を読み取るようにコマンドされています
  • MySQL Query Optimizerは、必要なデータを取得するには、テーブル(インデックス付けされていない列のため)とインデックス(インデックス付けされた列のため)の相互作用が多すぎることを認識しています。インデックス付きとインデックスなしの両方の列がテーブル内で一緒に存在するため、テーブル間を行き来するのではなく、テーブルのみを読み取ることを決定します。

私の正直な意見では、テーブルの行数、テーブルの現在のインデックス、およびクエリによって規定された行数により、MySQL Query Optimizerは正しい決定を行いました

[〜#〜]推奨[〜#〜]

このインデックスを作成する

ALTER TABLE `table` ADD INDEX mynewndx (indexed_column,some_other_column);

そして、2番目のクエリは今後再びテーブルにアクセスすることはありません。 MySQL Query Optimizerは、この新しいインデックスを検出すると、まったく異なる動作をします。

7
RolandoMySQLDBA

order byクエリの最適化に関するMySQLのドキュメント によれば、

場合によっては、MySQLがインデックスを使用してORDER BY [...]を解決できない場合があります。これらのケースには次のものが含まれます。

  • [...]
  • 使用されるテーブルインデックスのタイプは、行を順番に格納しません。たとえば、これはMEMORYテーブルのHASHインデックスに当てはまります。

InnoDBについての私の理解は、行は主キーに従って順番に格納されるということです。したがって、セカンダリインデックスの場合は順序が狂っています。

0
Matt Fenwick