以下は、約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 votes
、optimize table votes
であり、そのインデックスを削除して再度追加しましたが、mysqlは引き続き間違ったインデックスを使用しています。何が問題なのかよくわからない。
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)
_のサブセットです
そのクエリには、次のインデックスが必要です。
INDEX(voter_id, rate, subject_name, updated_at)
updated_at
は最後でなければなりません。他の3つは任意の順序にすることができます。 (ypercubeの3列のインデックスは、ORDER BY
列に到達する前にWHERE
列を終了しないため、あまり役に立ちません。)
このインデックスを追加すると、おそらく他のすべての二次キーを取り除くことができます。
KEY IDX_518B7ACFEBB4B8AD
(voter_id
)、-FKは私のインデックスを使用できますKEY subject_timestamp
(subject_name
、subject_id
、updated_at
)、-ほとんど冗長なKEY voter_timestamp
(voter_id
、updated_at
)、-あなたの試みであった可能性があります
4列のインデックスを使用すると、「改ページ調整」を最適化してOFFSET
を回避できる可能性があります。 このブログを参照してください。
別のトピックについて... X_name
およびX_id
が表示された場合、「正規化」が行われていると思います。私はこれらの2つの列がテーブルに表示され、他にはほとんど何もないことを期待します。他のテーブルでbothが表示されることを期待します。
(voter_id, updated_at)
は、フィルタリングが終了していないため(WHERE
)、voter_id
を超えません。次に、もう一方のインデックスの方が小さいため、それが選択されます。鉱山には、フィルタリングを処理する3つの列があり、次にORDER BY
の列があります。