Операции с базой данных очень часто становятся узким
местом при реализации веб проекта. Вопросы оптимизации в таких случаях
касаются не только администратора базы данных. Программистам нужно
правильно выполнять структурирование таблиц, писать оптимальные запросы и
более производительный код. В данной статье приводится небольшой список
техник оптимизации работы с MySQL для программистов.
1. Оптимизируйте ваши запросы для кэша запросов.
Большинство серверов MySQL используют кэширование
запросов. Это один из эффективных методов улучшения производительности,
который выполняется механизмом базы данных в фоновом режиме. Если
запрос выполняется много раз, то для получения результата начинает
использоваться кэш и операция выполняется значительно быстрее.
Проблема заключается в том, что это так просто и в то
же время скрыто от разработчика, и большинство программистов игнорирует
такую прекрасную возможность улучшить производительность проекта.
Некоторые действия в действительности могут создавать препятствия для
использования кэша запросов при выполнении.
2 |
$r = mysql_query( "SELECT username FROM user WHERE signup_date >= CURDATE()" ); |
5 |
$today = date ( "Y-m-d" ); |
6 |
$r = mysql_query( "SELECT username FROM user WHERE signup_date >= '$today'" ); |
Причина того, что кэш запросов не работает в первом случае, заключается в использовании функции CURDATE().
Такой подход используется для всех недетерминированных функций,
например, NOW(), RAND() и т.д. Так как возвращаемый результат функции
может измениться, то MySQL решает не размещать данный запрос в кэше. Все
что, нужно, чтобы исправить ситуацию - это добавить дополнительную
строчку кода PHP перед запросом.
2. Используйте EXPLAIN для ваших запросов SELECT
Использование ключевого слова EXPLAIN
может помочь составить картину того, что делает MySQL для выполнения
вашего запроса. Такая картина позволяет легко выявить узкие места и
другие проблемы в запросах или структуре таблиц.
Результат запроса EXPLAIN показывает, какие индексы используются, как таблица сканируется и сортируется, и так далее.
Возьмем запрос SELECT (предпочтительно, чтобы он был
сложным, с JOIN), добавим перед ним ключевое слово EXPLAIN. Вы можете
использовать PhpMyAdmin для этого. Такой запрос выведет результат в
прекрасную таблицу. Допустим, мы забыли добавить индекс для столбца,
который используется для JOIN:
После добавления индекса для поля group_id:
Теперь вместо сканирования 7883 строк, будут
сканироваться только 9 и 16 строк из двух таблиц. Хорошим методом оценки
производительности является умножение всех чисел в столбце “rows”.
Результат примерно пропорционален прорабатываемому объему данных.
3. Используйте LIMIT 1, если нужно получить уникальную строку
Иногда, во время использования запроса, вы уже
знаете, что ищете только одну строку. Вы можете получить уникальную
запись или просто проверить существование любого количества записей,
которые удовлетворяют предложению WHERE.
В таком случае добавление LIMIT 1 к вашему запросу
может улучшить производительность. При таком условии механизм базы
данных останавливает сканирование записей как только найдет одну и не
будет проходит по всей таблице или индексу.
04 |
$r = mysql_query( "SELECT * FROM user WHERE state = 'Alabama'" ); |
05 |
if (mysql_num_rows( $r ) > 0) { |
10 |
$r = mysql_query( "SELECT 1 FROM user WHERE state = 'Alabama' LIMIT 1" ); |
11 |
if (mysql_num_rows( $r ) > 0) { |
4. Индексируйте поля поиска
Индексируйте не только основные и уникальные ключи.
Если какие-нибудь столбцы в вашей таблице используются для поисковых
запросов, то их нужно индексировать.
Как вы можете видеть, данное правило применимо и к
поиску по части строки, например, “last_name LIKE ‘a%’”. Когда для
поиска используется начало строки, MySQL может использовать индекс
столбца, по которому проводится поиск.
Вам также следует разобраться, для каких видов
поиска нельзя использовать обычное индексирование. Например, при поиске
слова ( “WHERE post_content LIKE ‘%apple%’”) преимущества
индексирования будут не доступны. В таких случая лучше использовать полнотекстовый поиск mysql или построение собственных решений на основе индексирования.
5. Индексирование и использование одинаковых типов для связываемых столбцов
Если ваше приложение содержит много запросов с
директивой JOIN, вам нужно индексировать столбцы, которые связываются в
обеих таблицах. Это оказывает эффект на внутреннюю оптимизацию операций
связывания в MySQL.
Также связываемые столбцы должны иметь одинаковый
тип. Например, если вы связываете столбец DECIMAL со столбцом INT из
другой таблицы, MySQL не сможет использовать индекс по крайней мере для
одной из одной таблицы. Даже кодировка символов должна быть одинаковой
для одинаковых столбцов строчного типа.
2 |
$r = mysql_query("SELECT company_name FROM users |
3 |
LEFT JOIN companies ON (users.state = companies.state) |
4 |
WHERE users.id = $user_id "); |
6. Не используйте ORDER BY RAND()
Это один их тех трюков, которые круто выглядят, и
многие начинающие программисты попадают в его ловушку. Они даже
представить не могут, какую ужасную проблему сами себе создают, начав
использовать это выражение в своих запросах.
Если вам действительно нужно случайным образом
располагать строки в результате вашего запроса, то существует множество
лучших способов решить такую задачу. Конечно, это будет реализовано
дополнительным кодом, но вы будете спасены от проблемы, которая растет
по экспоненциальному закону вместе с ростом объема данных. Дело в том,
что MySQL выполняет операцию RAND() (которая занимает время процессора)
для каждой отдельной строки в таблице перед тем, как отсортировать ее и
выдать вам только одну строку.
02 |
$r = mysql_query( "SELECT username FROM user ORDER BY RAND() LIMIT 1" ); |
06 |
$r = mysql_query( "SELECT count(*) FROM user" ); |
07 |
$d = mysql_fetch_row( $r ); |
08 |
$rand = mt_rand(0, $d [0] - 1); |
10 |
$r = mysql_query( "SELECT username FROM user LIMIT $rand, 1" ); |
Так вы получаете случайное число, которое меньше, чем
количество строк в результате запроса, и используете его как смещение в
предложении LIMIT.
7. Старайтесь не использовать SELECT *
Чем больше данных будет прочитано из таблицы, тем
медленнее выполняется запрос. Такие операции также занимают время для
выполнения дисковых операций. А если сервер базу данных отделен от веб
сервера, то задержки будут вызваны еще и передачей данных по сети между
серверами.
Хорошей привычкой является указание столбца при выполнении SELECT.
02 |
$r = mysql_query( "SELECT * FROM user WHERE user_id = 1" ); |
03 |
$d = mysql_fetch_assoc( $r ); |
04 |
echo "Welcome {$d['username']}" ; |
07 |
$r = mysql_query( "SELECT username FROM user WHERE user_id = 1" ); |
08 |
$d = mysql_fetch_assoc( $r ); |
09 |
echo "Welcome {$d['username']}" ; |
8. Старайтесь использовать поле id везде
Хорошей практикой является использование в каждой
таблице поля id, для которого установлены свойства PRIMARY KEY,
AUTO_INCREMENT, и оно имеет тип из семейства INT. Предпочтительно -
UNSIGNED, так как в этом случае значение не может быть отрицательным.
Даже если в вашей таблице есть поле с уникальным
именем пользователя, не делайте его основным ключом. Поля с типом
VARCHAR медленно работают в качестве основных ключей. Также структура
вашей базы данных будет лучше, если в ней внутри использовать ссылки на
записи на основании id.
Кроме того механизм MySQL использует основные ключи
для своих внутренних задач, и использование поля id создает оптимальные
условия для их решения.
Одним возможным исключением из данного правила
являются “ассоциативные таблицы”, которые используются для отношений
многие-ко-многим между двумя другими таблицами. Например, таблица
“posts_tags” содержит 2 столбца: post_id, tag_id. Они используются для
описания отношений между двумя таблицами “post” и “tags”. Описанная
таблица может иметь основной ключ, который содержит оба поля id.
9. Используйте ENUM вместо VARCHAR
Столбцы типа ENUM очень
компактные и быстрые. Они хранятся в базе данных как и TINYINT, но еще
они могут содержать строчные значения. Такие особенности делают их
отличными кандидатами для реализации определенных полей.
Если у вас есть поля, которые содержат только
несколько различных видов значений, используйте для них ENUM вместо
VARCHAR. Например, может быть столбец с именем “status”, который будет
содержать только такие значения как “active”, “inactive”, “pending”,
“expired” и так далее.
MySQL может “предложить” способ изменения структуры
вашей таблицы. Когда вы создаете поле VARCHAR, то наверняка
"предложение" будет содержать рекомендацию сменить тип столбца на ENUM.
"Предложения" получаются в ходе выполнения вызова PROCEDURE ANALYSE().
10. Изучите предложения PROCEDURE ANALYSE()
PROCEDURE ANALYSE()
позволяет MySQL анализировать структуру столбцов и действительных
данных в вашей таблице и на основании анализа выдавать "предложения".
Это действует только если в вашей таблице есть реальные данные, так как
их наличие играет существенную роль при принятии решений.
Например, если вы создали поле типа INT для основного
ключа, но в таблице не так много записей, то "предложение" может
содержать рекомендацию сменить тип поля на MEDIUMINT. Или если вы
используете поле типа VARCHAR, то можете получить "предложение"
конвертировать его в ENUM, если в нем содержится только несколько
значений.
Вы также можете получить рекомендации, если нажмете
ссылку “Propose table structure” (Анализ структуры таблицы) в PhpMyAdmin
на закладке структуры таблицы.
Нужно только помнить, что это всего лишь предложения.
И если ваша таблица будет расти, то они могут оказаться неверными. Так
что решение об их применении остается за вами.
11. Используйте NOT NULL, если это возможно
Если нет особых причин использовать значение NULL, нужно всегда использовать для столбца свойство NOT NULL.
Спросите себя, есть ли разница между пустой строкой и
значением NULL (для полей типа INT: 0 и NULL). Если нет причин
использовать оба значения, то нет необходимости иметь поле NULL. (Вы
знаете, что Oracle рассматривает NULL и пустую строку как одинаковые
величины?)
Столбец NULL требует дополнительного пространства и
может быть источником затруднений для выражений сравнений. Просто
избегайте использовать его по мере возможности. Хотя, конечно, есть
объективные причины для использования значений NULL в некоторых случаях.