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


8

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

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

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)

これらの2つの列(time_onおよびdiff_ms)に実際にnullがありますか?クエリに追加するとどうなりますWHERE ... AND diff_ms IS NOT NULLか?
ypercubeᵀᴹ

SELECT COUNT(*), COUNT(diff_ms) FROM writetest_table;
ypercube

また、「更新2」の説明では、クエリが「テーブル:writetest_table_old」を示していますfrom writetest_table。それはタイプミスですか、それとも別のテーブルでクエリを実行しますか?
ypercubeᵀᴹ

回答:


3

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

走るように頼んだとき

SELECT time_on FROM writetest_table ORDER BY time_on LIMIT 1;

あなたはそれが2015-07-13 15:11:56あなたのWHERE条項にあるものだと言った

クエリを行ったとき

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句を使用しないでください。


私の唯一の問題は、ここからどうやって行くかです。100万行のみがフィルター処理され、合計に1秒しかかからない日付でクエリを実行しました。ただし、ほとんどのデータで合計を計算する必要がある場合があります。これを処理する方法の提案はありますか?MySQLがインデックスを使用するときとそうでないときを知るのに十分スマートであることを望んでいましたが、この場合には十分な情報がないと思います。
Locksleyu 2015

日付の範囲を指定するWHERE句を高速にするために構成されたある種のインデックスが技術的に実装可能であるように思えますが、サポートされていないようです。
Locksleyu 2015

このような短距離でデータが多すぎます。WHERE句は補正できません。どうして ?問題となっているのはインデックスではありません。これは、MySQLクエリオプティマイザのインデックスに対する意見です。はるかに多くのデータの蓄積を開始すると(約2週間の価値があるとしましょう)、インデックスの統計は横ばいになり、パフォーマンスが向上するはずです。完全なインデックススキャンは行わないでください。
RolandoMySQLDBA 2015

4

特定のクエリの場合:

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の葬式は数年前でした。
(/暴言)


私はあなたが提案した正確なインデックスを作成し、応答で最初に述べた正確なクエリを実行しましたが、時間はずっと悪くなり、常に約17秒かかりました(何度か試しました)。
Locksleyu 2015

何が原因かわかりません。問題になる場合は、テーブルに存在するtime_onの値は3671のみです(これは、テストスクリプトがデータを入力する方法が原因です)。
Locksleyu

次の3つのことを行う必要があります。1. run ALTER TABLE writetest_table DROP INDEX time_on;、2)run ANALYZE TABLE writetest_table;、および3)クエリを再実行します。時間は7秒に戻りますか?
RolandoMySQLDBA 2015

1
も実行する必要がありますEXPLAIN select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");。新しいインデックスが使用されていますか?それが使用されていない場合、それがキーの母集団であると言えます。最も早いtime_onがほんの数日前である場合は特にそうです。日数が増えるにつれて行数が増加するため、キーの分布は横ばいになり、EXPLAINがより良くなるはずです。 。
RolandoMySQLDBA 2015

RolandoMySQLDBA-私はあなたの3つのステップを試しました、そしてはい、時間が7秒に戻ります。説明しましたが、インデックスが使用されています。このようなインデックスを追加すると、2倍以上のパフォーマンスが低下するのはなぜでしょうか。
Locksleyu 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.