Как оптимизировать запросы MySQL на основе плана EXPLAIN

Рассматривая план запроса EXPLAIN, как определить, какие оптимизации лучше всего сделать?

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

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

id   select_type   table   type     possible_keys    key       key_len   ref                    rows   Extra
 1   SIMPLE        S       const    PRIMARY,l,p,f4   PRIMARY         2   const                     1   Using temporary
 1   SIMPLE        Q       ref      PRIMARY,S        S               2   const                   204   Using index
 1   SIMPLE        V       ref      PRIMARY,n,Q      Q               5   const,db.Q.QID            6   Using where; Using index; Distinct
 1   SIMPLE        R1      ref      PRIMARY,L        L             154   const,db.V.VID          447   Using index; Distinct
 1   SIMPLE        W       eq_ref   PRIMARY,w        PRIMARY         5   const,db.R.RID,const      1   Using where; Distinct
 1   SIMPLE        R2      eq_ref   PRIMARY,L        PRIMARY       156   const,db.W.RID,const      1   Using where; Distinct

Я правильно интерпретирую заключительную строку плана выполнения следующим образом:

  • поскольку он полностью совпадает с его первичным ключом, требуется только одна строка R2 для каждой строки вывода;
  • однако, такие выходные строки затем фильтруются на основе некоторых критериев, которые применяются к R2?

Если это так, моя проблема заключается в фильтрации, которая происходит на этом заключительном этапе. Если условие не приводит к фильтрации (например, WHERE `Col_1_to_3` IN (1,2,3)), запрос выполняется очень быстро (~ 50 мс); однако, если условие ограничивает выбранные строки (WHERE `Col_1_to_3` IN (1,2)), запрос занимает значительно больше времени (~ 5 с). Если ограничение на одно совпадение (WHERE `Col_1_to_3` IN (1)), оптимизатор предлагает совсем другой план выполнения (который работает чуть лучше 5 с, но все же намного хуже 50 мс). Кажется, что нет лучшего индекса, который можно использовать в этой таблице (если он уже полностью использует первичный ключ для возврата одной строки на результат?).

Как интерпретировать всю эту информацию? Имею ли я право догадываться, что, поскольку такая фильтрация выходных данных происходит в финальной таблице, которая должна быть объединена, значительные усилия тратятся впустую против присоединения к таблице раньше и фильтрации таких строк раньше? Если да, то как определить, когда в план выполнения R2 следует соединить?

В то время как я сопротивлялся, включая запрос и схему, полностью здесь (так как я действительно мог бы знать, что искать, а не просто сказать ответ), я понимаю, что необходимо заранее обсудить:

SELECT DISTINCT
    `Q`.`QID`
FROM
    `S`
    NATURAL JOIN `Q`
    NATURAL JOIN `V`
    NATURAL JOIN `R` AS `R1`
    NATURAL JOIN `W`

    JOIN `R` AS `R2` ON (
            `R2`.`SID` = `S`.`SID`
        AND `R2`.`RID` = `R1`.`RID`
        AND `R2`.`VID` = `S`.`V_id`
        AND `R2`.`Col_1_to_3` IN (1,2) -- this is where performance suffers!
    )

WHERE
    AND `S`.`SID` = @x
    AND `W`.`WID` = @y
;

Определение таблицы R:

CREATE TABLE `R` (
  `SID` smallint(6) unsigned NOT NULL,
  `RID` smallint(6) unsigned NOT NULL,
  `VID` varchar(50) NOT NULL DEFAULT '',
  `Col_1_to_3` smallint(1) DEFAULT NULL,
  `T` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`SID`,`RID`,`VID`),
  KEY `L` (`SID`,`VID`,`Col_1_to_3`),
  CONSTRAINT `R_f1` FOREIGN KEY (`SID`) REFERENCES `S` (`SID`),
  CONSTRAINT `R_f2` FOREIGN KEY (`SID`, `VID`) REFERENCES `V` (`SID`, `VID`),
  CONSTRAINT `R_f3` FOREIGN KEY (`SID`, `VID`, `Col_1_to_3`) REFERENCES `L` (`SID`, `VID`, `LID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

Ответ 1

Зависит от того, что вы собираетесь делать и что такое запрос.

Как правило, для каждой строки в EXPLAIN, которая имеет Using where, вам необходимо использовать ее с помощью столбца index (possible keys и keys). Это ваши фильтры и включают WHERE и ON. Сказав, что Using index еще лучше. Это означает, что существует индекс покрытия, и MySQL может извлекать данные прямо из индекса, а не посещать строку в данных таблицы.

Строки, в которых нет Using where, и он возвращает большое количество строк, следует посмотреть. Они возвращают значения для всех строк в таблице. Я не знаю, каков ваш запрос, поэтому я не знаю, беспокоиться ли здесь. Попробуйте фильтровать набор результатов, чтобы уменьшить размер и повысить производительность.

Обычно вам следует избегать просмотра Using filesort или Using temporary, хотя это плохо, если вы не ожидаете их.

Файлы Filesort обычно появляются с предложением ORDER. Обычно вы хотите, чтобы MySQL использовал индекс покрытия (Using index), чтобы строки возвращались уже по порядку с сервера. Если это не так, то MySQL должен их заказывать позже, используя filesort.

Using temporary может быть плохим, когда он ссылается на производные таблицы, потому что у них нет индексов. Кажется, что вы явно создали временную таблицу с индексами, поэтому здесь это неплохо. Иногда ваш единственный выбор - использовать производную таблицу и, следовательно, Using temporary.