web-dev-qa-db-ja.com

MySQL SELECTステートメントのTIMESTAMPフィールドのWHERE条件を最適化する

使用時間を追跡する分析システムのスキーマに取り組んでいます。特定の日付範囲の合計使用時間を確認する必要があります。

簡単な例を挙げると、このタイプのクエリは頻繁に実行されます。

select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");

このクエリは、多くの場合、データが多く含まれるテーブルで約7秒かかります。約3,500万行、Amazon RDS(db.m3.xlarge)で実行されているMySQLのMyISAMがあります。

WHERE句を取り除くことで、クエリの所要時間はわずか4秒になり、2番目の句(time_off> XXX)を追加すると、さらに1.5秒追加され、クエリ時間が8.5秒になります。

私はこれらのタイプのクエリが一般的に行われることを知っているので、それらをより速く、理想的には5秒未満に最適化したいと思います。

私はtime_onにインデックスを追加することから始めましたが、WHERE "="クエリは大幅に高速化しましたが、 ">"クエリには影響がありませんでした。 WHERE ">"または "<"クエリを高速化するインデックスを作成する方法はありますか?

または、このタイプのクエリのパフォーマンスについて他に提案がある場合は、お知らせください。

注:「diff_ms」フィールドを非正規化ステップとして使用しています(time_off-time_onと同じです)。これにより、集約のパフォーマンスが約30%から40%向上します。

私はこのコマンドでインデックスを作成しています:

ALTER TABLE writetest_table ADD INDEX time_on (time_on) USING BTREE;

元のクエリ(「time_on>」を使用)で「explain」を実行すると、time_onは「possible_key」で、select_typeは「SIMPLE」になります。 「追加」列は「使用場所」を示し、「タイプ」は「すべて」です。インデックスが追加された後、テーブルは「time_on」が「MUL」キータイプであることを示しています。これは、同じ時間が2回存在する可能性があるため、正しいように見えます。

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

CREATE TABLE `writetest_table` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `sessionID` int(11) DEFAULT NULL,
  `time_on` timestamp NULL DEFAULT NULL,
  `time_off` timestamp NULL DEFAULT NULL,
  `diff_ms` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `time_on` (`time_on`)
) ENGINE=MyISAM AUTO_INCREMENT=50410902 DEFAULT CHARSET=latin1;

更新:ypercubeの応答に基づいて次のインデックスを作成しましたが、これにより最初のクエリのクエリ時間が約17秒に増加します!

ALTER TABLE writetest_table  ADD INDEX time_on__diff_ms__ix (time_on, diff_ms) ;

更新2:EXPLAIN出力

mysql> explain select sum(diff_ms) from writetest_table where time_on > '2015-07-13 15:11:56';
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
| id | select_type | table               | type  | possible_keys        | key                  | key_len | ref  | rows     | Extra                    |
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
|  1 | SIMPLE      | writetest_table_old | index | time_on__diff_ms__ix | time_on__diff_ms__ix | 10      | NULL | 35831102 | Using where; Using index |
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
1 row in set (0.00 sec)

更新3:要求されたクエリの結果

mysql> SELECT time_on FROM writetest_table ORDER BY time_on LIMIT 1;
+---------------------+
| time_on             |
+---------------------+
| 2015-07-13 15:11:56 |
+---------------------+
1 row in set (0.01 sec)
8
Locksleyu

私は理解し始めていると思います。

走るように頼んだとき

SELECT time_on FROM writetest_table ORDER BY time_on LIMIT 1;

2015-07-13 15:11:56WHERE句に含まれています

クエリを行ったとき

select sum(diff_ms) from writetest_table;

3580万行の全表スキャンを実行しました。

クエリを行ったとき

select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");

3580万行のフルインデックススキャンを実行しました。

WHERE句のないクエリの方が高速であることは完全に理にかなっています。なぜですか?

テーブルスキャンは、1回のリニアパスで3580万行を読み取ります。

WHEREを使用したクエリのEXPLAINでも、3580万行が増加しました。インデックススキャンの動作は少し異なります。 BTREEはキーの順序を保持しますが、範囲スキャンを行うのは恐ろしいことです。特定のケースでは、最悪の範囲スキャンを実行しています。これには、テーブルに行があるのと同じ数のBTREEエントリがあります。 MySQLは、値を読み取るためにBTREEページを(少なくともリーフノード全体で)トラバースする必要があります。加えて time_on列は、インデックスで指定された順序で途中で比較する必要があります。したがって、非リーフBTREEノードもトラバースする必要があります。

BTREEに関する私の投稿をご覧ください

クエリが今日の午前0時の場合

select sum(diff_ms) from writetest_table where time_on >= ("2015-07-14 00:00:00");

今日も正午

select sum(diff_ms) from writetest_table where time_on >= ("2015-07-14 12:00:00");

時間がかからないはずです。

MORAL OF THE STORY:ターゲットテーブルの行数に等しい順序付けされた範囲スキャンを実行するWHERE句を使用しないでください。

3
RolandoMySQLDBA

特定のクエリの場合:

select sum(diff_ms) 
from writetest_table 
where time_on > '2015-07-13 15:11:56' ;     -- use single quotes, not double

(time_on, diff_ms)のインデックスが最適なオプションです。したがって、クエリが十分に頻繁に実行される場合、またはその効率がアプリケーションにとって重要である場合は、次のインデックスを追加します。

ALTER TABLE writetest_table 
  ADD INDEX time_on__diff_ms__ix      -- pick a name for the index
    (time_on, diff_ms) ;

(質問とは関係ありません)
そして実際に、テーブルのエンジンをInnoDBに変更します。それは2015年であり、MyISAMの葬式は数年前でした。
(/ rant)

4
ypercubeᵀᴹ