2009-10-15 3 views
1

Je n'ai pas ce travail d'index (Mysql) ce tableau:Pourquoi

CREATE TABLE `maindb`.`daily_info` (
    `di_date` date NOT NULL, 
    `di_sid` int(10) unsigned NOT NULL default '0', 
    `di_type` int(10) unsigned NOT NULL default '0', 
    `di_name` varchar(20) NOT NULL default '', 
    `di_num` int(10) unsigned NOT NULL default '0', 
    `di_abt` varchar(1) NOT NULL default 'a', 
    PRIMARY KEY (`di_date`,`di_sid`,`di_type`,`di_name`,`di_abt`) 
) ENGINE=MyISAM DEFAULT CHARSET=latin1; 

Lorsque j'utilise cette requête:

explain 
SELECT MONTH(di_date) as label1, DAYOFMONTH(di_date) as label2, sum(di_num) as count , di_abt as abt 
FROM `daily_info` 
WHERE di_sid=6 
     AND di_type = 4 
     AND di_name='clk-1' 
     AND di_date > '2009-10-01' AND di_date < '2009-10-16' 
GROUP BY 
     DAYOFMONTH(di_date) 
ORDER BY 
     TO_DAYS(di_date) DESC 

je reçois:

1, 'SIMPLE', 'daily_info', 'range', 'PRIMARY', 'PRIMARY', '3', '', 2500, 'Using where; Using temporary; Using filesort' 

Lorsque ac Si la clé fonctionnait et si la requête serait filtrée par di_date, di_sid et di_type, elle ne devrait rechercher que quelques dizaines de lignes.

Quel est le problème avec l'index (ou requête?)

Merci!

Répondre

4

Vous utilisez la condition de plage sur la première colonne d'index qui empêche la possibilité de filtrer sur d'autres colonnes.

Il n'existe aucune plage contiguë dans cet index qui contiendrait ces plages et uniquement celles qui satisfont la condition.

MySQL ne peut pas faire SKIP SCAN qui sauterait par-dessus les valeurs distinctes de di_date. C'est pourquoi il fait de son mieux: utilise l'accès range pour filtrer sur di_date et utilise WHERE pour filtrer sur tous les autres champs.

Soit recréer l'index comme cela (la meilleure décision):

PRIMARY KEY (`di_sid`,`di_type`,`di_name`,`di_date`,`di_abt`) 

ou, si vous ne parvenez pas à recréer l'index, vous pouvez émuler le SKIP SCAN:

SELECT MONTH(di.di_date) as label1, DAYOFMONTH(di.di_date) as label2, sum(di.di_num) as count , di.di_abt as abt 
FROM (
     SELECT DISTINCT di_date 
     FROM daily_info 
     WHERE di_date > '2009-10-01' AND di_date < '2009-10-16' 
     ) do 
JOIN daily_info di 
ON  di.di_date <= do.di_date 
     AND di.di_date>= do.di_date 
     AND di_sid = 6 
     AND di_type = 4 
     AND di_name = 'clk-1' 
GROUP BY 
     DAYOFMONTH(di.di_date) 
ORDER BY 
     TO_DAYS(di.di_date) DESC 

Assurez-vous que Using index for group-by et Range checked for each record sont présents dans le plan.

Cette condition:

di.date <= do.date 
AND di.date >= do.date 

est utilisé au lieu de simples di.date = do.date pour forcer la vérification de la plage.

Voir cet article dans mon blog pour une explication plus détaillée de émulant SKIP SCAN:

Mise à jour:

Cette dernière requête utilise en fait un équijointure et MySQL Optimise sans les astuces.

L'astuce ci-dessus s'applique uniquement aux requêtes à distance, i. e. lorsque la boucle la plus interne doit utiliser l'accès range, pas l'accès ref.

Il serait utile si vous deviez faire quelque chose comme di_name <= 'clk-1'

Cette requête devrait fonctionner correctement:

SELECT MONTH(di.di_date) as label1, DAYOFMONTH(di.di_date) as label2, sum(di.di_num) as count , di.di_abt as abt 
FROM (
     SELECT DISTINCT di_date 
     FROM daily_info 
     WHERE di_date > '2009-10-01' AND di_date < '2009-10-16' 
     ) do 
JOIN daily_info di 
ON  di.di_date = do.di_date 
     AND di_sid = 6 
     AND di_type = 4 
     AND di_name = 'clk-1' 
GROUP BY 
     DAYOFMONTH(di.di_date) 
ORDER BY 
     TO_DAYS(di.di_date) DESC 

Assurez-vous que di utilise l'accès ref sur la sous-clé tout possible ici, avec key_len = 33

Mise à jour 2

Dans votre requête, vous utilisez ces expressions de la GROUP BY:

MONTH(di_date) 
TO_DAYS(di_date) 
di_abt 

La requête comme il est maintenant résumera toutes les valeurs pour le 1st, etc. 2nd pour un mois et l'année.

I. e. pour le premier groupe, il ajoutera toutes les valeurs de Jan 1st, 2000, puis Feb 1st, 2000, etc.

Ensuite, il retournera toute valeur aléatoire de MONTH, une valeur aléatoire de TO_DAYS et une valeur aléatoire de di_abt de chaque groupe.

Votre condition est maintenant dans un seul mois, donc c'est OK maintenant, mais si votre condition s'étendra sur plusieurs mois (sans parler des années), la requête produira des résultats inattendus.

Voulez-vous vraiment regrouper par dates?

+0

Merci Quassnoi. – Nir

+0

Inedeed J'ai fait la 1ère option et j'ai récupéré des fichiers. Je vais essayer l'option de sous-sélection. Devrais-je retourner l'index à l'original? – Nir

+0

Vous obtiendrez un fichier de toute façon, il n'y a aucun moyen de s'en débarrasser dans cette requête exacte. Et je viens de remarquer une petite faille dans votre requête, voir la mise à jour post. – Quassnoi

0

Vous analysez la plage de la première partie de l'index. Vous ne pouvez donc pas utiliser les parties suivantes de l'index.

La façon d'améliorer cela est de créer un autre index avec les champs dans un ordre différent qui est plus propice à cette requête particulière.

Si votre index était di_sid, di_type, di_date, cela pourrait être mieux.