mysqlがクエリによる注文に間違ったインデックスを使用するのはなぜですか?


9

以下は、約10,000,000行のデータを持つ私のテーブルです

CREATE TABLE `votes` (
  `subject_name` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
  `subject_id` int(11) NOT NULL,
  `voter_id` int(11) NOT NULL,
  `rate` int(11) NOT NULL,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`subject_name`,`subject_id`,`voter_id`),
  KEY `IDX_518B7ACFEBB4B8AD` (`voter_id`),
  KEY `subject_timestamp` (`subject_name`,`subject_id`,`updated_at`),
  KEY `voter_timestamp` (`voter_id`,`updated_at`),
  CONSTRAINT `FK_518B7ACFEBB4B8AD` FOREIGN KEY (`voter_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

ここにインデックスのカーディナリティがあります

ここに画像の説明を入力してください

したがって、このクエリを実行すると:

SELECT SQL_NO_CACHE * FROM votes WHERE 
    voter_id = 1099 AND 
    rate = 1 AND 
    subject_name = 'medium'
ORDER BY updated_at DESC
LIMIT 20 OFFSET 100;

私はそれがインデックスvoter_timestamp を使用すると思っていましたが、mysqlは代わりにこれを使用することを選択します:

explain select SQL_NO_CACHE * from votes  where subject_name = 'medium' and voter_id = 1001 and rate = 1 order by updated_at desc limit 20 offset 100;`

type:
    index_merge
possible_keys: 
    PRIMARY,IDX_518B7ACFEBB4B8AD,subject_timestamp,voter_timestamp
key:
    IDX_518B7ACFEBB4B8AD,PRIMARY
key_len:
    102,98
ref:
    NULL
rows:
    9255
filtered:
    10.00
Extra:
    Using intersect(IDX_518B7ACFEBB4B8AD,PRIMARY); Using where; Using filesort

また、クエリ時間は200〜400ミリ秒です。

次のように適切なインデックスを使用するように強制すると、

SELECT SQL_NO_CACHE * FROM votes USE INDEX (voter_timestamp) WHERE 
    voter_id = 1099 AND 
    rate = 1 AND 
    subject_name = 'medium'
ORDER BY updated_at DESC
LIMIT 20 OFFSET 100;

MySQLは1〜2ミリ秒で結果を返すことができます

そしてここに説明があります:

type:
    ref
possible_keys:
    voter_timestamp
key:
    voter_timestamp
key_len:
    4
ref:
    const
rows:
    18714
filtered:
    1.00
Extra:
    Using where

では、mysqlがvoter_timestamp元のクエリのインデックスを選択しなかったのはなぜですか?

私が試したのはanalyze table votesoptimize table votesそのインデックスを削除して再度追加することですが、mysqlはまだ間違ったインデックスを使用しています。何が問題なのかよくわからない。


1
@ypercubeᵀᴹ(voter_id、updated_at)インデックスを使用するように強制すると、それを使用して非常に効率的になる場合があるように、where条件のすべての列にインデックスを付ける必要はないと思います。subject_name = "medium"パーツを削除すると、適切なインデックスも選択できるため、インデックスを作成する必要はありませんrate
フェニックスの

それでも、4列のインデックスは2列のインデックスよりも効率的です(voter_id, updated_at)。別のインデックスは(voter_id, subject_name, updated_at)or (subject_name, voter_id, updated_at)(レートなし)です。
ypercubeᵀᴹ

1
そして、はい、あなたは-ある時点で-正しいです。4列のインデックスは必要ありません。これは、このクエリに最適なインデックスです。2カラム(あなたが「正しい」と思うもの)は、現在持っているデータと分布についてはおそらく問題ありません。別のディストリビューションでは、恐ろしいかもしれません。例:行の99%がrate> 1で、rate = 1だったのは1%だけだったとします。2列のインデックスを使用すると効率的だと思いますか?
ypercubeᵀᴹ

インデックスで判断できない基準を満たす120が見つかるまで、インデックスの大部分をトラバースし、テーブルで数千回のルックアップを実行して、レート> 1を見つけて行を拒否する必要があります(subject_name='medium' and rate=1
ypercubeᵀᴹ

ypercube、Phoenix-MySQLは、インデックスが最初にすべてのフィルタリングを満たすまで、LIMITまたはに到達しませんORDER BY。つまり、完全な4列がない場合は、関連するすべての行を収集し、それらをすべて並べ替えてから、選択しますLIMIT。 4列のインデックス、クエリが読んだ後、ソートや停止を避けることができるだけの行を。LIMIT
リックジェームズ

回答:


5

MySQLは、データセットのフィルタリングが非常に優先度の高いクエリを計画するために、比較的単純な(他のRDBMSよりも単純な)コストモデルを使用しています。マージインデックスを使用した最初のクエリでは、インデックスのヒントを使用した2番目のクエリは18000を必要とする一方で、約9000行をスキャンする必要があると推定されます。 。optimizer_traceオンにしてクエリを実行し、結果を評価することで、これを確認(または他の理由を見つける)できます。

set global optimizer_trace='enabled=on';

-- run your query 

SELECT SQL_NO_CACHE * FROM votes WHERE 
    voter_id = 1099 AND 
    rate = 1 AND 
    subject_name = 'medium'
ORDER BY updated_at DESC
LIMIT 20 OFFSET 100;

select * from information_schema.`OPTIMIZER_TRACE`;

一つの注意index_merge:ほとんどの場合、それは非常に高価であることがわかります。OLAPタイプのシナリオには非常に役立ちますが、操作にクエリにかなりの時間がかかる場合があるため、次善の実行プランの方が実際には高速になる場合があるため、OLTPにはあまり適していません。

幸い、MySQLにはオプティマイザ用のスイッチが用意されているので、好きなようにカスタマイズできます。

実行できるすべてのオプションについて:

show global variables like 'optimizer_switch';

変更する場合は、文字列全体をコピーして貼り付ける必要はありません。dict.update()Python などで動作します。

 set global optimizer_switch='index_merge=off';

できれば、テーブル構造も調べて改善します。多くの二次キーを持つ〜100バイトの主キーを持つことは、実際には推奨されません。

あなたは4つの二次キーを持っていて、それらのいくつかは不必要です例えば(voter_id)インデックスはのサブセットです(voter_id, updated_at)


「インデックスマージインターセクト」は、MySQLではほとんど使用されません。おそらくすべてのケースで、より多くの列を持つインデックスを作成する方がはるかに優れています。「インデックスマージユニオン」が役立つ場合があります。に変わるORことUNIONは、多くの場合、同じかそれ以上です。
リックジェームズ

5

そのクエリでは、次のインデックスが必要です。

INDEX(voter_id, rate, subject_name, updated_at)

updated_at最後でなければなりません。他の3つは任意の順序にすることができます。(ypercubeの3列のインデックスは、列に到達WHEREする前に列を終了しないため、あまり役に立ちませんORDER BY。)

このインデックスを追加すると、おそらく他のすべての二次キーを取り除くことができます。

KEY IDX_518B7ACFEBB4B8ADvoter_id)、 - FKは私のインデックスキーを使用することができますsubject_timestampsubject_namesubject_idupdated_atほとんどが冗長KEYは- )、 ( voter_timestampvoter_id)、updated_at -あなたの試みであったかもしれません

4列のインデックスを使用すると、「ページ付け」を最適化して回避できOFFSETます。 このブログを参照してください。

別のトピックについて...とを見るX_nameX_id、「正規化」が行われていると思います。私はテーブルにこれらの2つの列が表示され、他にはほとんど何もないことを期待します。他のテーブルに両方が表示されると思いません

(voter_id, updated_at)voter_idフィルタリング(WHERE)が終了していないため、パスを通過しません。次に、もう一方のインデックスの方が小さいため、それが選択されます。鉱山には、フィルタリングを処理する3つの列があり、次にの列がありORDER BYます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.