問題の簡単な背景
アクティビティを格納する集計テーブルがあり、現在660万行あります。このテーブルからリストを作成する必要があります。
このアクティビティリストには、filteringの機能と、sortingのさまざまな機能があります列。
この表のデータ意味アクティビティ数は、100万/月の割合で増加しています。
MySQLをバージョン5.6で使用しています
テーブル構造は以下の通りです
CREATE TABLE `filter` (
`source_id` bigint(20) unsigned NOT NULL,
`entity_id` varchar(40) NOT NULL,
`type` int(11) NOT NULL,
`metrics_1` bigint(20) unsigned NOT NULL DEFAULT '0',
`metrics_2` bigint(20) unsigned NOT NULL DEFAULT '0',
`metrics_3` int(11) unsigned NOT NULL DEFAULT '1',
`posted_on` datetime NOT NULL,
`updated_on` datetime NOT NULL,
PRIMARY KEY (`source_id`,`type`,`entity_id`),
KEY `indx_posted_on` (`posted_on`,`source_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
このテーブルの主キーは
PRIMARY KEY (`source_id`,`type`,`entity_id`)
Queryアクティビティのリストを取得するために使用しているクエリは
SELECT
filter.`entity_id`
FROM filter AS filter
LEFT JOIN act_deleted AS act_deleted ON act_deleted.entity_id = filter.entity_id
WHERE
filter.source IN(211,493)
AND filter.`type` = 1
AND filter.posted_on BETWEEN '2015-01-29 00:00:00' AND '2016-08-12 23:59:59'
AND filter.metrics_1 BETWEEN 0 AND 9999999999
AND act_deleted.entity_id IS NULL
ORDER BY filter.`posted_on` DESC
LIMIT 5000, 50;
[〜#〜]注[〜#〜]act_deleted
には、削除された行が含まれます。削除された行を特定のユーザーに表示したくない。
type
、metrics_1
、metrics_2
条件は動的であり、ユーザーがこのフィルターを選択すると、WHERE
部分に表示されます。
このクエリに対して作成されたINDEXは
このクエリのposted_on
で並べ替えを行っているため、インデックスについて読み、上記のクエリ用にこのインデックスを作成しました。
`indx_posted_on` (`posted_on`)
私たちが直面している問題は次のとおりです
1)インデックスを使用した場合でも、クエリの初回実行時間は約28 seconds
です。
時間撮影
[〜#〜]注[〜#〜]クエリの連続実行時間は、同じインデックスの1 second
です。私はmysqlを読み取ってテーブルデータをバッファリングするため、連続したクエリの実行が高速になります。
2)クエリのIN
部分に高(最大30ソース)が存在する可能性があり、その場合、適用されたインデックスがクエリ実行プランで変更され、実行時間が約15 seconds
に増加します。 2 seconds
の下にある必要があります。
所要時間
この問題の解決策はFORCE INDEX
を使用することです。 フォースインデックスを使用しても問題ありませんか?
3)最も重要な問題は、metrics_1
、metrics_2
フィールドにもソートを追加する必要があることです。 そのために個別のインデックスを作成する必要がありますか?シナリオごとに個別のインデックスを作成すると、テーブルのサイズが大きくなり、データの間にデッドロックが発生します。挿入。 (現在、テーブルのサイズは約10 GBです)
[〜#〜] update [〜#〜]最も単純なクエリは(ユーザーによるフィルタなし)です
SELECT
filter.*
FROM `filters` AS filter
LEFT JOIN act_deleted AS act_deleted ON (act_deleted.entity_id = filter.entity_id AND act_deleted.user_id = 1)
WHERE
filter.source_id IN (211,493,527,505,554,465,561,565,529,537,504,485,542,590,488,533,468,545,477,547,569,521,513,461,663) AND
(filter.posted_on BETWEEN '2015-07-29 00:00:00' AND '2016-08-12 23:59:59') AND
act_deleted.entity_id IS NULL
ORDER BY filter.`posted_on` DESC / ORDER BY filter.`metrics_1` DESC / ORDER BY filter.`metrics_2` DESC
LIMIT 0, 20;
[〜#〜]更新[〜#〜]
インデックスを追加すると、テーブルサイズが増加し(現在は約10 GBの合計サイズで2 GBのデータと8 GBのインデックスサイズ))、同じクエリの更新部分でデッドロックが発生します。
metrics_1
を一括で更新しようとすると、デッドロックが数回発生します。それはより多くのインデックスが原因であるのか、それとも他の理由が考えられますか?
追加情報が必要な場合はお知らせください。どんな助けでもありがたいです。
ありがとう
テーブルサイズを縮小すると、パフォーマンスが向上します。
BIGINT
を使用しないでください。 _INT UNSIGNED
_は半分のスペースを取ります。INT
(4バイト)を使用しないでください。 ENUM
または_TINYINT UNSIGNED
_は1バイトのみです。_AND filter.metrcs_1 BETWEEN 0 AND 9999999999
_など、WHERE
の不要な部分を避けるために、もう少しコードを記述します。
OFFSET
(_LIMIT 5000, 50
_)による改ページは問題があります。 私のブログ を参照してください
_USE INDEX
_または_FORCE INDEX
_は使用しないでください。今日は役立つかもしれませんが、明日は傷つく可能性があります。
「範囲」で使用される列でstartを使用してもほとんど役に立ちません:KEY _indx_posted_on
_(_posted_on
_、_source_id
_)
thatクエリの場合:INDEX(type, source_id, posted_on),
INDEX(type、Posted_on)_. The first of those covers the case where you have only one
_ source_id. (Does such happen?). The second one is better than
INDEX(posted_on) `。
詳細 最適なインデックス まとめると、最初に_=
_ s、オプションでIN
s、最後に1つの範囲があります。しかし、IN
は_ORDER BY
_が消費されないようにします。
また、_innodb_buffer_pool_size
_がavailableRAMの約70%であることを確認してください。これは、I/Oの削減に役立つ場合があります。
[〜#〜] update [〜#〜](OPのUPDATEに基づく):
_INDEX(source_id, posted_on) -- IN + range -- should help with WHERE
INDEX(posted_on) -- part of WHERE, plus may consume ORDER BY
_
オプティマイザーは2つの間を選択します。
_AND metrics_1 BETWEEN...
_を追加すると、パフォーマンスが低下します。したがって、ユーザーが必要ない場合は、クエリに含めないでください。
可能なクエリのセットが多いので、いくつかのインデックスを作成することをお勧めします。
_INDEX(posted_on) -- to handle the ORDER BY, in case nothing else works well
INDEX(const, posted_on) -- to partially handle WHERE, plus ORDER BY
INDEX(const, range) -- a few 2-column indexes;
first is something used with '=', second is BETWEEN (or other range)
INDEX(in, range) -- a few more 2-column indexes;
first is something used with 'IN', second is a range
_
合計で10以下を目指してください。インデックスを付ける「const」/「in」/「range」列のガイドとして、ユーザーが通常何を求めるかを確認します。また、どのクエリが「遅すぎる」かを調べます。
すべての列が本当に必要な場合を除き、_filter.*
_は使用しないでください。特に、すべてのTEXT
列を省略できる場合は、一時テーブルの処理方法がになる可能性があります。
テキスト列のある大きなテーブルの場合、この「レイジーeval」はパフォーマンスに役立ちます。
_SELECT filter.*
FROM (
SELECT primary-key-fields-of-filter
rest-of-existing-SELECT
order-by-limit
) x
JOIN filter USING(primary-key-fields-of-filter)
ORDER BY repeat-the-order-by-but-not-limit
_