web-dev-qa-db-fra.com

Optimisation de la condition WHERE pour le champ TIMESTAMP dans l'instruction MySQL SELECT

Je travaille sur un schéma pour un système d'analyse qui suit les temps d'utilisation, et il est nécessaire de voir le temps d'utilisation total dans une certaine plage de dates.

Pour donner un exemple simple, ce type de requête serait exécuté souvent:

select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");

Cette requête prend généralement environ 7 secondes sur une table fortement peuplée. Il compte environ 35 millions de lignes, MyISAM sur MySQL fonctionnant sur Amazon RDS (db.m3.xlarge).

La suppression de la clause WHERE fait que la requête ne prend que 4 secondes et l'ajout d'une seconde clause (time_off> XXX) ajoute 1,5 seconde supplémentaire, ce qui porte le temps de requête à 8,5 secondes.

Comme je sais que ces types de requêtes seront couramment effectués, je voudrais optimiser les choses afin qu'elles soient plus rapides, idéalement en dessous de 5 secondes.

J'ai commencé par ajouter un index sur time_on, et bien que cela ait considérablement accéléré une requête WHERE "=", cela n'a eu aucun effet sur la requête ">". Existe-t-il un moyen de créer un index qui accélérerait les requêtes WHERE ">" ou "<"?

Ou s'il y a d'autres suggestions sur les performances de ce type de requête, faites-le moi savoir.

Remarque: J'utilise le champ "diff_ms" comme étape de dénormalisation (il est égal à time_off - time_on) qui améliore les performances de l'agrégation d'environ 30% -40%.

Je crée l'index avec cette commande:

ALTER TABLE writetest_table ADD INDEX time_on (time_on) USING BTREE;

Exécuter "expliquer" sur la requête d'origine (avec "time_on>") indique que time_on est une "possible_key" et le select_type est "SIMPLE". La colonne "extra" indique "Utiliser où" et "type" est "TOUT". Une fois l'index ajouté, le tableau indique que "time_on" est de type "MUL", ce qui semble correct car le même temps peut être présent deux fois.

Voici le schéma de la table:

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;

MISE À JOUR: J'ai créé l'index suivant basé sur la réponse de ypercube, mais cela augmente le temps de requête pour la première requête à environ 17 secondes!

ALTER TABLE writetest_table  ADD INDEX time_on__diff_ms__ix (time_on, diff_ms) ;

MISE À JOUR 2: sortie 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)

Mise à jour 3: résultat de la requête demandée

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)
8
Locksleyu

Je pense que je commence à comprendre.

Quand je t'ai demandé de courir

SELECT time_on FROM writetest_table ORDER BY time_on LIMIT 1;

Vous avez dit que c'était 2015-07-13 15:11:56 que vous avez dans votre clause WHERE

Quand vous avez fait la requête

select sum(diff_ms) from writetest_table;

Il a effectué une analyse complète de la table de 35,8 millions de lignes.

Quand vous avez fait la requête

select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");

Il a effectué un balayage d'index complet de 35,8 millions de lignes.

Il est totalement logique que la requête sans la clause WHERE soit plus rapide. Pourquoi?

L'analyse de la table lirait 35,8 millions de lignes en un seul passage linéaire.

L'EXPLAIN sur la requête avec le WHERE a également généré 35,8 millions de lignes. Un balayage d'index se comporterait un peu différemment. Bien que le BTREE conserve l'ordre des touches, il est horrible de faire des analyses de portée. Dans votre cas particulier, vous effectuez la pire analyse de plage possible, qui aurait le même nombre d'entrées BTREE qu'il y a de lignes dans le tableau. MySQL doit parcourir les pages BTREE (au moins à travers les nœuds terminaux) pour lire les valeurs. De plus, le time_on la colonne doit être comparée en cours de route dans l'ordre dicté par l'index. Par conséquent, les nœuds BTREE non-feuilles doivent également être traversés.

S'il vous plaît voir mes messages sur BTREEs

Si la requête était à minuit aujourd'hui

select sum(diff_ms) from writetest_table where time_on >= ("2015-07-14 00:00:00");

ou même midi aujourd'hui

select sum(diff_ms) from writetest_table where time_on >= ("2015-07-14 12:00:00");

cela devrait prendre moins de temps.

MORAL DE L'HISTOIRE: N'utilisez pas de clause WHERE qui effectue une analyse de plage ordonnée égale au nombre de lignes de la table cible.

3
RolandoMySQLDBA

Pour la requête spécifique:

select sum(diff_ms) 
from writetest_table 
where time_on > '2015-07-13 15:11:56' ;     -- use single quotes, not double

un index sur (time_on, diff_ms) serait la meilleure option. Donc, si la requête s'exécute suffisamment souvent ou si son efficacité est cruciale pour votre application, ajoutez cet index:

ALTER TABLE writetest_table 
  ADD INDEX time_on__diff_ms__ix      -- pick a name for the index
    (time_on, diff_ms) ;

(Sans rapport avec la question)
Et vraiment, changez le moteur de la table en InnoDB. C'est 2015 et les funérailles de MyISAM étaient il y a quelques années.
(/ diatribe)

4
ypercubeᵀᴹ