оригинал
Работа с базой данных зачастую самое слабое место в производительности многих web приложений. И об этом должны заботиться не только администраторы баз данных. Программисты должны выбирать правильную структуру таблиц, писать оптимизированные запросы и хороший код. Далее перечислены методы оптимизации работы с MySQL для программистов.
1. Оптимизируйте запросы для кэша запросов
У большинства MySQL серверов включено кэширование запросов. Один из наилучших способов улучшения производительности — просто предоставить кэширование самой базе данных. Когда какой-либо запрос повторяется много раз, его результат берется из кэша, что гораздо быстрее прямого обращения к базе данных.
Основная проблема в том, что многие просто используют запросы, которые не могут быть закэшированны:
// запрос не будет кэширован $r = mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()"); // а так будет! $today = date("Y-m-d"); $r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'");
Причина в том, что в первом запросе используется функция CURDATE(). Это относиться ко всем функциям, подобным NOW(), RAND() и другим, результат которых недетерминирован. Если результат функции может измениться, то MySQL не кэширует такой запрос. В данном примере это можно предотвратить вычислением даты до выполнения запроса.
2. Используйте EXPLAIN для ваших запросов SELECT
Используя EXPLAIN, вы можете посмотреть, как именно MySQL выполняет ваш запрос. Это может помочь вам избавиться от слабых мест производительности и других проблем в вашем запросе или в структуре таблиц.
Результат EXPLAIN покажет вам, какие используются индексы, как выбираются и сортируются таблицы и т.д.
Возьмите ваш SELECT запрос (он может быть сложным, с объединениями) и добавьте в начало ключевое слово EXPLAIN. Для этого вы можете использовать phpmyadmin. В результате вы получите очень интересную таблицу. Для примера, пусть я забыл добавить индекс в таблицу, которая участвует в объединении:

После добавления индекса для поля group_id:

Теперь вместо 7883 строк, выбираются только 9 и 16 строк из двух таблиц. Перемножение всех чисел в столбце rows даст число прямо пропорциональное производительности запроса.
3. LIMIT 1, когда нужна единственная строка
Иногда, обращаясь к таблице, вы точно знаете, что вам нужна только одна конкретная строка. Например, нужно получить одну уникальную строку или просто проверить существование записей, удовлетворяющих запросу WHERE.
В этом случае, добавление LIMIT 1 в ваш запрос будет оптимальнее. Таким образом, база данных остановит выборку записей, после нахождения первой же, вместо того, чтобы выбрать всю таблицу или индекс.
// есть пользователи в Alabama? // можно так: $r = mysql_query("SELECT * FROM user WHERE state = 'Alabama'"); if (mysql_num_rows($r) > 0) { // ... } // но так лучше: $r = mysql_query("SELECT * FROM user WHERE state = 'Alabama' LIMIT 1"); if (mysql_num_rows($r) > 0) { // ... }
4. Индексируйте поля, по которым ищите
Индекс это не только основной или уникальный ключ. Это так же любые столбцы в таблице, которые вы используете для поиска и их можно проиндексировать.

Как вы можете заметить, это правило также применимо для части строк, например — «last_name LIKE 'a%'». При поиске с начала строки, MySQL использует индекс этого столбца.
Вы так же должны понимать, что это не сработает для регулярных выражений. Например, когда вы ищите слово (т.е. «WHERE post_content LIKE '%apple%'»), то от обычного индекса не будет никакого толку. Лучше будет использовать полнотекстовый поиск или создать вашу собственную систему индексации.
5. Индексируйте поля для объединения и используйте для них одинаковые типы столбцов
Если ваше приложение содержит много объединений таблиц, вам необходимо проиндексировать в обеих таблицах поля, используемые для объединения. Это повлияет на то, как MySQL делает внутреннюю оптимизацию объединений.
Так же эти столбцы должны быть одного типа. Например, если вы объединяете столбец DECIMAL со столбцом INT из другой таблицы, MySQL не сможет использовать хотя бы один из индексов. Даже кодировки символов должны быть одного типа для строковых столбцов.
// выборки компаний в штате пользователя $r = mysql_query("SELECT company_name FROM users JOIN companies ON (users.state = companies.state) users.id = $user_id"); // обе колонки state должны быть проиндексированны // они обе должны иметь один тип данных и кодировку символов // а иначе MySQL сделает полную выборку из этих таблиц
6. Не используйте ORDER BY RAND()
(Имеется в виду выборка единственной строки. Примечание переводчика)
Это одна из тех вещей, который выглядят очень хорошо на первый взгляд, но многие начинающие программисты попались на эту удочку. Вы даже не представляете, какое слабое место в производительности возникнет, если будете использовать это в запросах.
Если вам действительно нужен случайный порядок строк в запросе, то есть лучшие способы сделать это. Конечно, это приведет к дополнительному коду, но позволит избавиться от слабого места в производительности, которое будет сужаться экспоненциально при увеличении данных. Проблема в том, что MySQL будет выполнять RAND() (а это нагрузка на процессор) для каждой строки при сортировке, выдавая только одну строку.
Таким образом вы выберите случайный номер, который меньше количества строк и используете его для смещения в LIMIT.
7. Избегайте SELECT *
Чем больше данных считывается из таблицы, тем медленнее запрос. Это увеличивает время работы с хранилищем данных. Также, когда сервер базы данных установлен отдельно от web-сервера, будет большая задержка при передаче данных по сети.
Прописывать, какие именно столбцы из запроса вам нужны — хорошая привычка.
// не очень хорошо: $r = mysql_query(«SELECT * FROM user WHERE user_id = 1»); $d = mysql_fetch_assoc($r); echo «Welcome {$d['username']}»; // лучше: $r = mysql_query(«SELECT username FROM user WHERE user_id = 1»); $d = mysql_fetch_assoc($r); echo «Welcome {$d['username']}»; // разница более значительна при большем наборе данных.
8. Старайтесь всегда создать поле ID
В каждой таблице нужно поле id, которое будет PRIMARY KEY, AUTO_INCREMENT, а так же иметь тип INT. Так же неплохо, чтобы оно было UNSIGNED, т.к. вряд ли у идентификатора будут отрицательные значения.
Даже если в вашей таблице пользователей есть уникальное поле username, не делаете его основным ключом. Использование поля VARCHAR, как основного ключа, очень медлительно. Да и структура вашего кода, относящаяся к пользователям, будет гораздо лучше, если у каждого пользователя будет свой внутренний идентификатор.
Есть так же и внутренние операции MySQL, использующие первичный ключ. И это становиться очень важно для более сложных конфигураций базы данных (кластеры, распараллеливание и т.д.)
Исключение из этого правила составляют «таблицы ассоциаций», используемые для связи «многие-ко-многим» между 2 таблицами. Например, таблица «posts_tags», содержит 2 поля: post_id, tag_id, который используется для объединения между двумя таблицами «Posts» и «Tags». Эта таблица будет иметь первичный ключ составленный из 2 полей.
9. Используйте ENUM вместо VARCHAR
ENUM — очень быстрый и компактный тип поля. Значения в нем храниться так же, как TINYINT, но отображаются как в строковом поле. Это делает его незаменимым в некоторых случаях.
Если у вас есть поле, в котором будет вполне определенный набор значений, используйте ENUM вместо VARCHAR. Например, если есть поле «status», его значения могут быть «active», «inactive», «pending», «expired» и т.д.
Можно даже получить от MySQL «совет» о том, как перестроить таблицу. Если у вас есть поле VARCHAR, MySQL может предложить заменить его на ENUM. Для этого используется PROCEDURE ANALYSE(), описанная ниже.
10. Используйте подсказки от PROCEDURE ANALYSE()
PROCEDURE ANALYSE() анализирует структуру вашей таблицы и данные в ней, и выдает возможные советы по оптимизации. Это возможно только при наличии реальных данных в таблице, т.к. анализ делается в основном на их основе.
Например, если вы создали первичный ключ типа INT, а записей не очень много, MySQL может предложить заменить его на MEDIUMINT. Или, если используется VARCHAR в котором есть несколько уникальных значений, будет предложен ENUM.
В phpmyadmin в структуре таблице есть ссылка «Анализ структуры таблицы», результат которой может быть, например, следующим:

Учтите, что это только советы. Если вы добавите еще записей, они могут стать не актуальными. В конечном итоге вам решать — использовать их или нет.


Очень интересная статья вышла! Молодцом автор! :)
Мда PROCEDURE ANALYSE() полезная штукень, пасиб за статью
По поводу пункта: 3. LIMIT 1, когда нужна единственная строка.
Если запрос типа: $r = mysql_query("SELECT * FROM user WHERE id = " . (int)$_GET['id']); - тогда же не нужно ставить LIMIT 1, ведь id уникальный ключ и дальше выбирать все равно не будет, так?
@Alexey, для уникального ключа - не будет, но в примере приведено простое поле
добрый день!
статья гуд.
хотел добавить, считаеться правилом хорошого тона создавать поля в таблице по формату-tablename_pole, и выборку делать не указываю только имя таблици, а - бд.имя таблици(взято с mssql)
А что означает параметр Std при выполнении PROCEDURE ANALYSE() ???
Большое спасибо за советы. Мне, как начинающему, многое было отсюда неведомо.
Теперь буду писать более оптимизированные запросы к базам)
По поводу:
-===============-
По поводу пункта: 3. LIMIT 1, когда нужна единственная строка.
Если запрос типа: $r = mysql_query("SELECT * FROM user WHERE id = " . (int)$_GET['id']); - тогда же не нужно ставить LIMIT 1, ведь id уникальный ключ и дальше выбирать все равно не будет, так?
-===============-
В данном случае есть нюансы. Зачастую разработкой занимается группа людей, причем запросы часто формируются динамически по определенной поставленной разработчиком логике.
Представьте случай, когда кто-либо из команды в случае срочной правки не разобрался как следует в коде и допустил ошибку, при какой WHERE не прописуется либо имеет при любых обстоятельствах значение true (к примеру вариант "WHERE 1=1"). В данном случае LIMIT Спасет от вытяжки всей таблицы, и в случае очень нагруженной системы может даже спасти от отказа сервера (к примеру вариант "To many connections")
"читаеться правилом хорошого тона создавать поля в таблице по формату-tablename_pole" - самое дурацкое правило "хорошего тона" из тех, которые я видел.
Особенно, когда используя его, говнокодеры первичный ключ называют <имя таблицы="">_ID, а вторичные с тем же именем.
Если за правило хорошего тона принять соглашение как а примере:
Таблица USER
===========
ID - первичный ключ
NAME
...
Таблица ANYDATA
===========
ID - первичный ключ
USER_ID - вторичный ключ, связанный с таблицей USER
ANYFIELD
...
При таком соглашении всегда понятно где первичный, а где вторичный. Да и обращаться к полю ANYDATA.USER_ID как-то более правильно, чем к ANYDATA_USER_ID.
@cava150, @Василий - первый вариант часто используется для MSSQL, второй для MySQL. скорее всего это просто исторически сложилось. спорить тут можно так же, как и о вкусе фломастеров :)