web-dev-qa-db-ja.com

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

以下は、約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;

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

enter image description here

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

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は引き続き間違ったインデックスを使用しています。何が問題なのかよくわからない。

9
Phoenix

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';
_

変更する場合は、文字列全体をコピーして貼り付ける必要はありません。 Pythonのdict.update()のように機能します。

_ set global optimizer_switch='index_merge=off';
_

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

4つのセカンダリキーがあり、それらのいくつかは不要です。たとえば、_(voter_id)_インデックスは_(voter_id, updated_at)_のサブセットです

5
Károly Nagy

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

INDEX(voter_id, rate, subject_name, updated_at)

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

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

KEY IDX_518B7ACFEBB4B8ADvoter_id)、-FKは私のインデックスを使用できますKEY subject_timestampsubject_namesubject_idupdated_at)、-ほとんど冗長なKEY voter_timestampvoter_idupdated_at)、-あなたの試みであった可能性があります

4列のインデックスを使用すると、「改ページ調整」を最適化してOFFSETを回避できる可能性があります。 このブログを参照してください。

別のトピックについて... X_nameおよびX_idが表示された場合、「正規化」が行われていると思います。私はこれらの2つの列がテーブルに表示され、他にはほとんど何もないことを期待します。他のテーブルでbothが表示されることを期待します。

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

5
Rick James