特定のIPアドレスのインターネットトラフィック統計を取得するために使用するクエリがあります。
hosts
の個別のIPアドレスフィールドと、assignments
と呼ばれるIPのブロックがあります。データは5分間隔で保存されます。
クエリ結果は時間列でグループ化され、これらの5分間隔の内外の合計SUMがグラフのプロットに使用されます。
テーブルはtraffic
と呼ばれ、(月末に)約2100万件のレコードが含まれます。
SHOW CREATE table traffic:
CREATE TABLE `traffic` (
`type` enum('v4_assignment','v4_Host','v6_subnet','v6_assignment','v6_Host') NOT NULL,
`type_id` int(11) unsigned NOT NULL,
`time` int(32) unsigned NOT NULL,
`bytesin` bigint(20) unsigned NOT NULL default '0',
`bytesout` bigint(20) unsigned NOT NULL default '0',
KEY `basic_select` (`type_id`,`time`,`type`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
SELECT traffic.time, SUM(traffic.bytesin), SUM(traffic.bytesout) FROM traffic
WHERE (
( traffic.type = 'v4_assignment' AND type_id IN (231, between 20 to 100 ids,265)) OR
( traffic.type = 'v4_Host' AND type_id IN (131, ... a lot of ids... ,1506)))
AND traffic.time >= 1343772000 AND traffic.time < 1346450399
GROUP BY traffic.time
ORDER BY traffic.time;
以下は、上記のクエリのexplain
出力です。
+----+-------------+---------+-------+---------------+--------------+---------+------+--------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+-------+---------------+--------------+---------+------+--------+----------------------------------------------+
| 1 | SIMPLE | traffic | range | basic_select | basic_select | 8 | NULL | 891319 | Using where; Using temporary; Using filesort |
+----+-------------+---------+-------+---------------+--------------+---------+------+--------+----------------------------------------------+
show indexes from traffic;
+---------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+---------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| traffic | 1 | basic_select | 1 | type_id | A | 13835 | NULL | NULL | | BTREE | |
| traffic | 1 | basic_select | 2 | time | A | 18470357 | NULL | NULL | | BTREE | |
| traffic | 1 | basic_select | 3 | type | A | 18470357 | NULL | NULL | | BTREE | |
+---------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
このクエリの完了には、30秒から30分かかります。より良いインデックスを使用したり、別のクエリを使用したりして、改善できるといいのですが、それを理解することができません。
更新:
役立つコメンターのアドバイスに従って、主キーを作成し、インデックスtraffic_pk (time, type, type_id, id)
を追加しました。残念ながら、この新しいインデックスのカーディナリティは私の元のインデックス(basic_select)と同じかそれよりも低く、MySQLはまだ元のキーを使用しています。
PDATE 2:元のインデックスbasic_select
を削除したところ、EXPLAIN
はrows
の値が高くなりましたが、EXTRA
フィールドのステップは少なくなりました。また、クエリの実行時間も1分未満になりました。 (まだ少し遅すぎますが、大きな改善です!)。
mysql> SHOW CREATE TABLE traffic_test \G;
*************************** 1. row ***************************
Table: traffic_test
Create Table: CREATE TABLE `traffic_test` (
`traffic_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`type` enum('v4_assignment','v4_Host','v6_subnet','v6_assignment','v6_Host') NOT NULL,
`type_id` int(11) unsigned NOT NULL,
`time` int(32) unsigned NOT NULL,
`bytesin` bigint(20) unsigned NOT NULL DEFAULT '0',
`bytesout` bigint(20) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`time`,`type`,`type_id`,`traffic_id`),
KEY `traffic_id_IDX` (`traffic_id`)
) ENGINE=InnoDB AUTO_INCREMENT=24545159 DEFAULT CHARSET=latin1
テーブルのインデックス:
mysql> SHOW INDEX FROM traffic;
+--------------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| traffic_test | 0 | PRIMARY | 1 | time | A | 18 | NULL | NULL | | BTREE | |
| traffic_test | 0 | PRIMARY | 2 | type | A | 38412 | NULL | NULL | | BTREE | |
| traffic_test | 0 | PRIMARY | 3 | type_id | A | 24545609 | NULL | NULL | | BTREE | |
| traffic_test | 0 | PRIMARY | 4 | traffic_id | A | 24545609 | NULL | NULL | | BTREE | |
| traffic_test | 1 | traffic_id_IDX | 1 | traffic_id | A | 24545609 | NULL | NULL | | BTREE | |
+--------------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
また、OR
を使用しないことでクエリを簡略化しました。
SELECT SQL_NO_CACHE traffic.time, SUM(traffic.bytesin), SUM(traffic.bytesout)
FROM traffic
WHERE traffic.type LIKE 'v4_Host' AND type_id IN (131,1974,1976,1514,1516,2767,2730,2731,2732,2733,2734,2769,2994,2709,1,4613,4614,4615,4616,326,1520,2652,1518,1521,1522,1523,1524,1525,2203,1515,1513,1467,1508,1973,1510,1975,1511,1475,1476,1468,1469,1470,1471,1472,1473,1500,1507,1478,1480,1481,1482,1483,1484,1485,1479,1486,1487,1488,1489,1490,1491,1495,1499,1494,2269,1474,1519,2204,2976,1922,1493,1492,1497,1496,1498,1501,1502,1503,1526,1509,1506)
AND traffic.time >= 1342181721
AND traffic.time < 1343391321
GROUP BY traffic.time ASC;
このクエリの古い実行:
3980 rows in set (6 min 15.27 sec)
新しい実行時間:
3980 rows in set (24.80 sec)
EXPLAIN出力:
+----+-------------+---------+-------+---------------+---------+---------+------+----------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+-------+---------------+---------+---------+------+----------+-------------+
| 1 | SIMPLE | traffic | range | PRIMARY | PRIMARY | 4 | NULL | 12272804 | Using where |
+----+-------------+---------+-------+---------------+---------+---------+------+----------+-------------+
行の値はまだかなり高いです。インデックスでtype
とtype_id
の順序を切り替えることで、これを改善できると思います。可能なタイプは4つだけで、type_idの数が多いためです。
これは正しい仮定ですか?
[AND traffic.time> = 1343772000 AND traffic.time <1346450399]句があるため、このテーブルからデータを削除しないか、またはテーブルが現在複数月のデータを格納していると思います。列[時間]の値はUNIXタイムスタンプのようです(1346450399 =金、2012年8月31日21:59:59 GMT)時間列に基づいてテーブルを分割します。 DBが対応するパーティションをスキャンするため、データの取得が高速化されます(テーブル全体をスキャンするよりもはるかに高速です)。
WHEREブロックの「OR」のため、オプティマイザは定義されたインデックスを使用しないことを選択します。クエリを2つの選択に分割して、和集合を作成します。
SELECT
traffic.time,
SUM(traffic.bytesin),
SUM(traffic.bytesout)
FROM
traffic
WHERE traffic.type LIKE 'v4_assignment'
AND type_id IN (1,2,3,4)
AND traffic.time >= 1343772000 AND traffic.time <= 1346450399
GROUP BY
traffic.time
UNION
SELECT
traffic.time,
SUM(traffic.bytesin),
SUM(traffic.bytesout)
FROM
traffic
WHERE traffic.type LIKE 'v4_Host'
AND type_id IN (5,6,7,8)
AND traffic.time >= 1343772000 AND traffic.time <= 1346450399
GROUP BY
traffic.time
ORDER BY
traffic.time
あなたの説明の出力に基づいて、使用されているインデックスが表示されないようです。おそらく、オプティマイザがフルテーブルスキャンを行ってからインデックスをたどる方が簡単(安価)であると判断したためでしょう。また、現在のインデックスでは、最初の列のカーディナリティが次の2つの列よりも低くなっています。インデックスの最初の列は、カーディナリティが最高(最大)の列にする必要があります。
新しいインデックスを次のように作成します。
MYSQL> CREATE INDEX MTIhai_traffic_idx1 ON traffic(time, type, type_id)
(time, type, type_id, bytes_in, bytes_out)
の複合インデックスをお勧めします。
(type_id, time, type)
の組み合わせが一意である場合(ところで、テーブルの主キーは何ですか?)、主キーを(time, type, type_id)
と定義するだけで済みます。次に、テーブルのクラスター化インデックスがこの主キーとなり、上記の複合インデックスは必要ありません。最も一般的なクエリが何であるかに応じて(このようなgroup by time
やwhere time >=? and time <?
がある場合)、クラスター化インデックスを使用できるため、効率が向上します。
このようにクエリを書き換えることもできます
LIKE
の代わりに=
を使用し、GROUP BY
とORDER BY
の組み合わせ(MySQL独自の構文により効率が向上する可能性があります):
SELECT t.time, SUM(t.bytesin), SUM(t.bytesout)
FROM traffic AS t
WHERE ( t.type = 'v4_assignment' AND t.type_id IN (231, between 20 to 100 ids,265)
OR t.type = 'v4_Host' AND t.type_id IN (131, ... a lot of ids... ,1506)
)
AND t.time >= 1343772000 AND t.time < 1346450399
GROUP BY t.time ASC ;
更新+修正
(InnoDB)テーブルでPRIMARY
およびUNIQUE
インデックスを定義していない場合、非表示の6バイト列が作成され、テーブルのクラスター化インデックスとして使用されます。
したがって、4バイトの自動インクリメント整数列を明示的に定義し、それをtime
列(または上記の3つの列すべて)と組み合わせて、PRIMARY
またはUNIQUE
キーとして使用することをお勧めします。クエリに役立つクラスター化インデックスを作成する以外の目的はありません。
ALTER TABLE traffic
ADD COLUMN
traffic_id INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
ADD CONSTRAINT traffic_PK
PRIMARY KEY (time, type, type_id, traffic_id)
ADD INDEX traffic_id_IDX (traffic_id) ;
または(より狭い主キーを持つため):
ALTER TABLE traffic
ADD COLUMN
traffic_id INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
ADD CONSTRAINT traffic_PK
PRIMARY KEY (time, traffic_id),
ADD INDEX traffic_id_IDX (traffic_id) ;
別の提案:
これらのクラスター化されたインデックスはどちらも、最初に提案した(time, type, type_id, bytes_in, bytes_out)
とほぼ同じです。
パフォーマンスが向上する可能性がある他の唯一のインデックスは(type, type_id, time, bytes_in, bytes_out)
です。ただし、これらのリストにtype_id
がどのように含まれるか、およびそれらが参照するデータのパーセンテージによって異なります。