1

MySQL 5.0.77 で実行されているアプリケーションに、大きくて急速に成長しているログ テーブルがあります。メッセージの種類に応じて、過去 X 日間のインスタンスをカウントするクエリを最適化する最善の方法を見つけようとしています。

CREATE TABLE `counters` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `kind` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_counters_on_kind` (`kind`),
  KEY `index_counters_on_created_at` (`created_at`)
) ENGINE=InnoDB AUTO_INCREMENT=302 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

このテスト セットでは、テーブルに 668521 行あります。最適化しようとしているクエリは次のとおりです。

SELECT kind, COUNT(id) FROM counters WHERE created_at >= ? GROUP BY kind;

現在、そのクエリには 3 ~ 5 秒かかり、次のように見積もられています。

+----+-------------+----------+-------+----------------------------------+------------------------+---------+------+---------+-------------+
| id | select_type | table    | type  | possible_keys                    | key                    | key_len | ref  | rows    | Extra       |
+----+-------------+----------+-------+----------------------------------+------------------------+---------+------+---------+-------------+
|  1 | SIMPLE      | counters | index | index_counters_on_created_at_idx | index_counters_on_kind | 258     | NULL | 1185531 | Using where | 
+----+-------------+----------+-------+----------------------------------+------------------------+---------+------+---------+-------------+
1 row in set (0.00 sec)

created_at インデックスを削除すると、次のようになります。

+----+-------------+----------+-------+---------------+------------------------+---------+------+---------+-------------+
| id | select_type | table    | type  | possible_keys | key                    | key_len | ref  | rows    | Extra       |
+----+-------------+----------+-------+---------------+------------------------+---------+------+---------+-------------+
|  1 | SIMPLE      | counters | index | NULL          | index_counters_on_kind | 258     | NULL | 1185531 | Using where | 
+----+-------------+----------+-------+---------------+------------------------+---------+------+---------+-------------+
1 row in set (0.00 sec)

(はい、何らかの理由で、行の見積もりがテーブル内の行数よりも大きくなっています。)

したがって、明らかに、そのインデックスには意味がありません。

これを行うより良い方法は本当にありませんか?列をタイムスタンプとして試しましたが、遅くなりました。

編集:特定の日付の代わりに間隔を使用するようにクエリを変更すると、最終的にインデックスが使用され、行の見積もりが上記のクエリの約 20% に削減されることがわかりました。

SELECT kind, COUNT(id) FROM counters WHERE created_at >= 
    (NOW() - INTERVAL 7 DAY) GROUP BY kind;

なぜそうなったのかは完全にはわかりませんが、それを理解すれば、問題は一般的にもっと理にかなっているだろうと確信しています.

4

2 に答える 2

0

連結インデックスを使用しないのはなぜですか?

CREATE INDEX idx_counters_created_kind ON counters(created_at, kind);

インデックスのみのスキャンを実行する必要があります(COUNT(ID)はとにかくNULLではないため、Extrasで「インデックスの使用」に言及します)。

参照:

于 2011-10-20T07:08:31.647 に答える
0

質問の最新の編集を読んだ後、問題は、WHERE句で使用されているパラメーターが、MySQL によって値ではなく文字列として解釈されていたことにあるようdatetimeです。これにより、index_counters_on_created_atインデックスがオプティマイザーによって選択されなかった理由が説明され、代わりに、値を文字列表現に変換してcreated_atから比較を行うためのスキャンが行われます。datetimeこれは、where句で明示的にキャストすることで防ぐことができると思います。

where `created_at` >= convert({specific_date}, datetime)

私の元のコメントは、最適化の部分にも当てはまります。

ここでの真のパフォーマンス キラーはkindコラムです。データベースエンジンを実行するとき、最初に列GROUP BY内のすべての個別の値を決定する必要があり、その結果、テーブルまたはインデックスのスキャンが行われるためです。kindそのため、推定された行がテーブル内の行の総数よりも大きくなります。1 回のパスで列内の個別の値が決定さkindれ、2 回目のパスでどの行がcreate_at >= ?条件を満たすかが決定されます。さらに悪いことに、kind列はvarchar (255)大きすぎて効率的ではなく、utf8文字セットとutf8_unicode_ci照合を使用することに加えて、その列の一意の値を決定するために必要な比較の複雑さが増します。

kind列のタイプを に変更すると、パフォーマンスが大幅に向上しintます。整数の比較は、Unicode 文字の比較よりも効率的で単純だからです。また、およびkindを格納する の メッセージのカタログ テーブルがあると役立ちます。次に、種類のカタログ テーブルの結合と、最初に日付でフィルター処理するログ テーブルのサブクエリでグループ化を行います。kind_iddescription

select k.kind_id, count(*)
from
    kind_catalog k
    inner join (
        select kind_id
        from counters
        where create_at >= ?
    ) c on k.kind_id = c.kind_id
group by k.kind_id

countersこれにより、最初にテーブルがフィルター処理さcreate_at >= ?れ、その列のインデックスの恩恵を受けることができます。次に、それをkind_catalogテーブルに結合し、SQL オプティマイザーが適切であれば、テーブルkind_catalogの代わりに小さいテーブルをスキャンしてグループ化を行いcountersます。

于 2011-10-19T22:51:18.390 に答える