Сайт о телевидении

Сайт о телевидении

» » Как сделать запрос в запросе mysql. Оптимизация MySQL запросов

Как сделать запрос в запросе mysql. Оптимизация MySQL запросов

Операции с базой данных очень часто становятся узким местом при реализации веб проекта. Программистам нужно правильно выполнять структурирование таблиц, писать оптимальные запросы и более производительный код. В данной статье приводится небольшой список техник оптимизации работы с 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 решает не размещать данный запрос в кэше. Все что, нужно, чтобы исправить ситуацию - это добавить дополнительную строчку кода PHP перед запросом.

2. Используйте EXPLAIN для ваших запросов SELECT

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

Результат запроса EXPLAIN показывает, какие индексы используются, как таблица сканируется и сортируется, и так далее.

Возьмем запрос SELECT (предпочтительно, чтобы он был сложным, с JOIN), добавим перед ним ключевое слово EXPLAIN. Вы можете использовать PhpMyAdmin для этого. Такой запрос выведет результат в прекрасную таблицу. Допустим, мы забыли добавить индекс для столбца, который используется для JOIN:

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

Теперь вместо сканирования 7883 строк, будут сканироваться только 9 и 16 строк из двух таблиц. Хорошим методом оценки производительности является умножение всех чисел в столбце “rows”. Результат примерно пропорционален прорабатываемому объему данных.

3. Используйте LIMIT 1, если нужно получить уникальную строку

Иногда, во время использования запроса, вы уже знаете, что ищете только одну строку. Вы можете получить уникальную запись или просто проверить существование любого количества записей, которые удовлетворяют предложению WHERE.

В таком случае добавление LIMIT 1 к вашему запросу может улучшить производительность. При таком условии механизм базы данных останавливает сканирование записей как только найдет одну и не будет проходит по всей таблице или индексу.

// Есть ли какой нибудь пользователь из Алабамы? // Так не нужно делать: $r = mysql_query("SELECT * FROM user WHERE state = "Alabama""); if (mysql_num_rows($r) > 0) { // ... } // Вот так будет значительно лучше: $r = mysql_query("SELECT 1 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. Индексирование и использование одинаковых типов для связываемых столбцов

Если ваше приложение содержит много запросов с директивой JOIN, вам нужно индексировать столбцы, которые связываются в обеих таблицах. Это оказывает эффект на внутреннюю оптимизацию операций связывания в MySQL.

Также связываемые столбцы должны иметь одинаковый тип. Например, если вы связываете столбец DECIMAL со столбцом INT из другой таблицы, MySQL не сможет использовать индекс по крайней мере для одной из одной таблицы. Даже кодировка символов должна быть одинаковой для одинаковых столбцов строчного типа.

// Поиск компании из определенного штата $r = mysql_query("SELECT company_name FROM users LEFT JOIN companies ON (users.state = companies.state) WHERE users.id = $user_id"); // оба столбца для названия штата должны быть индексированы // и оба должны иметь одинаковый тип и кодировку символов // или MySQL проведет полное сканирование таблицы

6. Не используйте ORDER BY RAND()

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

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

// Так делать НЕ НУЖНО: $r = mysql_query("SELECT username FROM user ORDER BY RAND() LIMIT 1"); // Вот так будет лучше работать: $r = mysql_query("SELECT count(*) FROM user"); $d = mysql_fetch_row($r); $rand = mt_rand(0,$d - 1); $r = mysql_query("SELECT username FROM user LIMIT $rand, 1");

Так вы получаете случайное число, которое меньше, чем количество строк в результате запроса, и используете его как смещение в предложении LIMIT.

7. Старайтесь не использовать SELECT *

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

Хорошей привычкой является указание столбца при выполнении SELECT.

// Плохо: $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, так как в этом случае значение не может быть отрицательным.

Даже если в вашей таблице есть поле с уникальным именем пользователя, не делайте его основным ключом. Поля с типом VARCHAR медленно работают в качестве основных ключей. Также структура вашей базы данных будет лучше, если в ней внутри использовать ссылки на записи на основании id.

Кроме того механизм MySQL использует основные ключи для своих внутренних задач, и использование поля id создает оптимальные условия для их решения.

Одним возможным исключением из данного правила являются “ассоциативные таблицы”, которые используются для отношений многие-ко-многим между двумя другими таблицами. Например, таблица “posts_tags” содержит 2 столбца: post_id, tag_id. Они используются для описания отношений между двумя таблицами “post” и “tags”. Описанная таблица может иметь основной ключ, который содержит оба поля id.

9. Используйте ENUM вместо VARCHAR

// Создаем подготовленное выражение if ($stmt = $mysqli->prepare("SELECT username FROM user WHERE state=?")) { // Привязываем параметры $stmt->bind_param("s", $state); // Выполняем $stmt->execute(); // Привязываем переменные результата $stmt->bind_result($username); // Получаем значения $stmt->fetch(); printf("%s is from %s\n", $username, $state); $stmt->close(); }

13. Небуферированные запросы

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

Отличное объяснение функции из документации PHP:

“mysql_unbuffered_query() отправляет SQL запрос на сервер MySQL без автоматического получения и буферирования строк результата, как это делает функция mysql_query(). Таким образом, сохраняется определенный объем памяти запросами SQL, которые выдают большой набор результата, и можно начинать работать с набором результата сразу же после получения первой строки, не дожидаясь пока запрос SQL будет полностью выполнен.”

Однако существует несколько ограничений. Вы должны либо прочитать все строки либо вызвать перед тем, как выполнить следующий запрос. Также нельзя использовать или для набора результата.

14. Храните IP адрес как UNSIGNED INT

Многие программисты создают поле VARCHAR(15) для хранения IP адреса, даже не задумываясь о том, что будут хранить в этом поле целочисленное значение. Если использовать INT, то размер поля сократится до 4 байт, и оно будет иметь фиксированную длину.

Нужно использовать тип UNSIGNED INT, так как IP адрес задействует все 32 бита беззнакового целого.

$r = "UPDATE users SET ip = INET_ATON("{$_SERVER["REMOTE_ADDR"]}") WHERE user_id = $user_id";

15. Таблицы с фиксированной длиной записи (Static) работают быстрее

Когда каждый отдельный столбец в таблице имеет фиксированную длину, то вся таблица в целом рассматривается как . Примеры типов столбцов, которые не имеют фиксированной длины: VARCHAR, TEXT, BLOB. Если вы включите хотя бы один столбец с таким типом, то таблица перестает рассматриваться как "static" и будет по-другому обрабатываться механизмом MySQL.

Таблицы "static" быстрее обрабатываются механизмом MySQL при поиске записей. Когда нужно прочитать определенную запись в таблице, то ее положение быстро вычисляется. Если размер строки не фиксирован, то для определения положения записи нужно время на поиск и сопоставление с индексом основного ключа.

Такие таблицы также проще кэшировать и проще восстанавливать при сбоях. Но они могут занимать больше места. Например, если конвертировать поле VARCHAR(20) в поле CHAR(20), то всегда будут заняты 20 байт вне зависимости от того, используются они или нет.

Использование техники "Вертикальное разделение" дает возможность отделить столбцы с переменной длиной в отдельную таблицу.

16. Вертикальное разделение

Вертикальное разделение - это действие по разделению структуры таблицы по вертикали с целью оптимизации.

Пример 1 : У вас есть таблица, которая содержит домашние адреса, редко используемые в приложении. Вы можете разделить вашу таблицу и хранить адреса в отдельной таблице. Таким образом основная таблица пользователей сократится в размере. А как известно, меньшая таблица обрабатывается быстрее.

Пример 2 : У вас в таблице есть поле “last_login”. Оно обновляется каждый раз, когда пользователь регистрируется на сайте. Но каждое обновление таблицы вызывает кэширование запроса, что может создать перегрузку системы. Вы можете выделить данное поле в другую таблицу, чтобы сделать обновления таблицы пользователей не такими частыми.

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

17. Разделяйте большие запросы DELETE или INSERT

Если вам нужно выполнить большой запрос DELETE или INSERT на работающем сайте, то нужно быть осторожным, чтобы не нарушить трафик. Когда выполняется большой запрос, то он может заблокировать ваши таблицы и привести к остановке приложения.

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

Если вы блокируете таблицы на продолжительное время (например, на 30 и более секунд) на высоко нагруженном веб сервере, вы можете вызвать накапливание процессов и запросов, что потребует значительного времени на расчистку или даже приведет к остановке вашего веб сервера.

Если у вас есть скрипт, который удаляет большое количество записей, просто используйте предложение LIMIT для разбиения его на маленькие партии, чтобы избежать описанной ситуации.

While (1) { mysql_query("DELETE FROM logs WHERE log_date <= "2009-10-01" LIMIT 10000"); if (mysql_affected_rows() == 0) { // выполняем удаление break; } // вы можете сделать небольшую паузу usleep(50000); }

18. Маленькие столбцы обрабатываются быстрее

Для механизма базы данных диск является наиболее важным узким местом. Стремление сделать все более компактным и маленьким обычно хорошо сказывается в сфере производительности за счет сокращения объема перемещаемых данных.

Документация MySQL содержит список для всех типов.

Если таблица будет содержать всего несколько строк, то нет причин делать основной ключ типа INT, а не MEDIUMINT, SMALLINT или даже TINYINT. если вам нужна только дата, используйте DATE вместо DATETIME.

Нужно только помнить о возможностях роста.

19. Выбирайте правильный механизм хранения данных

Есть два основных механизма хранения данных для MySQL: MyISAM и InnoDB. Каждый имеет свои достоинства и недостатки.

MyISAM отлично подходит для приложений с большой нагрузкой по чтению, но он не очень хорошо масштабируется при наличии большого количества записей. Даже если вы обновляете одно поле в одной строке, вся таблица будет заблокирована и ни один процесс не сможет ничего прочитать пока запрос не завершится. MyISAM быстро выполняет вычисления для запросов типа SELECT COUNT(*).

InnoDB является более сложным механизмом хранения данных, и он может быть более медленным, чем MyISAM для большинства маленьких приложений. Но он поддерживает блокирование строк, что лучше для масштабирования таблиц. Также он поддерживает некоторые дополнительные особенности, такие как транзакции.

20. Используйте объектно-реляционное отображение

Использование объектно-реляционного отображения (ORM - Object Relational Mapper) дает ряд преимуществ. Все, что можно сделать в ORM , можно сделать вручную, но с большими усилиями и более высокими требованиями к уровню разработчика.

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

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

Для PHP можно использовать ORM .

21. Будьте осторожны с постоянными соединениями

Постоянные соединения предназначены для сокращения потерь на восстановление соединений к MySQL. Когда создается постоянное соединение, то оно остается открытым даже после завершения скрипта. Так как Apache повторно использует дочерние процессы, то процесс выполняется для нового скрипта, и он использует тоже соединение с MySQL.

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

Apache работает на принципах параллельности, и создает множество дочерних процессов. Вот в чем заключается причина того, что постоянные соединения не работают как ожидается в данной системе. Прежде, чем использовать функцию mysql_pconnect(), проконсультируйтесь с вашим системным администратором.

Использование баз данных в значительной степени облегчает человеку жизнь, работу с данными, позволяя получать в краткие сроки нужную информацию из базы, либо записывать в неё. Однако работа с данными требует должного подхода, программисту следует учитывать некоторые аспекты взаимодействия с базами данных. В частности речь идет о MySQL. Далее давайте рассмотрим выжимку из советов по оптимизации взаимодействия с базами данных MySQL.

Делайте запросы MySQL удобными для кэширования

Встроенный механизм кэширования запросов на сервере MySQL позволяет заметно улучшить производительность. Большинство серверов баз данных MySQL включен механизм кэширования. Множество одинаковых запросов к базе данных за короткий промежуток времени способны создавать значительные потери в производительности, механизм кэширования способен кэшировать такие запросы, отдавая данные уже из кэша. Есть запросы, которые MySQL не способен кэшировать, и эти запросы рекомендуется делать немного иначе.

// этот запрос MySQL закэшировать не сможет $res = mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()"); // сделать можно иначе $today = date("Y-m-d"); $res = mysql_query("SELECT username FROM user WHERE signup_date >= "$today"");

Дело в том, что в первом запросе была использована функция CURDATE(), особенность её работы не позволяет помещать результаты запроса в кэш. Значение даты можно предварительно записать в строку запроса, это позволит исключить использование функции CURDATE() в запросе.
По аналогии есть и другие функции, которые не кэшируются самим сервером MySQL, среди них RAND(), NOW() а так же другие функции, результат которых недетерминирован.

Просмотрите как выполняется ваш запрос с помощью синтаксиса EXPLAIN

Посмотреть, как MySQL выполняет ваш запрос можно с помощью синтаксиса EXPLAIN . Его использование может помочь определить слабые места в производительности запроса, а так же в структуре таблиц. В качестве результата запроса EXPLAIN возвратит данные, которые покажут, какие используются индексы, каким образом выбираются данные из таблиц, как сортируются, и т.д. Для этого достаточно добавить вначале SELECT-запроса ключевое слово EXPLAIN, после чего будет показана таблица, с данными.

Когда вам нужна одна запись, выставляйте LIMIT 1

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

// запрос города с кодом Shymkent из базы $res = mysql_query("SELECT * FROM location WHERE city = "Shymkent""); if (mysql_num_rows($res) > 0){ } // добавляем LIMIT 1 для оптимизации запроса $res = mysql_query("SELECT * FROM location WHERE city = "Shymkent" LIMIT 1"); if (mysql_num_rows($res) > 0){ }

Индексируйте поля по которым производится поиск

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

// тут сработает индекс city LIKE ‘shym%’ // тут же индекс задействован не будет city LIKE ‘%shymkent%’

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

Индексируйте поля по которым объединяются таблицы

Если вы используйте множество объединений таблиц, то вам стоит задуматься о том, чтобы поля, участвующих в объединении были проиндексированы в обеих таблицах. Это дело влияет на то, как MySQL будет производить внутреннюю оптимизацию объединений полей таблицы. Поля объединения должны быть одного типа и одной кодировки. Т.е. к примеру, если одно поле будет иметь тип DECIMAL, а другое INT, то MySQL не сможет воспользоваться индексом.

Найдите альтернативу вместо ORDER BY RAND()

Использование рандомной сортировки действительно является весьма удобным, и об этом такого же мнения многие начинающие программисты. Однако тут есть подводные камни, и очень весомые, используя подобный метод выборки в своих запросах, вы оставляете узкое место в производительности. Здесь же рекомендуется прибегнуть к дополнительному коду вместо использования ORDER BY RAND(), в качестве альтернативы, чтобы избавиться от слабого места в производительности, которое напомнит о себе при увеличении объема данных.

Используйте выборку конкретных полей, вместо SELECT *

Не ленитесь указывать конкретные нужные поля в запросе при выборке, вместо использования «*» — выборка всех полей, дело в том, что чем больше данных считывается из таблицы, тем медленнее становиться ваш запрос.

Добавляйте поле ID для всех таблиц

Каждая таблица в хорошем её исполнении должна иметь поле id типа INT, которое является первичным ключом (PRIMARY_KEY), и AUTO_INCREMENT. Кроме того, для поля нужно указать параметр UNSIGNED, который означает то, что значение всегда будет положительным.
В MySQL есть внутренние операции, которые могут использовать первичный ключ, это играет роль для сложных конфигураций баз данных, таких как кластеры, распараллеливание, и т.д.
Кроме того, если есть несколько таблиц, и необходимо выполнить объединенный запрос, то тут ID таблиц окажется весьма кстати.

ENUM как альтернатива VARCHAR

Давайте представим, вы хотите добавить поле в таблице, которое должно содержать определенный набор значений. Традиционно многие программисты выставляют тип VARCHAR для полей. Однако есть и другой тип поля, который гораздо быстрей и компактнее. Значения в данном типе хранятся так же, как и TINYINT, но отображаются как в строковом типе.

Используйте значение NOT NULL вместо NULL

Поля NULL занимают больше места в записи, из-за того что возникает необходимость отмечать это NULL значение. Таблицы MyISAM, поля с NULL хранятся таким образом, что каждое поле занимает 1 дополнительный бит, который округляется до ближайшего байта. Если использование NULL в поле не принципиально, то рекомендуется использовать NOT NULL.

Пользуйтесь Prepared Statements

$res = "UPDATE hosts SET ip = INET_ATON("{$_SERVER["REMOTE_ADDR"]}") WHERE id = $host_id";

Используйте статичные таблицы

Статичная таблица это обычная таблица в базе, за исключеним того, что каждое поле в таблице имеет фиксированный размер. Если в таблице есть колонки, не фиксированной длины, к примеру, это могут быть: VARCHAR, TEXT, BLOB, она перестает быть статичной, и будет обрабатываться MySQL немного иначе. Статичные таблицы, или их можно ещё назвать таблицами фиксированного размера работают быстрее не статичных. Записи из таких таблицах будут просматриваться быстрее, при необходимости выбора нужной строки MySQL быстро вычислит её позицию. Если поле имеет не фиксированный размер, то в этом случае поиск производиться по индексу. Есть и другие плюсы использования статических таблиц, дело в том, что эти таблицы проще кэшируются, а так же восстанавливаются после падения базы данных.

Используйте вертикальное разделение

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

Разделяйте объемные запросы INSERT и DELETE

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

While (1){ mysql_query("DELETE FROM logs WHERE log_date <= "2015-07-20" LIMIT 1000"); if (mysql_affected_rows() == 0){ // записи удалены успешно break; } usleep(50000); // делаем небольшую паузу }

Стремитесь использовать поля небольшого размера

Как известно данные базы хранятся на жестком диске, это зачастую это может оказаться одним из слабых мест в веб-приложении. Дело в том, что записи небольшого размера являются более предпочтительными, т.к. использование их уменьшает работу с жестким диском. Если вы уверенны, что конкретная таблица будет хранить мало строк, то рациональным решением будет использование типов полей, с минимальными возможными значениями. К примеру, если основной ключ имеет тип INT, и вы будете хранить в таблице лишь небольшое кол-во данных, то лучше сделать его типа MEDIUMINT, SMALLINT или даже TINYINT.

Выбирайте тип таблиц под свои задачи

Два широко известных типа таблиц на сегодняшний день, это MyISAM и InnoDB , каждый из них имеет свои положительные и отрицательные стороны. К примеру, MyISAM хорошо считывает данные из таблиц в большом объеме, одно он более медлителен при записи. Он так же хорошо выполняет запросы вида SELECT COUNT(*).
Механизм хранения данных у InnoDB более сложный, чем у MyISAM, однако, он поддерживает блокировку строк, что является положительной стороной при масштабировании. Поэтому сказать, что одно лучше другого нельзя, да и не правильно, нужно выбирать тип исходя из своих потребностей.

9 октября 2008 в 23:37

Оптимизация MySQL запросов

  • MySQL

В повседневной работе приходится сталкиваться с довольно однотипными ошибками при написании запросов.

В этой статье хотелось бы привести примеры того, как НЕ надо писать запросы.

  • Выборка всех полей
    SELECT * FROM table

    При написании запросов не используйте выборку всех полей - "*". Перечислите только те поля, которые вам действительно нужны. Это сократит количество выбираемых и пересылаемых данных. Кроме этого, не забывайте про покрывающие индексы. Даже если вам на самом деле необходимы все поля в таблице, лучше их перечислить. Во-первых, это повышает читабельность кода. При использовании звездочки невозможно узнать какие поля есть в таблице без заглядывания в нее. Во-вторых, со временем количество столбцов в вашей таблице может изменяться, и если сегодня это пять INT столбцов, то через месяц могут добавиться TEXT и BLOB поля, которые будут замедлять выборку.

  • Запросы в цикле.
    Нужно четко представлять себе, что SQL - язык, оперирующий множествами. Порой программистам, привыкшим думать терминами процедурных языков, трудно перестроить мышление на язык множеств. Это можно сделать довольно просто, взяв на вооружение простое правило - «никогда не выполнять запросы в цикле». Примеры того, как это можно сделать:

    1. Выборки
    $news_ids = get_list("SELECT news_id FROM today_news ");
    while($news_id = get_next($news_ids))
    $news = get_row("SELECT title, body FROM news WHERE news_id = ". $news_id);

    Правило очень простое - чем меньше запросов, тем лучше (хотя из этого, как и из любого правила, есть исключения). Не забывайте про конструкцию IN(). Приведенный код можно написать одним запросом:
    SELECT title, body FROM today_news INNER JOIN news USING(news_id)

    2. Вставки
    $log = parse_log();
    while($record = next($log))
    query("INSERT INTO logs SET value = ". $log["value"]);

    Гораздо более эффективно склеить и выполнить один запрос:
    INSERT INTO logs (value) VALUES (...), (...)

    3. Обновления
    Иногда бывает нужно обновить несколько строк в одной таблице. Если обновляемое значение одинаковое, то все просто:
    UPDATE news SET title="test" WHERE id IN (1, 2, 3).

    Если изменяемое значение для каждой записи разное, то это можно сделать таким запросом:
    UPDATE news SET
    title = CASE
    WHEN news_id = 1 THEN "aa"
    WHEN news_id = 2 THEN "bb" END
    WHERE news_id IN (1, 2)

    Наши тесты показывают, что такой запрос выполняется в 2-3 раза быстрее, чем несколько отдельных запросов.

  • Выполнение операций над проиндексированными полями
    SELECT user_id FROM users WHERE blogs_count * 2 = $value

    В таком запросе индекс использоваться не будет, даже если столбец blogs_count проиндексирован. Для того, чтобы индекс использовался, над проиндексированным полем в запросе не должно выполняться преобразований. Для подобных запросов выносите функции преобразования в другую часть:
    SELECT user_id FROM users WHERE blogs_count = $value / 2;

    Аналогичный пример:
    SELECT user_id FROM users WHERE TO_DAYS(CURRENT_DATE) - TO_DAYS(registered) <= 10;

    Не будет использовать индекс по полю registered, тогда как
    SELECT user_id FROM users WHERE registered >= DATE_SUB(CURRENT_DATE, INTERVAL 10 DAY);
    будет.

  • Выборка строк только для подсчета их количества
    $result = mysql_query(«SELECT * FROM table», $link);
    $num_rows = mysql_num_rows($result);
    Если вам нужно выбрать количество строк, удовлетворяющих определенному условию, используйте запрос SELECT COUNT(*) FROM table, а не выбирайте все строки лишь для того, чтобы подсчитать их количество.
  • Выборка лишних строк
    $result = mysql_query(«SELECT * FROM table1», $link);
    while($row = mysql_fetch_assoc($result) && $i < 20) {

    }
    Если вам нужны только n строк выборки, используйте LIMIT, вместо того, чтобы отбрасывать лишние строки в приложении.
  • Использование ORDER BY RAND()
    SELECT * FROM table ORDER BY RAND() LIMIT 1;

    Если в таблице больше, чем 4-5 тысяч строк, то ORDER BY RAND() будет работать очень медленно. Гораздо более эффективно будет выполнить два запроса:

    Если в таблице auto_increment"ный первичный ключ и нет пропусков:
    $rnd = rand(1, query("SELECT MAX(id) FROM table"));
    $row = query("SELECT * FROM table WHERE id = ".$rnd);

    Либо:
    $cnt = query("SELECT COUNT(*) FROM table");
    $row = query("SELECT * FROM table LIMIT ".$cnt.", 1");
    что, однако, так же может быть медленным при очень большом количестве строк в таблице.

  • Использование большого количества JOIN"ов
    SELECT
    v.video_id
    a.name,
    g.genre
    FROM
    videos AS v
    LEFT JOIN
    link_actors_videos AS la ON la.video_id = v.video_id
    LEFT JOIN
    actors AS a ON a.actor_id = la.actor_id
    LEFT JOIN
    link_genre_video AS lg ON lg.video_id = v.video_id
    LEFT JOIN
    genres AS g ON g.genre_id = lg.genre_id

    Нужно помнить, что при связи таблиц один-ко многим количество строк в выборке будет расти при каждом очередном JOIN"е. Для подобных случаев более быстрым бывает разбить подобный запрос на несколько простых.

  • Использование LIMIT
    SELECT… FROM table LIMIT $start, $per_page

    Многие думают, что подобный запрос вернет $per_page записей (обычно 10-20) и поэтому сработает быстро. Он и сработает быстро для нескольких первых страниц. Но если количество записей велико, и нужно выполнить запрос SELECT… FROM table LIMIT 1000000, 1000020, то для выполнения такого запроса MySQL сначала выберет 1000020 записей, отбросит первый миллион и вернет 20. Это может быть совсем не быстро. Тривиальных путей решения проблемы нет. Многие просто ограничивают количество доступных страниц разумным числом. Также можно ускорить подобные запросы использованием покрывающих индексов или сторонних решений (например sphinx).

  • Неиспользование ON DUPLICATE KEY UPDATE
    $row = query("SELECT * FROM table WHERE id=1");

    If($row)
    query("UPDATE table SET column = column + 1 WHERE id=1")
    else
    query("INSERT INTO table SET column = 1, id=1");

    Подобную конструкцию можно заменить одним запросом, при условии наличия первичного или уникального ключа по полю id:
    INSERT INTO table SET column = 1, id=1 ON DUPLICATE KEY UPDATE column = column + 1

Читайте

В данной статье мы рассмотрим способы обращения к таблицам баз данный MySQL с помощью языка запросов SQL. SQL - это аббревиатура, которая так и "раскрывается" - структуризированный язык запросов.
В языке PHP для это цели существует целый ряд функций с префиксом "mysql". Нам для рассмотрения запросов понадобится не так много из них. Функция, без которой в языке PHP выполнение SQL-запросов было бы просто невозможным:

Resource mysql_query(запрос)

Данная функция посылает запрос к базе данных и возвращает в случае успешного обращения идентификатор ресурса.
Для того чтобы подключиться к базе данных MySQL необходимо выполнить следующую последовательность:

$host="localhost"; // имя хоста (уточняется у провайдера) $database="db_name"; // имя базы данных, которую вы должны создать $user="user_name"; // заданное вами имя пользователя, либо определенное провайдером $pswd="your_pass"; // заданный вами пароль $dbh = mysql_connect($host, $user, $pswd) or die("Не могу соединиться с MySQL."); mysql_select_db($database) or die("Не могу подключиться к базе.");

Итак mysql_connect() - функция для подключения к серверу MySQL на Вашем хостинге.
А mysql_select_db() выбирает базу данных на сервере для подключения.
Иными словами подключаемся к серверу, выбираем базу и начинаем работать.
Функция die() вызывается в случае ошибки и выводит в окно браузера сообщение, которое вы указали.
Для завершения работы с базами данных используется функция:

Mysql_close($dbh);

Здесь $dbh - дескриптор, которые при соединении возвратила функция mysql_connect .
Закончив стартовый обзор, начнем рассмотрение собственно SQL-запросов.
Для этого прежде всего вам необходимо создать базу данных с определенном именем. А в ней создать таблицу, тоже с конкретным именем. В наших примерах будем обращаться к таблице my_sql_table . Чтобы создать эту таблицу давайте выполним в phpmyadmin нашего localhost следующий запрос:

CREATE TABLE `my_sql_table` (`id` INT NOT NULL , // идентификатор будущих записей таблицы `firstname` VARCHAR(50) NOT NULL , // текстовое поле VARCHAR `surname` VARCHAR(50) NOT NULL , // max длиной 50 символов PRIMARY KEY (`id`) // первичный ключ - идентификатор id);

Итак таблица создана. Выполним первый запрос, который сразу оформим в виде PHP-кода:

\n"; echo "Имя: ".$row["firstname"]."
\n"; echo "Фамилия: ".$row["surname"]."


\n"; } ?>

Разберем PHP-код файла firstsql.php . Начнем с собственно запроса к таблицам базы данных (БД).

$query = "SELECT * FROM `my_sql_table`";

Данный запрос можно расшифровать так: выбрать из таблицы my_sql_table БД все записи из всех полей. Таким образом знак * после слова SELECT означает "выбрать абсолютно все". Итак, запрос сформирован. Теперь его надо выполнить:

$res = mysql_query($query);

В случае успешного выполнения запроса функция mysql_query() вернет нам идентификатор ресурса $res .
Его мы должны передать в качестве параметра в функцию mysql_fetch_array() . Название этой функции говорит само за себя. Т.е. она формирует и выдает массив по выборке из таблицы БД. В случае нашей таблицы массив будет состоять из числа элементов, равных количествам записей (строк) в таблице и содержать значения id, firstname, surname для каждой строки таблицы. Следовательно, следующий код:

While($row = mysql_fetch_array($res)) { echo "Номер: ".$row["id"]."
\n"; echo "Имя:".$row["firstname"]."
\n"; echo "Фамилия:".$row["surname"]."


\n"; }

можно прокомментировать так: пока введенная нами переменная $row получает не нулевые результаты работы функции mysql_fetch_row следует выдать в броузер значение полей $row["id"], $row["firstname"], $row["surname"] с помощью echo .
Если запрос выполнить так:

$query = "SELECT firstname FROM `my_sql_table`";

то это будет означать, что из всех строк выбирается только значения поля firstname.
Следовательно предыдущий код следует переписать как:

$res = mysql_query($query); while($row = mysql_fetch_array($res)) { echo "Имя:".$row["firstname"]."
\n"; }

Если Вы хотите выбрать строки таблицы с конкретным значением id где фамилия (surname) будет Петров , то запрос перепишется следующим образом:

$query = "SELECT id FROM `my_sql_table` where surname="Петров"";

А вот если потребуется узнать фамилию того, кто находится под номером, к примеру, 5, то запрос будет таким:

$query = "SELECT surname FROM `my_sql_table` where id=5";

В этом случае Вы знаете, что результатом запроса будет всего одна строка из таблицы. Т.е. нет смысла организовывать цикл с использованием while . И обработка запроса будет следующей

$res = mysql_query($query); $row = mysql_fetch_row($res); echo "Фамилия пятого человека в списке: ".$row."\n";

Здесь вместо mysql_fetch_array() мы применили mysql_fetch_row() . Т.е. получить значение поля (или полей) конкретной строки. Поскольку поле у нас было одно - surname - мы можем обратиться к единственному элементу массива $row как $row; .

Итак, рассмотрим наиболее типичные примеры запросов MySQL. Рассмотрение проведем на базе таблицы my_sql_table :
1. Добавим в таблицу my_sql_table поле middle_name (отчество) после surname :

$query = "ALTER TABLE `my_sql_table` ADD `middle_name`
VARCHAR(50) NOT NULL AFTER `surname`";

2. Теперь удалим поле surname из таблицы my_sql_table:

$query = "ALTER TABLE `my_sql_table` DROP `surname`";

3. Удаляем записи из таблицы my_sql_table с фамилией Сидоров:

$query = "DELETE FROM `my_sql_table` where surname="Сидоров"";

4. Помимо знаков равенства, также "больше" или "меньше", в языке MySQL запросов существует понятие "похоже на ". Выберем записи из таблицы my_sql_table, где в фамилии встречается "дор " :

$query = "SELECT * FROM `my_sql_table` where surname like "%дор%"";

Здесь наличие "% " в начале и конце "дор" и означает, что запрос будет искать именно "дор", причем не важно в начале, конце, или середине фамилии он находится. Рассмотрим следующий пример
5. Выберем записи из таблицы my_sql_table с фамилией, которая начинается на П . Обратите внимание на расположение "% ":

$query = "SELECT * FROM `my_sql_table` where surname like "П%"";

6. Вычислим максимальное значение id :

$query = "SELECT MAX(id) FROM `my_sql_table`";

7. Вычислим количество полей в my_sql_table с фамилией, которая начинается на П .

$query = "SELECT COUNT(*) FROM `my_sql_table` where surname like "П%"";

8. Удаление таблицы my_sql_table:

$query = "DROP TABLE `my_sql_table`";

Для запросов 1-3 на языке PHP достаточно просто выполнить запрос:

Mysql_query($query);

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




Есть еще вопросы или что-то непонятно - добро пожаловать на наш

В данной статье хотелось бы рассказать о том какие SQL - запросы бывают и как их обрабатывать при помощи языка программирования PHP. Ничего сложного и сверхъестественного здесь не будет. Статья скорее адресована на новичков. В языке PHP для обработки запросов имеется целый набор функций с префиксов "mysql", но в данной статье мы рассмотрим самые основные и самые необходимы, а их не так уж и много.
mysql_query() - Функция посылает запрос к базе данных и в случае успешного выполнения возвращает какой-то идентификатор.

Для того чтобы подключиться к базе данных вы можете использовать код ниже:

$host="localhost"; // имя хоста (уточняется у провайдера), на OpenServer обычно используется localhost $database="db_name"; // имя базы данных, которую вы должны создать $user="user_name"; // заданное вами имя пользователя, либо имя, которое дал вам провайдер $passwod="your_pass"; // заданный вами пароль $db = mysql_connect($host, $user, $password) or die("Не удалось подключиться к MySQL."); mysql_select_db($database) or die("Не удалось подключиться к базе данных.");

Рассмотрим этот код более детально.
mysql_connect - Функция выполняет подключение к MySQL серверу.
mysql_select_db() - Функция выполняет подключение к бизе данных.
die() - Функция срабатывает только в случае ошибки и выводит нам в браузере текст, который мы же и указали. Используется она больше для удобства, чем как обязательность...
mysql_close() - Используется эта функция для завершения работы с базой данных. Например: mysql_close($db); .

На этом небольшое введение заканчивается и мы переходим к самим SQL - запросам. Как вы видели код выше, мы используем базу данных MySQL, ее вам и нужно создать, а также создать таблицу и пользователя, указав ему пароль. В данной статье мы будем рассматривать все на примере нашей таблицы "table_name" , создать ее вы можете в phpmyadmin , выполнив следующий SQL - запрос:

CREATE TABLE `table_name` (`id` INT NOT NULL , // уникальный идентификатор будущих записей таблицы `name` VARCHAR(50) NOT NULL , // текстовое поле VARCHAR, максимальной длиной 50 символов `surname` VARCHAR(50) NOT NULL , // такое же текстовое поле, максимальной длиной 50 символов PRIMARY KEY (`id`) // первичный ключ - идентификатор id);

Таблица создана, теперь мы можем смело перейти к выполнению различных запросов. Запрос конечно же оформим с помощью PHP, для этого создадим файл name.php со следующим содержимым:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // имя хоста (уточняется у провайдера) $database = "db_name" ; // имя базы данных, которую вы должны создать $user = "user_name" ; // заданное вами имя пользователя, либо определенное провайдером $passwod = "your_pass" ; // заданный вами пароль $db = mysql_connect ($host , $user , $passwod ) or die ("Не удалось подключиться к MySQL." ) ; mysql_select_db ($database ) or die ("Не удалось подключиться к базе данных." ) ; $query = "SELECT * FROM `table_name`" ; $resource = mysql_query ($query ) ; while ($row = mysql_fetch_array ($resource ) ) { echo "Порядковый номер: " . $row [ "id" ] . "
\n " ; echo "Имя: " . $row [ "name" ] . "
\n " ; echo "Фамилия: " . $row [ "surname" ] . "

\n " ; } ?>




\n"; } ?>

И также как и ранее разберем этот код. А начнем мы пожалуй с первого запросы к нашей таблице.

$resource = mysql_query($query);

В случае успешного выполнения запросы, функция "mysql_query()" вернет нам идентификатор "$resource" . На нашем пример мы передаем его другой функции "mysql_fetch_array()" , которая формирует массив и выдает его данные, по выборке из таблицы базы данных. На примере нашей таблицы массив будет состоять из ряда элементов, равных количеству строк (записей) в таблице. Которые в свою очередь будут содержать следующие значения: id, name, surname , в каждой строке таблицы. Используем мы вот этот теперь код:

while($row = mysql_fetch_array($resource)) { echo "Порядковый номер: ".$row["id"]."
\n"; echo "Имя: ".$row["name"]."
\n"; echo "Фамилия: ".$row["surname"]."


\n"; }

То есть, пока данная нами переменная $row не получает результаты работы функции "mysql_fetch_row" равные нулю, следует выдавать браузеру значения полей: $row["id"], $row["name"], $row["surname"] при помощи переменной "echo" . На этом описание и разбор данного кода закончен.

Разберем еще один пример запроса:

1 $query = "SELECT name FROM `table_name`" ;

$query = "SELECT name FROM `table_name`";

Тут мы уже не выбираем из таблицы всё, а выбираем только значения полей "name" . Мы также должны переписать немного и дальнейший код вывода. Он будет уже таким:

1 2 3 4 5 $resource = mysql_query ($query ) ; while ($row = mysql_fetch_array ($resource ) ) { echo "Имя:" . $row [ "name" ] . "
\n " ; }

$resource = mysql_query($query); while($row = mysql_fetch_array($resource)) { echo "Имя:".$row["name"]."
\n"; }

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

К примеру на нужно вывести фамилию из строки под номером 10 , то запрос перепишем следующим образом:

После выполнения подобного запроса результатом будет только одна строка, и соответственно нет смысла создавать цикл while и загонять все в массив. Будем использовать более простой код:

$resource = mysql_query($query); $row = mysql_fetch_row($resource); echo "Фамилия десятого человека в списке: ".$row."\n";

Если вы внимательно присмотритесь, то увидите новую функцию "mysql_fetch_row()" , а ранее мы использовали "mysql_fetch_array()" . Данная функция позволяет получить значения полей (или одного поля) какой-то конкретной строки. Так как мы получали только фамилию, а это один элемент, то обратиться к нему мы можем с помощью $row вот так $row; . На этом рассмотрение подобных запросов закончим пока что и двинемся быстренько дальше.

Чтобы добавить в таблицу "table_name" поле "middlename" (отчество) после surname :

1 $query = "ALTER TABLE `table_name` ADD `middlename` VARCHAR(50) NOT NULL AFTER `surname`" ;

$query = "ALTER TABLE `table_name` ADD `middlename` VARCHAR(50) NOT NULL AFTER `surname`";

Кроме знаков равенства, при использовании MySQL - запросов можно использовать такое понятие как "похоже на" . Выполним следующий запрос, и получим все записи из таблицы, где в фамилии встречается "ов" :

Использование знака "%" в начале или конце обозначает, что "ов" может быть в любой части слова. Рассмотрим теперь пример где мы выбираем данные по букве "В" в начале имени Обратите внимание на расположение знака "%" .

Следующий запрос на получение максимального значения поля id:

$query = "ALTER TABLE `table_name` DROP `surname`";

И теперь полностью удалим еще нашу таблицу "table_name" :

1 $query = "DROP TABLE `table_name`" ;

$query = "DROP TABLE `table_name`";

На этом пожалуй мы и закончим. Подводя итоги статьи, мы рассмотрели построение основных запросов, вывод данных из базы MySQL, подключение к базе. Хотелось бы еще уточнить для запросов на получение (SELECT) мы можем использовать функцию - mysql_query() . Вот собственно и все, что я хотел вам рассказать. При возникновении вопросов или в случае появления дополнений к статье пишите в комментарии.