web-dev-qa-db-fra.com

Comment optimiser les performances COUNT (*) sur InnoDB en utilisant l'index

J'ai une table InnoDB large mais étroite avec ~ 9m d'enregistrements. Faire count(*) ou count(id) sur la table est extrêmement lent (6+ secondes):

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;

Bien que l'instruction ne soit pas exécutée trop souvent, il serait intéressant de l'optimiser. Selon http://www.cloudspace.com/blog/2009/08/06/fast-mysql-innodb-count-really-fast/ cela devrait être possible en forçant InnoDB à utiliser un index :

SELECT COUNT(id) FROM perf2 USE INDEX (PRIMARY);

Le plan d'explication semble correct:

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

Malheureusement, la déclaration est aussi lente qu'auparavant. Selon "SELECT COUNT (*)" est lent, même avec la clause where J'ai également essayé d'optimiser la table sans succès.

Quel/est le/re un moyen d'optimiser les performances de COUNT(*) sur InnoDB?

26
andig

Pour l'instant, j'ai résolu le problème en utilisant cette approximation:

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

Le nombre approximatif de lignes peut être lu dans la colonne rows du plan d'explication lors de l'utilisation d'InnoDB comme indiqué ci-dessus. Lors de l'utilisation de MyISAM, cela restera VIDE car la référence de la table est optimisée à l'écart, donc si le repli vide vers le SELECT COUNT Traditionnel à la place.

17
andig

Depuis MySQL 5.1.6, vous pouvez utiliser Event Scheduler et insérer régulièrement le décompte dans un tableau de statistiques.

Créez d'abord une table pour contenir le décompte:

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

Créez ensuite un événement pour mettre à jour la table:

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);

Ce n'est pas parfait, mais il offre une solution autonome (pas de cronjob ou de file d'attente) qui peut être facilement adaptée pour fonctionner aussi souvent que la fraîcheur requise du compte.

15
Che

Basé sur le code @Che, vous pouvez également utiliser des déclencheurs sur INSERT et UPDATE to perf2 afin de maintenir la valeur dans le tableau des statistiques à jour.

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";

Cela aurait l'avantage d'éliminer le problème de performances lié à l'exécution d'un comptage (*) et ne serait exécuté que lorsque les données changent dans la table perf2

13
MQuirion