Выполнение запросов без кэша
После оператора SELECT
добавляется спецификатор SQL_NO_CACHE
.
SELECT SQL_NO_CACHE ...
Причиной выполнять запросы “мимо” кэша имеет смысл при профилировании и отладке запросов, когда попадание в кэш может сильно “наврать” в смысле времени выполнения запроса.
Кэш автоматически очищается при любых изменениях таблицы, поэтому важным оказывается, как правило, именно время выполнения запроса без кэша.
Стоимость запроса
SESSION STATUS LIKE 'last_query_cost'; SHOW
Как правило, из всех возможных вариантов запроса, оптимизатор выбирает запрос с минимальной стоимостью, и выполняет его.
Однако, оптимизатор может не всегда корректно это делать.
Причинами могут быть
- Некорректная статистика
- Некорректная метрика стоимости
- Наличие других одновременно выполняющихся запросов
- Не всегда возможна оценка стоимости. В таких случаях, оптимизатор просто следует эвристическим правилам
- Не учитывается стоимость неподконтрольных оптимизатору операций, скажем, выполнение пользовательских функций
- Не всегда рассматриваются все возможные планы выполнения
Конечно, над кодом оптимизатора постоянно ведется работа, и, как правило, в каждой следующей версии он становится “умнее”. Однако, не следует считать оптимизатор “серебрянной пулей”.
Профилирование (продолжительность запроса)
В начале сессии пишется
SET @@profiling = 1;
В конце используется SHOW PROFILES
.
Для очистки истории, может использоваться конструкция
SET @@profiling = 0;
SET @@profiling_history_size = 0;
SET @@profiling_history_size = 100;
SET @@profiling = 1;
Пример:
Способы оптимизации, применяемые оптимизатором
Изменение порядка соединения
Применение правил алгебраической эквивалентности
Оптимизации
COUNT
,MIN
,MAX
Свертка константных выражений и распространение равенства Например,
sql SELECT SQL_NO_CACHE * FROM reserves r JOIN tariffs t USING (idCourt);
приведет к полному сканированию таблицы и полному сканированию индексов. С другой стороны,sql SELECT SQL_NO_CACHE * FROM reserves r JOIN tariffs t USING (idCourt) WHERE idCourt=1;
сводится к двум выборам по индексированным полям:sql SELECT * FROM (SELECT * FROM reserves WHERE idCourt=1) AS r, (SELECT * FROM tariffs WHERE idCourt=1) as t;
Покрывающие индексы Если индекс содержит все необходимые столбцы, то MySQL может просто использовать индексы, вообще не трогая данные таблицы
Оптимизация подзапросов Подзапросы могут быть преобразованы к эквивалентной форме и сведены к выборке по индексу
Раннее завершение Если оптимизатор обнаруживает, что какое-то условие не может выполняться, то вся конструкция по этому условию выкидывается из запроса. Например,
SELECT sql_no_cache * FROM join tariffs t using (idCourt) reserves r WHERE = -1; idCourt
завершится сразу после чтения определения таблиц, обнаружив, что
idCourt
имеет типUNSIGNED INT
.Сравнение по списку IN IN в MySQL реализован с помощью сортированного двоичного поиска, и оказывается часто гораздо более эффективен, чем композиция с помощью
OR
.
Способы влияния на работу оптимизатора
STRAIGHT_JOIN
Может использоваться вместо JOIN
(но USING
не работает), либо в качестве спецификтора после SELECT
, тогда все JOIN
работают как STRAIGHT_JOIN
.
Эффективно заставляет оптимизатор сначала прочитать левую таблицу в соединении, а только потом правую.
Хинты индексов
В выражении SELECT
любая таблица может быть указана со спецификторами индексов, которые следует использовать для выборки или соединения.
Синтаксис:
AS] alias] [index_hint_list]
tbl_name [[
index_hint_list:...
index_hint [, index_hint]
index_hint:USE {INDEX|KEY}
FOR {JOIN|ORDER BY|GROUP BY}] ([index_list])
[INDEX|KEY}
| IGNORE {FOR {JOIN|ORDER BY|GROUP BY}] (index_list)
[FORCE {INDEX|KEY}
| FOR {JOIN|ORDER BY|GROUP BY}] (index_list)
[
index_list:... index_name [, index_name]
Например:
SELECT sql_no_cache
*
FROM
force index (PRIMARY) on t.idCourt = r.idCourt;
reserves r straight_join tariffs t -- reserves r join tariffs t on t.idCourt = r.idCourt;
во многих случаях выполнится быстрее, чем
SELECT sql_no_cache
*
FROM
join tariffs t on t.idCourt = r.idCourt; reserves r
поскольку во втором случае оптимизатор будет склонен использовать полный просмотр таблицы для минимизации операций сравнения, в то время как для минимизации времени выполнения более эффективным оказывается выборка по индексу.
Оптимизация COUNT
Выборка COUNT(*)
без условий, как правило, гораздо быстрее, чем выборка по условию. Так же, чем больший объем данных выбирается по условию, тем медленнее работает запрос.
Как следствие, иногда осмысленно оказывается переформулировать запрос, чтобы минимизировать проверку условия. Например:
select sql_no_cache count(*) from world.City where id > 5;
Можно переформулировать как
select sql_no_cache (select count(*) from world.City) - count(*) from world.City where id <= 5;
Это дает прирост производительности по времени около 15% на таблицах InnoDB и еще больше на таблицах MyISAM.
Правила построения индексов
- Всегда стройте индексы по столбцам, которые используются в
ON
,USING
иWHERE
- Учитывайте порядок соединения. Первая таблица в соединении не использует индексы! Учитывайте работу оптимизатора.
- Старайтесь, чтобы в
GROUP BY
иORDER BY
встречались столбцы из одной таблицы. Составляйте индексы по ним.
Насчет ORDER BY в запросах с соединениями
Для ORDER BY важно, чтобы таблица, по которой будет производиться сортировка, в JOIN была на первом месте. Однако оптимизатор может переставлять таблицы в удобном ему порядке (и не всегда правильном). В таких случаях следует использовать STRAIGHT_JOIN
.
В качестве примера, из одного популярного форумного движка:
SELECT t.*, p.*, u.username
FROM topics as t, posts as p, users as u
WHERE t.topic_replies>0 AND p.poster_id=u.user_id AND topic_first_post_id<>p.post_id AND topic_approved=1 AND p.topic_id=t.topic_id AND t.forum_id='16' AND p.post_id<'244103'
ORDER by post_id desc LIMIT 40
преобразованный к
SELECT STRAIGHT_JOIN t.*, p.*, u.username
FROM phpbb3_posts as p, phpbb3_topics as t, phpbb3_users as u
WHERE t.topic_replies>0 AND p.poster_id=u.user_id AND topic_first_post_id<>p.post_id AND topic_approved=1 AND p.topic_id=t.topic_id AND t.forum_id='13' AND p.post_id<'234103'
ORDER by post_id desc LIMIT 40
на больших таблицах дает выигрыш в несколько сотен раз.
Это не значит, что всегда нужно использовать STRAIGHT_JOIN
. Но иногда, при обнаружении узких мест, он необходим.