Методы повышения производительности приложений могут происходить из разных мест, но обычно первое, на что мы обращаем внимание — самое распространенное узкое место — это база данных. Можно ли это улучшить? Как мы можем измерить и понять, что нужно и что можно улучшить?
Одним из очень простых, но очень полезных инструментов является профилирование запросов. Включение профилирования — это простой способ получить более точную оценку времени выполнения запроса. Это двухступенчатый процесс. Во-первых, мы должны включить профилирование. Затем мы вызываем 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 будет выполнять наш запрос. Он работает с explain
SELECT
DELETE
INSERT
REPLACE
Официальная документация довольно хорошо описывает, как UPDATE
С помощью EXPLAIN вы можете увидеть, куда следует добавлять индексы в таблицы, чтобы оператор выполнялся быстрее, используя индексы для поиска строк. Вы также можете использовать EXPLAIN, чтобы проверить, объединяет ли оптимизатор таблицы в оптимальном порядке.
Чтобы проиллюстрировать использование explain
explain
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
-
PRIMARY
DERIVED
-
SUBQUERY
-
UNION
Полный список значений, которые могут появиться в поле
select_type
здесь . -
-
table
-
type
Это, вероятно, самое важное поле в выводе объяснения. Это может указывать на отсутствующие индексы, а также на то, как запрос должен быть переписан. Возможные значения для этого поля следующие (упорядочены от лучшего типа к худшему):-
system
-
const
Это самый быстрый тип соединения. -
eq_ref
-
ref
Этот тип объединения обычно отображается для индексированных столбцов по сравнению с операторами=
<=>
-
fulltext
-
ref_or_null
-
index_merge
Столбец KEYexplain
-
unique_subquery
-
range
-
index
-
all
Это худший тип объединения, который часто указывает на отсутствие соответствующих индексов в таблице.
-
-
possible_keys
Эти ключи могут или не могут быть использованы на практике. -
keys
MySQL всегда ищет оптимальный ключ, который можно использовать для запроса. Присоединяясь ко многим таблицам, он может определить некоторые другие ключи, которые не перечислены вpossible_keys
-
key_len
-
ref
-
rows
Это очень важный показатель; чем меньше проверенных записей, тем лучше. -
Extra
Такие значения, какUsing filesort
Using 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
Цель должна получить лучшее значение в столбце rows
type
Наш результат по первому запросу — 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
Таким образом, мы могли бы изменить LIKE
LIKE
Подробнее о полнотекстовых индексах можно узнать здесь .
Есть также два очень интересных случая, которые мы должны рассмотреть: 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
Поскольку мы знаем, что LIMIT
ORDER 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 ALL
ORDER BY
LIMIT
Эта комбинация также используется в большинстве интерактивных приложений с большими наборами данных. Функциональные возможности, такие как недавно зарегистрированные пользователи и топ-теги, обычно используют эту комбинацию.
Поскольку это общая проблема, существует также небольшой список общих решений, которые мы должны применять для решения проблем с производительностью.
- Убедитесь, что мы используем индексы . В нашем случае
created_at
Таким образом, мы выполняемORDER BY
LIMIT
- Сортировать по столбцу в ведущей таблице . Обычно, если
ORDER BY
- Не сортируйте по выражениям . Выражения и функции не позволяют использовать индексы с помощью
ORDER BY
- Остерегайтесь большого значения
LIMIT
. Большие значенияLIMIT
ORDER BY
Это влияет на производительность.
Вот некоторые из мер, которые мы должны предпринять, когда у нас есть LIMIT
ORDER BY
Вывод
Как мы видим, explain
Есть много проблем, которые мы замечаем только тогда, когда наши приложения находятся в производстве и имеют большие объемы данных или большое количество посетителей, попадающих в базу данных. Если эти вещи можно обнаружить на ранних этапах использования explain
Наше приложение имеет все необходимые индексы, и оно довольно быстрое, но теперь мы знаем, что всегда можем прибегнуть к explain