web-dev-qa-db-ja.com

インデックスを使用してInnoDBのCOUNT(*)パフォーマンスを最適化する方法

〜9mのレコードを持つInnoDBテーブルが大きくて狭いです。テーブルでcount(*)またはcount(id)を実行すると、非常に遅くなります(6秒以上):

_DROP TABLE IF EXISTS `perf2`;

CREATE TABLE `perf2` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `channel_id` int(11) DEFAULT NULL,
  `timestamp` bigint(20) NOT NULL,
  `value` double NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ts_uniq` (`channel_id`,`timestamp`),
  KEY `IDX_CHANNEL_ID` (`channel_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

RESET QUERY CACHE;
SELECT COUNT(*) FROM perf2;
_

ステートメントはあまり頻繁に実行されませんが、最適化するのは良いことです。 http://www.cloudspace.com/blog/2009/08/06/fast-mysql-innodb-count-really-fast/ によると、これはInnoDBにインデックスの使用を強制することで可能になります。 :

_SELECT COUNT(id) FROM perf2 USE INDEX (PRIMARY);
_

EXPLAIN PLANは問題ないようです。

_id  select_type table   type    possible_keys   key     key_len ref     rows    Extra
1   SIMPLE      perf2   index   NULL            PRIMARY 4       NULL    8906459 Using index
_

残念ながら、声明は以前と同じくらい遅いです。 "SELECT COUNT(*)"は、where句がある場合でも遅い によると、成功せずにテーブルの最適化も試みました。

InnoDBでCOUNT(*)パフォーマンスを最適化する方法は何ですか?

26
andig

当分の間、私はこの近似を使用して問題を解決しました:

EXPLAIN SELECT COUNT(id) FROM data USE INDEX (PRIMARY)

上記のようにInnoDBを使用する場合、おおよその行数は、Explain Planのrows列から読み取ることができます。 MyISAMを使用する場合、テーブル参照が最適化されているため、これは空のままです。したがって、従来のSELECT COUNT代わりに。

17
andig

MySQL 5.1.6以降では、 Event Scheduler を使用して、統計テーブルに定期的にカウントを挿入できます。

最初に、カウントを保持するテーブルを作成します。

CREATE TABLE stats (
`key` varchar(50) NOT NULL PRIMARY KEY,
`value` varchar(100) NOT NULL);

次に、テーブルを更新するイベントを作成します。

CREATE EVENT update_stats
ON SCHEDULE
  EVERY 5 MINUTE
DO
  INSERT INTO stats (`key`, `value`)
  VALUES ('data_count', (select count(id) from data))
  ON DUPLICATE KEY UPDATE value=VALUES(value);

完璧ではありませんが、必要な新鮮さの数だけ実行するように簡単に調整できる自己完結型のソリューション(cronジョブまたはキューなし)を提供します。

15
Che

@Cheコードに基づいて、statsテーブルの値を最新の状態に保つために、perf2へのINSERTおよびUPDATEでトリガーを使用することもできます。

CREATE TRIGGER `count_up` AFTER INSERT ON `perf2` FOR EACH ROW UPDATE `stats`
SET 
  `stats`.`value` = `stats`.`value` + 1 
WHERE
  `stats`.`key` = "perf2_count";

CREATE TRIGGER `count_down` AFTER DELETE ON `perf2` FOR EACH ROW UPDATE `stats`
SET 
  `stats`.`value` = `stats`.`value` - 1 
WHERE
  `stats`.`key` = "perf2_count";

これには、count(*)を実行するパフォーマンスの問題を排除できるという利点があり、テーブルのデータが変更されたときにのみ実行されますperf2

13
MQuirion