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 généralement 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". Après l'ajout de l'index, 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)
time_on
etdiff_ms
)? Que se passe-t-il si vous ajoutez une requêteWHERE ... AND diff_ms IS NOT NULL
?SELECT COUNT(*), COUNT(diff_ms) FROM writetest_table;
writetest_table_old
" pendant la requêtefrom writetest_table
. Est-ce une faute de frappe ou vous exécutez la requête dans une table différente?Réponses:
Je pense que je commence à comprendre.
Quand je t'ai demandé de courir
Vous avez dit que c'était
2015-07-13 15:11:56
ce que vous aviez dans votreWHERE
clauseQuand vous avez fait la requête
Il a effectué une analyse complète de la table de 35,8 millions de lignes.
Quand vous avez fait la requête
Il a effectué un balayage d'index complet de 35,8 millions de lignes.
Il est parfaitement 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 traverser les pages BTREE (au moins à travers les nœuds terminaux) pour lire les valeurs. De plus, la
time_on
colonne doit être comparée en cours de route dans l'ordre dicté par l'indice. Par conséquent, les nœuds BTREE non-feuilles doivent également être traversés.S'il vous plaît voir mes messages sur BTREEs
Aug 06, 2013
: Dans MySQL si la colonne X a des valeurs uniques, quelle est la différence entre l'index UNIQUE et l'index B-TreeJun 28, 2012
: Avantages de BTREE dans MySQLSi la requête était à minuit aujourd'hui
ou même midi aujourd'hui
cela devrait prendre moins de temps.
MORAL OF THE STORY: N'utilisez pas de clause WHERE qui effectue une analyse de plage ordonnée égale au nombre de lignes de la table cible.
la source
Pour la requête spécifique:
un indice 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:(Pas lié à la question)
Et vraiment, changez le moteur de la table en InnoDB. Nous sommes en 2015 et les funérailles de MyISAM ont eu lieu il y a quelques années.
(/ diatribe)
la source
ALTER TABLE writetest_table DROP INDEX time_on;
, 2) exécuterANALYZE TABLE writetest_table;
et 3) réexécuter la requête. Le temps revient-il à 7 secondes?EXPLAIN select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");
. Le nouvel index est-il utilisé? S'il n'est pas utilisé, je dirais que c'est votre population clé, surtout si votre premier time_on est il y a seulement quelques jours.Comme le nombre de lignes augmente avec des jours plus distincts, la distribution des clés devrait se stabiliser et EXPLAIN devrait être meilleur .