web-dev-qa-db-ja.com

IN、BETWEEN、ORDER BY、LIMITクエリのインデックス最適化

問題の簡単な背景

アクティビティを格納する集計テーブルがあり、現在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には、削除された行が含まれます。削除された行を特定のユーザーに表示したくない。

typemetrics_1metrics_2条件は動的であり、ユーザーがこのフィルターを選択すると、WHERE部分に表示されます。

このクエリに対して作成されたINDEXは

このクエリのposted_onで並べ替えを行っているため、インデックスについて読み、上記のクエリ用にこのインデックスを作成しました。

`indx_posted_on` (`posted_on`)

実行計画 enter image description here

私たちが直面している問題は次のとおりです

1)インデックスを使用した場合でも、クエリの初回実行時間は約28 secondsです。

時間撮影

enter image description here

[〜#〜]注[〜#〜]クエリの連続実行時間は、同じインデックスの1 secondです。私はmysqlを読み取ってテーブルデータをバッファリングするため、連続したクエリの実行が高速になります。

2)クエリのIN部分に高(最大30ソース)が存在する可能性があり、その場合、適用されたインデックスがクエリ実行プランで変更され、実行時間が約15 secondsに増加します。 2 secondsの下にある必要があります。

このシナリオのExplainの出力は次のようになります enter image description here

所要時間

enter image description here

この問題の解決策はFORCE INDEXを使用することです。 フォースインデックスを使用しても問題ありませんか?

3)最も重要な問題は、metrics_1metrics_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を一括で更新しようとすると、デッドロックが数回発生します。それはより多くのインデックスが原因であるのか、それとも他の理由が考えられますか?

追加情報が必要な場合はお知らせください。どんな助けでもありがたいです。

ありがとう

1
Paresh Balar

テーブルサイズを縮小すると、パフォーマンスが向上します。

  • 40億を超えるIDが必要になると予想される場合を除き、BIGINTを使用しないでください。 _INT UNSIGNED_は半分のスペースを取ります。
  • 低カーディナリティフラグ/タイプ/レベル(?)/ etcに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、オプションでINs、最後に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
_
1
Rick James