MySQL Indexes для чрезвычайно медленных запросов - PullRequest
3 голосов
/ 28 июля 2011

Следующий запрос, независимо от среды, занимает более 30 секунд для вычисления.

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

Когда я объясняю запрос,

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

У меня есть базовые знания по индексированию, но я пробовал почти все комбинации, которые могу придумать, и не могу улучшитьскорость этого запроса.Таблица ответов составляет около 2 миллионов строк, вопрос - около 1500 строк, answer_group - около 50, а org_survey - около 8 000.

Вот базовая структура для каждого:

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

Я знаю, что есть небольшие вещи, которые я, вероятно, могу сделать, чтобы повысить эффективность базы данных, например, уменьшить размер целых чисел там, где это не нужно.Тем не менее, это довольно тривиально, учитывая нелепое время, необходимое для достижения результата.Как я могу правильно проиндексировать эти таблицы, основываясь на том, что объяснение показало мне?Кажется, что я пробовал большое разнообразие комбинаций безрезультатно.Кроме того, есть ли что-нибудь еще, что кто-либо может увидеть, что оптимизирует таблицу и уменьшит запрос?Мне нужно, чтобы он был рассчитан менее чем за секунду.Заранее спасибо!

Ответы [ 2 ]

5 голосов
/ 28 июля 2011

1.Если вы хотите использовать индекс r.added_dt вместо:

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

use:

CURDATE() - INTERVAL 1000000 DAY < r.added_dt 

В любом случае, вышеуказанное условие проверяет, added_at - это миллион дней или нет.Вы действительно храните такие старые даты?Если нет, вы можете просто удалить это условие.

Если вы хотите это условие, индекс на added_at очень поможет.Ваш текущий запрос проверяет все строки на наличие этого условия, вызывая функцию DATEDIFF() столько раз, сколько строк в таблице response.


2. Так как r.response_answer не может бытьNULL вместо:

SELECT COUNT( r.response_answer ) 

использование:

SELECT COUNT( * ) 

COUNT(*) быстрее COUNT(field).


3.Два изтри поля, которые вы используете для объединения таблиц, имеют разные типы данных:

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 не совпадает с tinyint, и то же самое относится к varchar(7) и varchar(10).Когда они используются для объединения, MySQL теряет время на преобразование из одного типа в другой.Преобразование одного из них, чтобы они имели идентичные типы данных.Это не основная проблема запроса, но это изменение также поможет всем другим запросам, использующим эти объединения.

И после внесения этого изменения выполните «Анализ таблицы» для таблицы.Это поможет MySQL лучше составлять планы выполнения.


У вас есть условие response_answer = 5, где response_answer равно text.Это не ошибка, но лучше использовать response_answer = '5' (преобразование 5 в '5' будет в любом случае выполнено MySQL, если вы этого не сделаете).

Реальная проблема заключается в том, что у вас нет составного индекса по 3 полям, которые используются в условиях WHERE.Попробуйте добавить это:

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

(это может занять некоторое время, так как ваш стол не маленький)

1 голос
/ 28 июля 2011

Можете ли вы попробовать следующий запрос? Я удалил подзапрос из вашего исходного. Это может позволить оптимизатору составить лучший план выполнения.

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...