2011-07-27 4 views
3

La requête suivante, quel que soit l'environnement, prend plus de 30 secondes à calculer.Index MySQL pour les requêtes extrêmement lentes

SELECT COUNT(r.response_answer) 
FROM response r 
INNER JOIN (
SELECT G.question_id 
FROM question G 
INNER JOIN answer_group AG ON G.answer_group_id = AG.answer_group_id 
WHERE AG.answer_group_stat = 'statistic' 
) AS q ON r.question_id = q.question_id 
INNER JOIN org_survey os ON os.org_survey_code = r.org_survey_code 
WHERE os.survey_id =42 
AND r.response_answer = 5 
AND DATEDIFF(NOW() , r.added_dt) <1000000 
AND r.uuid IS NOT NULL 

Quand j'explique la requête,

id select_type table type possible_keys key key_len ref rows Extra 
1 PRIMARY <derived2> ALL NULL NULL NULL NULL 1087  
1 PRIMARY r ref question_id,org_survey_code,code_question,uuid,uor question_id 4 q.question_id 1545 Using where 
1 PRIMARY os eq_ref org_survey_code,survey_id,org_survey_code_2 org_survey_code 12 survey_2.r.org_survey_code 1 Using where 
2 DERIVED G ALL agid NULL NULL NULL 1680  
2 DERIVED AG eq_ref PRIMARY PRIMARY 1 survey_2.G.answer_group_id 1 Using where 

J'ai une connaissance de base de l'indexation, mais je l'ai essayé presque toutes les combinaisons que je peux penser et ne peut pas sembler pour améliorer la vitesse de cette requête . La table des réponses est juste autour de 2 millions de lignes, la question est d'environ 1500 lignes, answer_group est d'environ 50, et org_survey est d'environ 8000.

Voici la structure de base pour chacun:

CREATE TABLE `response` (
`response_id` int(10) unsigned NOT NULL auto_increment, 
`response_answer` text NOT NULL, 
`question_id` int(10) unsigned NOT NULL default '0', 
`org_survey_code` varchar(7) NOT NULL, 
`uuid` varchar(40) default NULL, 
`added_dt` datetime default NULL, 
PRIMARY KEY (`response_id`), 
KEY `question_id` (`question_id`), 
KEY `org_survey_code` (`org_survey_code`), 
KEY `code_question` (`org_survey_code`,`question_id`), 
KEY `IDX_ADDED_DT` (`added_dt`), 
KEY `uuid` (`uuid`), 
KEY `response_answer` (`response_answer`(1)), 
KEY `response_question` (`response_answer`(1),`question_id`), 
) ENGINE=MyISAM AUTO_INCREMENT=2298109 DEFAULT CHARSET=latin1 

CREATE TABLE `question` (
`question_id` int(10) unsigned NOT NULL auto_increment, 
`question_text` varchar(250) NOT NULL default '', 
`question_group` varchar(250) default NULL, 
`question_position` tinyint(3) unsigned NOT NULL default '0', 
`survey_id` tinyint(3) unsigned NOT NULL default '0', 
`answer_group_id` mediumint(8) unsigned NOT NULL default '0', 
`seq_id` int(11) NOT NULL default '0', 
PRIMARY KEY (`question_id`), 
KEY `question_group` (`question_group`(10)), 
KEY `survey_id` (`survey_id`), 
KEY `agid` (`answer_group_id`) 
) ENGINE=MyISAM AUTO_INCREMENT=1860 DEFAULT CHARSET=latin1 

CREATE TABLE `org_survey` (
`org_survey_id` int(11) NOT NULL auto_increment, 
`org_survey_code` varchar(10) NOT NULL default '', 
`org_id` int(11) NOT NULL default '0', 
`org_manager_id` int(11) NOT NULL default '0', 
`org_url_id` int(11) default '0', 
`division_id` int(11) default '0', 
`sector_id` int(11) default NULL, 
`survey_id` int(11) NOT NULL default '0', 
`process_batch` tinyint(4) default '0', 
`added_dt` datetime default NULL, 
PRIMARY KEY (`org_survey_id`), 
UNIQUE KEY `org_survey_code` (`org_survey_code`), 
KEY `org_id` (`org_id`), 
KEY `survey_id` (`survey_id`), 
KEY `org_survey_code_2` (`org_survey_code`,`total_taken`), 
KEY `org_manager_id` (`org_manager_id`), 
KEY `sector_id` (`sector_id`) 
) ENGINE=MyISAM AUTO_INCREMENT=9268 DEFAULT CHARSET=latin1 

CREATE TABLE `answer_group` (
`answer_group_id` tinyint(3) unsigned NOT NULL auto_increment, 
`answer_group_name` varchar(50) NOT NULL default '', 
`answer_group_type` varchar(20) NOT NULL default '', 
`answer_group_stat` varchar(20) NOT NULL default 'demographic', 
PRIMARY KEY (`answer_group_id`) 
) ENGINE=MyISAM AUTO_INCREMENT=53 DEFAULT CHARSET=latin1 

Je sais qu'il ya des petites choses que je peux probablement faire pour améliorer l'efficacité de la base de données, telles que la réduction de la taille des entiers où il est inutile. Cependant, ceux-ci sont assez triviales considérant le temps ridicule qu'il prend juste pour produire un résultat ici. Comment puis-je indexer correctement ces tables, en fonction de ce que l'explication m'a montré? Il semble que j'ai essayé une grande variété de combinaisons en vain. En outre, y a-t-il quelque chose que tout le monde peut voir qui optimisera la table et réduira la requête? J'ai besoin qu'il soit calculé en moins d'une seconde. Merci d'avance!

Répondre

1

Pouvez-vous essayer la requête suivante? J'ai supprimé la sous-requête de votre version d'origine. Cela peut permettre à l'optimiseur de produire un meilleur plan d'exécution.

SELECT COUNT(r.response_answer) 
FROM response r 
    INNER JOIN question q  ON r.question_id = q.question_id 
    INNER JOIN answer_group ag ON q.answer_group_id = ag.answer_group_id 
    INNER JOIN org_survey os ON os.org_survey_code = r.org_survey_code 
WHERE 
     ag.answer_group_stat = 'statistic' 
    AND os.survey_id = 42 
    AND r.response_answer = 5 
    AND DATEDIFF(NOW(), r.added_dt) < 1000000 
    AND r.uuid IS NOT NULL 
5

1. Si vous voulez que l'indice de r.added_dt à utiliser, au lieu de:

DATEDIFF(NOW(), r.added_dt) < 1000000 

utilisation:

CURDATE() - INTERVAL 1000000 DAY < r.added_dt 

Quoi qu'il en soit, la condition ci-dessus vérifie si added_at est un million de jours ou non. Rangez-vous vraiment des dattes si anciennes? Sinon, vous pouvez simplement supprimer cette condition.

Si vous voulez cette condition, un index sur added_at aiderait beaucoup. Votre requête telle qu'elle est maintenant, vérifie toutes les lignes pour cette condition, appelant la fonction DATEDIFF() autant de fois que les lignes de la table response.


2.Depuis r.response_answer ne peut pas être NULL, au lieu de:

SELECT COUNT(r.response_answer) 

utilisation:

SELECT COUNT(*) 

COUNT(*) est plus rapide que COUNT(field).


3.Deux des trois champs que vous utilisez pour les tables joindre ont différents types de données:

ON  question . answer_group_id 
    = answer_group . answer_group_id 

CREATE TABLE question (
    ... 
    answer_group_id mediumint(8) ...,    <--- mediumint 

CREATE TABLE answer_group (
    answer_group_id` tinyint(3) ...,    <--- tinyint 

------------------------------- 

ON org_survey . org_survey_code 
    = response . org_survey_code 

CREATE TABLE response (
    ... 
    org_survey_code varchar(7) NOT NULL,    <--- 7 

CREATE TABLE org_survey (
    ... 
    org_survey_code varchar(10) NOT NULL default '', <--- 10 

mediumint est Datatype pas le même que tinyint et va de même pour varchar(7) et varchar(10). Quand ils sont utilisés pour la jointure, MySQL doit perdre du temps à convertir un type en un autre. Convertir l'un d'entre eux afin qu'ils aient des types de données identiques. Ce n'est pas le problème principal de la requête, mais cette modification aidera également toutes les autres requêtes qui utilisent ces jointures.

Et après avoir effectué cette modification, faites une 'Table d'analyse' pour la table. Cela aidera mysql à faire de meilleurs plans d'exécution.


Vous avez une condition response_answer = 5, où response_answer est text. Ce n'est pas une erreur, mais il vaut mieux utiliser response_answer = '5' (la conversion de 5 en '5' sera faite par MySQL de toute façon, si vous ne le faites pas).

Le vrai problème est que vous n'avez pas un index composé sur les 3 champs qui sont utilisés dans les conditions WHERE. Essayez d'ajouter celui-ci:

ALTER TABLE response 
    ADD INDEX ind_u1_ra1_aa 
     (uuid(1), response_answer(1), added_at) ; 

(cela peut prendre un certain temps que votre table est pas petite)

Questions connexes