web-dev-qa-db-ja.com

MySQLでこのスローカウントクエリを改善するにはどうすればよいですか?

私はここで迷っています。このクエリが完了するまでに約50秒かかるため、遅すぎると思います。これがクエリです。 db.r3.largeで実行されているAmazon RDSのMySQLとMySQLバージョンMySQL 5.7.17を使用します

SELECT bucket_label            AS bid, 
       Count(user_id)          AS c, 
       Count(DISTINCT user_id) AS cu 
FROM   event_impression 
WHERE  context = 'PROD' 
       AND experiment_id = Unhex(Replace("18454a99-ada6-41a8-b192-bcd3d5c514cb", 
                                 "-", 
                                 "")) 
       AND timestamp >= '2018-04-08 22:21:04' 
       AND timestamp <= '2018-04-10 22:21:04' 
GROUP  BY bucket_label; 

ここにインデックスがあります

mysql> show index from event_impression;
+------------------+------------+-------------------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table            | Non_unique | Key_name                | Seq_in_index | Column_name   | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+------------------+------------+-------------------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| event_impression |          1 | user_id                 |            1 | user_id       | A         |     3248866 |     NULL | NULL   |      | BTREE      |         |               |
| event_impression |          1 | experiment_id           |            1 | experiment_id | A         |        4305 |     NULL | NULL   |      | BTREE      |         |               |
| event_impression |          1 | bucket_label            |            1 | bucket_label  | A         |        7108 |     NULL | NULL   |      | BTREE      |         |               |
| event_impression |          1 | timestamp               |            1 | timestamp     | A         |     3315621 |     NULL | NULL   |      | BTREE      |         |               |
| event_impression |          1 | event_impression_ibfk_1 |            1 | experiment_id | A         |        2914 |     NULL | NULL   |      | BTREE      |         |               |
| event_impression |          1 | event_impression_ibfk_1 |            2 | bucket_label  | A         |        9619 |     NULL | NULL   |      | BTREE      |         |               |
+------------------+------------+-------------------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
6 rows in set (0.01 sec)

これがテーブルスキーマです

mysql> describe event_impression;
+---------------+---------------+------+-----+---------+-------+
| Field         | Type          | Null | Key | Default | Extra |
+---------------+---------------+------+-----+---------+-------+
| user_id       | varchar(200)  | NO   | MUL | NULL    |       |
| experiment_id | varbinary(16) | NO   | MUL | NULL    |       |
| bucket_label  | varchar(64)   | NO   | MUL | NULL    |       |
| timestamp     | datetime      | NO   | MUL | NULL    |       |
| payload       | varchar(4096) | YES  |     | NULL    |       |
| context       | varchar(200)  | YES  |     | PROD    |       |
+---------------+---------------+------+-----+---------+-------+
6 rows in set (0.01 sec)

そして、これがそのクエリの結果です

mysql> select bucket_label as bid,
    -> count(user_id) as c,
    -> count(distinct user_id) as cu
    -> from wasabi.event_impression
    -> where context = 'PROD'
    -> and experiment_id = UNHEX(REPLACE("18454a99-ada6-41a8-b192-bcd3d5c514cb", "-",""))
    -> and timestamp >= '2018-04-08 22:21:04'
    -> and timestamp <= '2018-04-10 22:21:04'
    -> group by bucket_label;
+---------+--------+-------+
| bid     | c      | cu    |
+---------+--------+-------+
| 1       | 294308 | 22403 |
| 1_1     | 185561 | 14703 |
| 2_1     | 267417 | 22183 |
| 2_2     | 284134 | 21945 |
+---------+--------+-------+
4 rows in set (41.22 sec)

このクエリを改善するための提案はあります。または、設定を確認したい場合もお知らせください。

これはテーブルの大きさです

mysql> select count(*) from event_impression;
+----------+
| count(*) |
+----------+
| 40955148 |
+----------+
1 row in set (10.88 sec)

これはEXPLAINです

+----+-------------+------------------+------------+------+--------------------------------------------------------------+-------------------------+---------+-------+----------+----------+------------------------------------+
| id | select_type | table            | partitions | type | possible_keys                                                | key                     | key_len | ref   | rows     | filtered | Extra                              |
+----+-------------+------------------+------------+------+--------------------------------------------------------------+-------------------------+---------+-------+----------+----------+------------------------------------+
|  1 | SIMPLE      | event_impression | NULL       | ref  | experiment_id,bucket_label,timestamp,event_impression_ibfk_1 | event_impression_ibfk_1 | 18      | const | 14958978 |     1.43 | Using index condition; Using where |
+----+-------------+------------------+------------+------+--------------------------------------------------------------+-------------------------+---------+-------+----------+----------+------------------------------------+
1 row in set, 1 warning (0.16 sec)

編集する

explain select bucket_label as bid, 
count(user_id) as c, 
count(distinct user_id) as cu
from wasabi.event_impression
where context = 'PROD' 
and experiment_id = UNHEX(REPLACE("18454a99-ada6-41a8-b192-bcd3d5c514cb", "-",""))
and timestamp >= '2018-04-08 22:21:04'
and timestamp <= '2018-04-10 22:21:04'
group by bucket_label;

'1', 'SIMPLE', 'event_impression', NULL, 'ref', 'experiment_id,bucket_label,timestamp,event_impression_ibfk_1', 'event_impression_ibfk_1', '18', 'const', '14551230', '1.32', 'Using index condition; Using where'

[〜#〜]編集[〜#〜]

Whereステートメントを切り替えて、先頭にタイムスタンプを付けます。

explain select bucket_label as bid, 
count(user_id) as c, 
count(distinct user_id) as cu
from wasabi.event_impression
where timestamp BETWEEN '2018-04-08 22:21:04' AND '2018-04-10 22:21:04'
AND context = 'PROD'
AND experiment_id = UNHEX(REPLACE("18454a99-ada6-41a8-b192-bcd3d5c514cb", "-",""))
group by bucket_label;

+----+-------------+------------------+------------+------+--------------------------------------------------------------+-------------------------+---------+-------+----------+----------+------------------------------------+
| id | select_type | table            | partitions | type | possible_keys                                                | key                     | key_len | ref   | rows     | filtered | Extra                              |
+----+-------------+------------------+------------+------+--------------------------------------------------------------+-------------------------+---------+-------+----------+----------+------------------------------------+
|  1 | SIMPLE      | event_impression | NULL       | ref  | experiment_id,bucket_label,timestamp,event_impression_ibfk_1 | event_impression_ibfk_1 | 18      | const | 16274608 |     1.22 | Using index condition; Using where |
+----+-------------+------------------+------------+------+--------------------------------------------------------------+-------------------------+---------+-------+----------+----------+------------------------------------+


mysql> show index from wasabi.event_impression;
+------------------+------------+-------------------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table            | Non_unique | Key_name                | Seq_in_index | Column_name   | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+------------------+------------+-------------------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| event_impression |          1 | user_id                 |            1 | user_id       | A         |     3836266 |     NULL | NULL   |      | BTREE      |         |               |
| event_impression |          1 | experiment_id           |            1 | experiment_id | A         |        5083 |     NULL | NULL   |      | BTREE      |         |               |
| event_impression |          1 | bucket_label            |            1 | bucket_label  | A         |        8393 |     NULL | NULL   |      | BTREE      |         |               |
| event_impression |          1 | timestamp               |            1 | timestamp     | A         |     3915091 |     NULL | NULL   |      | BTREE      |         |               |
| event_impression |          1 | event_impression_ibfk_1 |            1 | experiment_id | A         |        3441 |     NULL | NULL   |      | BTREE      |         |               |
| event_impression |          1 | event_impression_ibfk_1 |            2 | bucket_label  | A         |       11358 |     NULL | NULL   |      | BTREE      |         |               |
+------------------+------------+-------------------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
6 rows in set (0.01 sec)
1
toy

WHEREの_=_部分でインデックスを開始し、次に「範囲」を1つ追加します。

_INDEX(context, experiment_id,   -- in either order
      timestamp)
_

これにより、クエリの実行が速くなります。

More 最適なインデックスの作成について。

注:「複合」インデックスは、複数の単一列インデックスを持つことと同じではありません。

「カバリング」インデックスはかさばりますが、少し高速に実行できます。

_INDEX(context, experiment_id,   -- in either order
      timestamp,
      bucket_label, user_id)    -- in either order
_

Donotput timestamp first。

インデックスの使用は左から右に機能します。行をスキャンすると、インデックスの最初の列で可能なことを実行してから、次の列に移動します。

最初の列が_=_(たとえば、_context = 'PROD'_)でテストされている場合、インデックス内の隣接するallと一致する行、および次の行列は便利です。

最初の列が「範囲」でテストされる場合(たとえば、_timestamp BETWEEN ... AND ..._)、次の列は役に立たなくなります。したがって、オプティマイザーは最初の範囲で停止します。

おなじみの例...最初に姓でソートされた人のリストがあるとします。そして、「Dave Poole」を検索します。

_INDEX(last, first)
_

私は少し気まぐれがあると思う

_WHERE first = 'Dave'
  AND last  = 'Poole'   -- (in either order)
_

しかし今、クエリが

_WHERE first LIKE 'D%'   -- (this is one form of "range")
  AND last = 'Poole'
_

これは効率的であることがわかります-BTreeをドリルダウンして「Pooles」の長いリストにDがある場所までドリルダウンし、次に前方にスキャンします。

一方、どうですか

_WHERE first = 'Dave'
  AND last LIKE 'P%'
_

では、どのようにしてエントリを見つけますか?まあ、P ...が始まる場所までBTreeをすばやくドリルダウンできますが、姓がPで始まるエントリのallをスキャンする必要があります。より効率的な方法があります。それ。

(もちろん、INDEX(first, last)があると効率的です。)

選択性/カーディナリティについては...

  • Poole + Daveの検索は非常に選択的です-それは両方の組み合わせです。 BTreeはプールせず、次にデイブ。それは同時に両方を行います。
  • Poole + D%の検索は、ペアの組み合わせ選択性でもあり、lastまたはfirstよりも優れています。
  • P%+ ...を検索すると、lastの「P」の選択性しか使用できないため、最悪のケースになります。

したがって、2018年からは...(timestamp)はPROD + abcd + 2018(推奨するインデックス)よりも劣ります。

2
Rick James

カーディナリティが高いほど、インデックスの選択性が高くなります。 event_impression_ibfk_1はExperiment_idとbucket_labelで使用されており、どちらも特に選択的ではないようです。

Explain Planを正しく読んでいると、1490万行または4,000万行のテーブルをスキャンする必要があると思います。静かな時間、またはできればダウンタイムがある場合は、実行する価値があるかもしれません

ANALYZE TABLE event_impression

これは、テーブルの分散統計が最新であることを確認し、クエリオプティマイザーを支援するためです。このようなコマンドを実行するための物理リソースがあることを確認してください。CPUとディスクにかなりの負荷がかかります

テーブルに一意のフィールドの組み合わせがありますか?その場合、これにより、InnoDBエンジンはそれを使用してテーブルのクラスター化インデックスを作成します。一意性を構成するフィールドの1つがタイムスタンプである場合に有利になる範囲スキャンでは、クラスター化インデックスが非常に高速です。テーブルに含めることができるクラスター化インデックスは1つだけです。 MySQLでは、クラスター化インデックスを作成するDBエンジンの優先順位は次のとおりです。

  • 主キー
  • 一意のキー
  • 内部行IDで構成される合成キー

失敗すると、タイムスタンプ、experiment_id、bucket_labelにインデックスを配置しようとします。繰り返しますが、これはクワイエット/ダウンタイムで行うのが最善であり、それを行うために利用可能な物理リソースがあることを確認してください。

別のポイントでは、タイムスタンプなどの予約語であるフィールド名を使用する場合は十分に注意してください。追跡が非常に難しいアプリケーションでスローされる、いくつかの奇妙な例外が発生する可能性があります。

1
Dave Poole

sHOW INDEX FROM event_impressionによると、event_impressionにはPRIMARYキーがありません。カーディナリティー番号を見ると、e_i_timestampなど、列の名前が予約されていないWordに変更された後、e_i_timestamp、user_idをテーブルの複合主キーとして使用できます。 PRIMARY KEYを計画した結果、通常はINNODBのテーブルサイズが小さくなり、40M行に対してより効率的なテーブルになります。

0
Wilson Hauck