Статьи

Повышение производительности MySQL с помощью индексов и объяснений

Методы повышения производительности приложений могут происходить из разных мест, но обычно первое, на что мы обращаем внимание — самое распространенное узкое место — это база данных. Можно ли это улучшить? Как мы можем измерить и понять, что нужно и что можно улучшить?

Одним из очень простых, но очень полезных инструментов является профилирование запросов. Включение профилирования — это простой способ получить более точную оценку времени выполнения запроса. Это двухступенчатый процесс. Во-первых, мы должны включить профилирование. Затем мы вызываем show profiles

Давайте представим, что у нас есть следующая вставка в нашей базе данных (и давайте предположим, что Пользователь 1 и Галерея 1 уже созданы):

 INSERT INTO `homestead`.`images` (`id`, `gallery_id`, `original_filename`, `filename`, `description`) VALUES
(1, 1, 'me.jpg', 'me.jpg', 'A photo of me walking down the street'),
(2, 1, 'dog.jpg', 'dog.jpg', 'A photo of my dog on the street'),
(3, 1, 'cat.jpg', 'cat.jpg', 'A photo of my cat walking down the street'),
(4, 1, 'purr.jpg', 'purr.jpg', 'A photo of my cat purring');    

Очевидно, что этот объем данных не вызовет никаких проблем, но давайте использовать его для создания простого профиля. Давайте рассмотрим следующий запрос:

 SELECT * FROM `homestead`.`images` AS i
WHERE i.description LIKE '%street%';

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

Чтобы получить точное время выполнения этого запроса, мы использовали бы следующий SQL:

 set profiling = 1;
SELECT * FROM `homestead`.`images` AS i
WHERE i.description LIKE '%street%';
show profiles;

Результат будет выглядеть следующим образом:

Query_Id продолжительность запрос
1 0.00016950 ПОКАЗАТЬ ПРЕДУПРЕЖДЕНИЯ
2 0.00039200 ВЫБРАТЬ * ИЗ homestead images
3 0.00037600 ПОКАЗАТЬ КЛЮЧИ ОТ homestead images
4 0.00034625 ПОКАЗАТЬ Базы данных, как усадьба
5 0.00027600 ПОКАЗЫВАЙТЕ СТОЛЫ ИЗ ДОМА
6 0.00024950 ВЫБРАТЬ * ИЗ homestead homestead
7 0.00104300 ПОКАЗАТЬ ПОЛНЫЕ КОЛОННЫ ИЗ images homestead

Как мы видим, images Команда дает нам время не только для исходного запроса, но и для всех других запросов. Таким образом, мы можем точно профилировать наши запросы.

Но как мы можем улучшить их?

Мы можем либо положиться на наши знания SQL и импровизировать, либо мы можем положиться на команду show profiles;

Объяснение используется для получения плана выполнения запроса или того, как MySQL будет выполнять наш запрос. Он работает с explainSELECTDELETEINSERTREPLACE Официальная документация довольно хорошо описывает, как UPDATE

С помощью EXPLAIN вы можете увидеть, куда следует добавлять индексы в таблицы, чтобы оператор выполнялся быстрее, используя индексы для поиска строк. Вы также можете использовать EXPLAIN, чтобы проверить, объединяет ли оптимизатор таблицы в оптимальном порядке.

Чтобы проиллюстрировать использование explainexplain

 UserManager.php

Чтобы использовать команду SELECT * FROM `homestead`.`users` WHERE email = '[email protected]';

 explain

Это результат (прокрутите вправо, чтобы увидеть все):

Я бы SELECT_TYPE стол перегородки тип possible_keys ключ key_len ссылка строки отфильтрованный дополнительный
1 ПРОСТО «пользователей» ЗНАЧЕНИЕ NULL ‘Const’ ‘UNIQ_1483A5E9E7927C74’ ‘UNIQ_1483A5E9E7927C74’ ‘182’ ‘Const’ 100,00 ЗНАЧЕНИЕ NULL

Эти результаты нелегко понять с первого взгляда, поэтому давайте подробнее рассмотрим каждый из них:

  • EXPLAIN SELECT * FROM `homestead`.`users` WHERE email = '[email protected]';

  • id Это поле может принимать различные значения, поэтому мы сосредоточимся на наиболее важных:

    • select_type
    • SIMPLE
    • PRIMARYDERIVED
    • SUBQUERY
    • UNION

    Полный список значений, которые могут появиться в поле select_typeздесь .

  • table

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

    • system
    • const Это самый быстрый тип соединения.
    • eq_ref
    • ref Этот тип объединения обычно отображается для индексированных столбцов по сравнению с операторами =<=>
    • fulltext
    • ref_or_null
    • index_merge Столбец KEY explain
    • unique_subquery
    • range
    • index
    • all Это худший тип объединения, который часто указывает на отсутствие соответствующих индексов в таблице.
  • possible_keys Эти ключи могут или не могут быть использованы на практике.

  • keys MySQL всегда ищет оптимальный ключ, который можно использовать для запроса. Присоединяясь ко многим таблицам, он может определить некоторые другие ключи, которые не перечислены в possible_keys

  • key_len

  • ref

  • rows Это очень важный показатель; чем меньше проверенных записей, тем лучше.

  • Extra Такие значения, как Using filesortUsing temporary

Полная документация по выходному формату explainна официальной странице MySQL .

Возвращаясь к нашему простому запросу: это простой тип выбора с постоянным типом соединения. Это лучший случай запроса, который мы можем иметь. Но что происходит, когда нам нужны более крупные и сложные запросы?

Возвращаясь к нашей схеме приложения, мы можем получить все изображения галереи. Мы также могли бы хотеть иметь только фотографии, которые содержат слово «кошка» в описании. Это определенно тот случай, который мы могли бы найти в требованиях проекта. Давайте посмотрим на запрос:

 SIMPLE

В этом более сложном случае мы должны иметь больше информации для анализа нашего SELECT gal.name, gal.description, img.filename, img.description FROM `homestead`.`users` AS users
LEFT JOIN `homestead`.`galleries` AS gal ON users.id = gal.user_id
LEFT JOIN `homestead`.`images` AS img on img.gallery_id = gal.id
WHERE img.description LIKE '%dog%';

 explain

Это дает следующие результаты (прокрутите вправо, чтобы увидеть все ячейки):

Я бы SELECT_TYPE стол перегородки тип possible_keys ключ key_len ссылка строки отфильтрованный дополнительный
1 ПРОСТО «пользователей» ЗНАЧЕНИЕ NULL ‘показатель’ ‘PRIMARY, UNIQ_1483A5E9BF396750’ ‘UNIQ_1483A5E9BF396750’ ‘108’ ЗНАЧЕНИЕ NULL 100,00 «Использование индекса»
1 ПРОСТО «Гал» ЗНАЧЕНИЕ NULL «Ссылка» ‘PRIMARY, UNIQ_F70E6EB7BF396750, IDX_F70E6EB7A76ED395’ ‘UNIQ_1483A5E9BF396750’ ‘108’ ‘Homestead.users.id’ 100,00 ЗНАЧЕНИЕ NULL
1 ПРОСТО ‘IMG’ ЗНАЧЕНИЕ NULL «Ссылка» ‘IDX_E01FBE6A4E7AF8F’ ‘IDX_E01FBE6A4E7AF8F’ ‘109’ ‘Homestead.gal.id’ ’25 .00′ «Использование где»

Давайте внимательнее посмотрим, что мы можем улучшить в нашем запросе.

Как мы видели ранее, основными столбцами, на которые мы должны обратить внимание вначале, являются столбец EXPLAIN SELECT gal.name, gal.description, img.filename, img.description FROM `homestead`.`users` AS users
LEFT JOIN `homestead`.`galleries` AS gal ON users.id = gal.user_id
LEFT JOIN `homestead`.`images` AS img on img.gallery_id = gal.id
WHERE img.description LIKE '%dog%';
type
Цель должна получить лучшее значение в столбце rowstype

Наш результат по первому запросу — rows Это означает, что мы, вероятно, можем улучшить его.

Глядя на наш запрос, есть два пути к нему. Во-первых, таблица index Мы либо расширяем запрос, чтобы убедиться, что мы нацеливаемся на пользователей, либо мы должны полностью удалить Users Это только добавляет сложности и времени к нашей общей производительности.

 users

Так что теперь у нас точно такой же результат. Давайте посмотрим на SELECT gal.name, gal.description, img.filename, img.description FROM `homestead`.`galleries` AS gal
LEFT JOIN `homestead`.`images` AS img on img.gallery_id = gal.id
WHERE img.description LIKE '%dog%';

Я бы SELECT_TYPE стол перегородки тип possible_keys ключ key_len ссылка строки отфильтрованный дополнительный
1 ПРОСТО «Гал» ЗНАЧЕНИЕ NULL ‘ВСЕ’ ‘PRIMARY, UNIQ_1483A5E9BF396750’ ЗНАЧЕНИЕ NULL ЗНАЧЕНИЕ NULL ЗНАЧЕНИЕ NULL 100,00 ЗНАЧЕНИЕ NULL
1 ПРОСТО ‘IMG’ ЗНАЧЕНИЕ NULL «Ссылка» ‘IDX_E01FBE6A4E7AF8F’ ‘IDX_E01FBE6A4E7AF8F’ ‘109’ ‘Homestead.gal.id’ ’25 .00′ «Использование где»

Мы остались с типом explain Хотя ALL В соответствии с нашими требованиями нам нужны все изображения галерей, поэтому нам нужно просмотреть всю таблицу галерей. Хотя индексы действительно хороши при поиске конкретной информации в таблице, они не могут помочь нам, когда нам нужна вся информация в ней. Когда у нас есть такой случай, мы должны прибегнуть к другому методу, например, кешированию.

Последнее улучшение, которое мы можем внести, поскольку мы имеем дело с ALL Таким образом, мы могли бы изменить LIKELIKE Подробнее о полнотекстовых индексах можно узнать здесь .

Есть также два очень интересных случая, которые мы должны рассмотреть: match()newest Они относятся к галереям и касаются некоторых угловых случаев, о которых нам следует знать:

 related

Выше для связанных галерей.

 EXPLAIN SELECT * FROM `homestead`.`galleries` AS gal
LEFT JOIN `homestead`.`users` AS u ON u.id = gal.user_id
WHERE u.id = 1
ORDER BY gal.created_at DESC
LIMIT 5;

Выше для новейших галерей.

На первый взгляд, эти запросы должны быть быстрыми, потому что они используют EXPLAIN SELECT * FROM `homestead`.`galleries` AS gal
ORDER BY gal.created_at DESC
LIMIT 5;
И это относится к большинству запросов, использующих LIMIT К сожалению для нас и нашего приложения, эти запросы также используют LIMIT Поскольку нам нужно упорядочить все результаты, прежде чем ограничивать запрос, мы теряем преимущества использования ORDER BY

Поскольку мы знаем, что LIMITORDER BY

Я бы SELECT_TYPE стол перегородки тип possible_keys ключ key_len ссылка строки отфильтрованный дополнительный
1 ПРОСТО «Гал» ЗНАЧЕНИЕ NULL ‘ВСЕ’ ‘IDX_F70E6EB7A76ED395’ ЗНАЧЕНИЕ NULL ЗНАЧЕНИЕ NULL ЗНАЧЕНИЕ NULL 100,00 «Используя где; Использование filesort ‘
1 ПРОСТО «И» ЗНАЧЕНИЕ NULL ‘Eq_ref’ ‘PRIMARY, UNIQ_1483A5E9BF396750’ «PRIMARY ‘108’ ‘Homestead.gal.id’ «100,00» ЗНАЧЕНИЕ NULL

И,

Я бы SELECT_TYPE стол перегородки тип possible_keys ключ key_len ссылка строки отфильтрованный дополнительный
1 ПРОСТО «Гал» ЗНАЧЕНИЕ NULL ‘ВСЕ’ ЗНАЧЕНИЕ NULL ЗНАЧЕНИЕ NULL ЗНАЧЕНИЕ NULL ЗНАЧЕНИЕ NULL 100,00 «Использование файловой сортировки»

Как мы видим, у нас наихудший случай типа соединения: explain

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

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

  • Убедитесь, что мы используем индексы . В нашем случае created_at Таким образом, мы выполняем ORDER BYLIMIT
  • Сортировать по столбцу в ведущей таблице . Обычно, если ORDER BY
  • Не сортируйте по выражениям . Выражения и функции не позволяют использовать индексы с помощью ORDER BY
  • Остерегайтесь большого значения LIMIT . Большие значения LIMITORDER BY Это влияет на производительность.

Вот некоторые из мер, которые мы должны предпринять, когда у нас есть LIMITORDER BY

Вывод

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

Наше приложение имеет все необходимые индексы, и оно довольно быстрое, но теперь мы знаем, что всегда можем прибегнуть к explain