Http перенаправление на другую страницу. Как в PHP задать редирект на другой URL до загрузки страницы? Использование метатегов HTML для редиректа

Http перенаправление на другую страницу. Как в PHP задать редирект на другой URL до загрузки страницы? Использование метатегов HTML для редиректа

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

Использование ключевого слова EXPLAIN может помочь составить картину того, что делает 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%’”) преимущества индексирования будут не доступны. В таких случая лучше использовать полнотекстовый поиск mysql или построение собственных решений на основе индексирования.

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. Небуферированные запросы

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

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

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

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

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) работают быстрее

Когда каждый отдельный столбец в таблице имеет фиксированную длину, то вся таблица в целом рассматривается как “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 Doctrine .

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

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

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

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

В этой статье мы научимся отправлять запросы к базе данных через PHP . Данная статья очень важная, и её необходимо Вам обязательно понять. Однако, успокою Вас - материал очень простой, поэтому затруднений быть не должно.

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

Теперь переходим к отправке запросов к базе данных в PHP :


}
$mysqli->query("INSERT INTO mytable (name, email) VALUES ("MyName", "[email protected]")");
$mysqli->close();
?>

В данном примере мы подключились к базе данных, проверили успешность подключения, отправили запрос с помощью метода query() , а затем закрыли подключение. Как видите, всё очень просто. Для отправки любых SQL-запросов достаточно всего одного метода - query() , поэтому в данном случае всё безумно просто.

Теперь чуть-чуть усложним задачу. Давайте с Вами разберём запросы, которые возвращают result_set - результат. Самый популярный запрос, возврающающий result_set - это выборка данных из таблицы. В следующем примере мы с Вами сделаем выборку данных, а затем выведем результат:

$mysqli = @new mysqli("localhost", "Admin", "pass", "mybase");
if (mysqli_connect_errno()) {
echo "Подключение невозможно: ".mysqli_connect_error();
}
$result_set = $mysqli->
while ($row = $result_set->fetch_assoc()) {
print_r($row);
echo "
";
}
$result_set->close();
$mysqli->close();
?>

Для начала немного поясню, что из себя представляет result_set . Result_set - это таблица с результатом. У этой таблицы есть набор записей (строки таблицы). И для вывода всех записей надо перебрать каждую строку таблицы и вывести её. А теперь поясняю пример: после отправки запроса у нас формируется result_set . Затем мы в цикле присваиваем переменной row значение очередной строки, то есть одномерный массив, который возвращает метод fetch_assoc() . Когда все строки закончатся, метод fetch_assoc() вернёт false , и будет произведён выход из цикла. Внутри цикла while мы просто выводим массив с использованием отладочной функции print_r() , хотя, безусловно, можно было бы вывести с использованием foreach , но сейчас это не нужно.

Давайте кратко подведём итог, как нужно работать с result_set :

  1. Получить result_set , отправив соответствующий запрос к базе данных.
  2. В цикле на каждой итерации присваивать следующую строку (запись) из result_set с использованием метода fetch_assoc() некоторой переменной row . Дальше Вы можете работать с этой переменной как с одномерным ассоциативным массивом, у которого ключи - это названия полей таблицы, а значения соответствуют текущей записи.
  3. Обязательно закройте result_set методом close() , чтобы осводить ресурсы.

Как Вы заметили, метод fetch_assoc() возвращает всегда следующую запись. То есть сначала 1-ю, потом 2-ю, потом 3-ю и так далее. Если у Вас есть неплохой опыт программирования, то Вы сразу догадаетесь, что это происходит благодаря внутреннему указателю, который Вы, разумеется, можете двигать. Где это используется? Например, это может использоваться, когда Вам необходимо поработать с result_set не 1 , а 2 и более раз. Чтобы заново не формировать тот же запрос, можно просто передвинуть указатель в начало. И тогда можно снова заниматься перебором result_set с помощью метода fetch_assoc().

Для изменения положения указателя существует метод data_seek() , который принимает целое число от 0 до "количества записей - 1 ", соответственно указатель встаёт на соответствующую параметру запись:

$mysqli = @new mysqli("localhost", "Admin", "pass", "mybase");
if (mysqli_connect_errno()) {
echo "Подключение невозможно: ".mysqli_connect_error();
}
$result_set = $mysqli->query("SELECT * FROM mytable");
$result_set->num_rows;
while ($row = $result_set->fetch_assoc()) {
print_r($row);
echo "
";
}
$result_set->data_seek(0);
while ($row = $result_set->fetch_assoc()) {
print_r($row);
echo "
";
}
$result_set->close();
$mysqli->close();
?>

В этом примере мы вывели количество записей в result_set с помощью свойства num_rows . А также познакомились с методом data_seek() . То есть мы перебрали весь результат, затем вернули указатель на 0-ю запись и вновь перебрали результат.

В этой статье мы с Вами повторили подключение к базе данных и закрытие подключения. А также узнали, как отправлять запросы к базе данных через PHP . Узнали, как получать result_set и как работать с ним. Это всё, что Вам необходимо знать для успешной работы с MySQL в PHP .

СУБД MySQL - одна из множества баз данных, поддерживаемых в PHP. Система MySQL распространяется бесплатно и обладает достаточной мощностью для решения реальных задач.

Краткое введение в MySQL

SQL - это аббревиатура от слов Structured Query Language , что означает структурированный язык запросов. Этот язык является стандартным средством для доступа к различным базам данных.

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

Для работы с базами данных удобно пользоваться средством, входящее в комплект Web-разработчика: Denwer phpMyAdmin . Здесь можно создать новую базу данных, создать новую таблицу в выбранной базе данных, заполнить таблицу данными, а также добавлять, удалять и редактировать данные.

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


Каждый столбец после своего типа данных содержит и другие спецификаторы:

Тип Описание
NOT NULL Все строки таблицы должны иметь значение в этом атрибуте. Если не указано, поле может быть пустым (NULL)
AUTO_INCREMENT Специальная возможность MySQL, которую можно задействовать в числовых столбцах. Если при вставке строк в таблицу оставлять такое поле пустым, MySQL автоматически генерирует уникальное значение идентификатора. Это значение будет на единицу больше максимального значения, уже существующего в столбце. В каждой таблице может быть не больше одного такого поля. Столбцы с AUTO_INCREMENT должны быть проиндексированными
PRIMARY KEY Столбец является первичным ключом для таблицы. Данные в этом столбце должны быть уникальными. MySQL автоматически индексирует этот столбец
UNSIGNED После целочисленного типа означает, что его значение может быть либо положительным, либо нулевым
COMMENT Название столбца таблицы

Создание новой базы данных MySQL CREATE DATABASE .

CREATE DATABASE IF NOT EXISTS `base` DEFAULT CHARACTER SET cp1251 COLLATE cp1251_bin

Создание новой таблицы осуществляется при помощи SQL-команды CREATE TABLE . Например, таблица books для книжного магазина будет содержать пять полей: ISBN, автор, название, цена и количество экземпляров:

CREATE TABLE books (ISBN CHAR(13) NOT NULL, PRIMARY KEY (ISBN), author VARCHAR(30), title VARCHAR(60), price FLOAT(4,2), quantity TINYINT UNSIGNED); Чтобы избежать сообщения об ошибке, если таблица уже есть необходимо изменить первую строчку, добавив фразу "IF NOT EXISTS": CREATE TABLE IF NOT EXISTS books ...

Для создания автообновляемого поля с текущей датой типа TIMESTAMP или DATETIME используйте следующую конструкцию:

CREATE TABLE t1 (ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, dt DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP);

Добавление данных в эту таблицу осуществляется при помощи SQL-команды INSERT . Например:

INSERT INTO books (ISBN, author, title, price, quantity) VALUES ("5-8459-0184-7", "Зандстра Мэт", "Освой самостоятельно PHP4 за 24 часа", "129", "5");

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

Символ * означает, что необходимы все поля. Например:

SELECT * FROM books;

Для получения доступа только к некоторому полю следует указать его имя в инструкции SELECT . Например:

SELECT author, title, price FROM books;

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

SELECT * FROM books WHERE price % Соответствует любому количеству символов, даже нулевых
_ Соответствует ровно одному символу

Для того, чтобы строки, извлеченные по запросу, перечислялись в определенном порядке, используется конструкция ORDER BY . Например:

SELECT * FROM books ORDER BY price;

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

SELECT * FROM books ORDER BY price DESC;

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

SELECT * FROM books ORDER BY 4, 2, 3;

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

UPDATE books SET price = price * 1.1;

Конструкция WHERE ограничит работу UPDATE определенным строками. Например:

UPDATE books SET price = price * 1.05 WHERE price

Для удаления строк из базы данных используется оператор DELETE . Ненужные строки указываются при помощи конструкции WHERE . Например, какие-то книги проданы:

DELETE FROM books WHERE quantity = 0;

Если нужно удалить все записи

TRUNCATE TABLE table_name

Для полного удаления таблицы используется:

DROP TABLE table_name

Связь PHP с базой данных MySQL

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

Чтобы получить доступ к базе данных из Web, используя PHP, надо сделать следующие основные шаги:

  • Подключение к серверу MySQL.
  • Выбор базы данных.
  • Выполнение запроса к базе данных:
    • добавление ;
    • удаление ;
    • изменение ;
    • поиск ;
    • сортировка .
  • Получение результата запроса.
  • Отсоединение от базы данных.

Для подключения к серверу базы данных в PHP есть функция mysql_connect() . Ее аргументы: имя компьютера, имя пользователя и пароль. Эти аргументы можно опустить. По умолчанию имя компьютера = localhost , тогда имя пользователя и пароль не требуется. Если PHP используется в сочетании с сервером Apache, то можно воспользоваться функцией mysql_pconnect() . В этом случае соединение с сервером не исчезает после завершения работы программы или вызова функции mysql_close() . Функции mysql_connect() и mysql_pconnect() возвращают идентификатор подключения, если все прошло успешно. Например:

$link = mysql_pconnect (); if (!$link) die ("Невозможно подключение к MySQL");

После того, как соединение с сервером MySQL установлено, нужно выбрать базу данных. Для этого используется функция mysql_select_db() . Ее аргумент: имя базы данных. Функция возвращает true , если указанная база данных существует и доступ к ней возможен. Например:

$db = "sample"; mysql_select_db ($db) or die ("Невозможно открыть $db");

Для добавления, удаления, изменения и выбора данных нужно сконструировать и выполнить запрос SQL. Для этого в языке PHP существует функция mysql_query() . Ее аргумент: строка с запросом. Функция возвращает идентификатор запроса.

Пример 1

Добавление записи в таблицу

При каждом выполнении примера 1 в таблицу будет добавляться новая запись, содержащая одни и те же данные. Разумеется имеет смысл добавлять в базу данные, введенные пользователем.

В примере 2.1 приведена HTML-форма для добавления новых книг в базу данных.

Пример 2.1

HTML-форма добавления новых книг
ISBN
Автор
Название
Цена
Количество

Результаты заполнения этой формы передаются в insert_book.php.

Пример 2.2

Программа добавления новых книг (файл insert_book.php) Пожалуйста, вернитесь назад и закончите ввод"); } $isbn = trim ($_POST["isbn"]); $author = trim ($_POST["author"]); $title = trim ($_POST["title"]) ; $isbn = addslashes ($isbn); $author = addslashes ($author); $title = addslashes ($title) ; $db = "sample"; $link = mysql_connect(); if (!$link) die ("Невозможно подключение к MySQL"); mysql_select_db ($db) or die ("Невозможно открыть $db"); $query = "INSERT INTO books VALUES ("" .$isbn."", "".$author."", "".$title."", "" .floatval($_POST["price"])."", "".intval($_POST["quantity"])."")"; $result = mysql_query ($query); if ($result) echo "Книга добавлена в базу данных."; mysql_close ($link); ?>

В примере 2.2 введенные строковые данные обработаны функцией addslashes() . Эта функция добавляет обратные слеши перед одинарными кавычками ("), двойными кавычками ("), обратным слешем (\) и null-байтом. Дело в том, что по требованиям систаксиса запросов баз данных такие символы дожны заключаться в кавычки.

Для определения количества записей в результате запроса используется функция mysql_num_rows() .

Все записи результата запроса можно просмотреть в цикле. Перед этим с помощью функции mysql_fetch_ для каждой записи получают ассоциативный массив.

В примере 3.1 приведена HTML-форма для поиска определенных книг в базе данных.

Пример 3.1

HTML-форма поиска книг
Ищем по:

Что ищем:

Результаты заполнения этой формы передаются в search_book.php.

Пример 3.2

Пожалуйста, вернитесь назад и закончите ввод"); $searchterm = addslashes ($searchterm); $link = mysql_pconnect (); if (!$link) die ("Невозможно подключение к MySQL"); $db = "sample"; mysql_select_db ($db) or die ("Невозможно открыть $db"); $query = "SELECT * FROM books WHERE " .$_POST["searchtype"]." like "%".$searchterm."%""; $result = mysql_query ($query); $n = mysql_num_rows ($result); for ($i=0; $i<$n; $i++) { $row = mysql_fetch_array($result); echo "

".($i+1). $row["title"]. "



"; } if ($n == 0) echo "Ничего не можем предложить. Извините"; mysql_close ($link); ?>

Альтернативный вариант

Программа поиска книг (файл search_book.php) Пожалуйста, вернитесь назад и закончите ввод"); $searchterm = addslashes ($searchterm); mysql_connect() or die ("Невозможно подключение к MySQL"); mysql_select_db ("sample") or die ("Невозможно открыть БД"); $result = mysql_query ("SELECT * FROM books WHERE ".$_POST["searchtype"]." like "%".$searchterm."%""); $i=1; while($row = mysql_fetch_array($result)) { echo "

".($i++) . $row["title"]."
"; echo "Автор: ".$row["author"]."
"; echo "ISBN: ".$row["ISBN"]."
"; echo "Цена: ".$row["price"]."
"; echo "Количество: ".$row["quantity"]."

"; } if ($i == 1) echo "Ничего не можем предложить. Извините"; mysql_close(); ?>

Итак, как работает архитектура Web-баз данных:

  1. Web-браузер пользователя выдает HTTP-запрос определенной Web-страницы. Например, пользователь, используя HTML-форму, ищет все книги о PHP. Страница обработки формы называется search_book.php.
  2. Web-сервер принимает запрос на search_book.php, извлекает этот файл и передает на обработку механизму PHP.
  3. PHP выполняет соединение с MySQL-сервером и отправляет запрос.
  4. Сервер принимает запрос к базе данных, обрабатывает его и отправляет результат (список книг) обратно механизму PHP.
  5. Механизм PHP завершает выполнение сценария, форматирует результат запроса в HTML. После этого результат в виде HTML возвращается Web-серверу.
  6. Web-сервер пересылает HTML в браузер, и пользователь имеет возможность просмотреть запрошенный список книг.

Использование механизма транзакций

Использование механизма транзакция на примере как передать деньги от одного человека другому

If(mysql_query ("BEGIN") && mysql_query ("UPDATE money SET amt = amt - 6 WHERE name = "Eve"") && mysql_query ("UPDATE money SET amt = amt + 6 WHERE name = "Ida"") && mysql_query ("COMMIT")){ echo "Успешно"; }else{ mysql_query ("ROLLBACK"); echo "Не успешно"; }

SELECT … FOR UPDATE

Если Вы запускаете несколько процессов, которые делают select запрос к одной и той же таблице, то они могут выбрать одну и ту же запись одновременно.

Чтобы избежать вышеупомянутой ситуации необходимо выполнить не просто SELECT запрос, а его расширенную версию, о которой многие и не подозревают: SELECT … FOR UPDATE.

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

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

Как оптимизировать MySQL запросы?


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

Почему MySQL?

Сегодня идет много разговоров о Dig Data и других новых технологиях. NoSQL и облачные решения это супер, но много популярного софта (такого как WordPress, phpBB, Drupal) до сих пор работает на MySQL. Миграция на новейшие решения может вылиться не только в изменении конфигурации на серверах. К тому же, эффективность MySQL до сих пор на уровне, особенно версия Percona.

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

Хорошее понимание SQL это важнейший инструмент для веб-разработчика, именно он позволит эффективно оптимизировать и использовать реляционные базы данных. В этой статье мы сфокусируемся на популярной открытой базе данных, часто используется в связке с PHP, и это MySQL.

Для кого эта статья?

Для веб-разработчиков, архитекторов и разработчиков баз данных и системных администраторов, хорошо знакомых с MySQL. Если раньше вы не использовали MySQL, эта статья может не принести вам пользы, но я все равно буду стараться быть как можно более информативным и полезным даже для новичков в MySQL.

Сначала бэкап

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

Делать бэкапы MySQL просто, используя утилиту mysqldump:

$ mysqldump myTab > myTab-backup.sql Вы можете узнать больше о mysqldump .

Что делает запрос медленным?

Вот общий список факторов, влияющих на скорость выполнения запросов и нагрузки сервера:

  • индексы таблиц;
  • условие WHERE(и использования внутренних функций MySQL, например, таких как IF или DATE);
  • сортировка по ORDER BY;
  • частое повторение одинаковых запросов;
  • тип механизма хранения данных (InnoDB, MyISAM, Memory, Blackhole);
  • не использование версии Percona;
  • конфигурации сервера (my.cnf / my.ini);
  • большие выдачи данных (более 1000 строк);
  • нестойкое соединение;
  • распределенная или кластерная конфигурация;
  • слабое проектирование таблиц.
Далее мы обратимся ко всем этим проблемам. Также, установите Percona , если вы еще не используете эту встроенную замену стандартному MySQL - это придаст сильное увеличение мощности базы данных.

Что такое индексы?

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

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

Нестойкое соединение?

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

Уменьшаем частое повторение одинаковых запросов

Наиболее быстрый и эффективный способ, который я нашел для этого - это создание хранилища запросов и результатов их выполнения с помощью Memcached или Redis. С Memcache вы можете легко положить в кэш результат выполнения вашего запроса, например, следующим образом:

connect("localhost",11211); $cacheResult = $cache->get("key-name"); if($cacheResult){ //не нуждаемся в запросе $result = $cacheResult; } else { //запускаем ваш запрос $mysqli = mysqli("p:localhost","username","password","table"); //добавляйте p: для договременного хранения $sql = "SELECT * FROM posts LEFT JOIN userInfo using (UID) WHERE posts.post_type = "post" || posts.post_type = "article" ORDER BY column LIMIT 50"; $result = $mysqli->query($sql); $memc->set("key-name", $result->fetch_array(), MEMCACHE_COMPRESSED,86400); } //Пароль $cacheResult в шаблон $template->assign("posts", $cacheResult); ?> Теперь тяжелый запрос, использующий LEFT JOIN, будет выполняться только раз за каждые 86 400 секунд (то есть раз в сутки), что значительно уменьшит нагрузку MySQL сервера, оставив ресурсы для других соединений.

Примечание: Допишите p: в начале аргумента хоста MySQLi для создания постоянного соединения.

Распределенная или кластерная конфигурация

Когда данных становится все больше, и скорость вашего сервиса идет под уклон, паника может овладеть вами. Быстрым решением может стать распределения ресурсов (sharding). Однако я не рекомендую делать это, если вы не обладаете хорошим опытом, поскольку распределение по своей сути делает структуры данных сложнейшими.

Слабое проектирование таблиц

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

Обеспечение правильного проектирования для нужного использования является первостепенным в создании вашего приложения. Храните различные данные в различных таблицах (например, категории и статьи) и убедитесь, что отношения к другу (many to one) и один ко многим (one to many) могут быть легко связаны с идентификаторами (ID). Использование FOREIGN KEY в MySQL идеально подходит для хранения каскадных данных в таблицах.

При создании таблицы помните следующее:

  • Создавайте эффективные таблицы для решения ваших задач, а не заполняйте таблицы лишними данными и связями.
  • Не ожидайте от MySQL выполнения вашей бизнес логики или програмности - данные должны быть готовы к вставке строки вашей скриптовым языком. Например, если вам нужно отсортировать список в случайном порядке, сделайте это в массиве PHP, не используя ORDER BY из арсенала MySQL.
  • Используйте индексные типы UNIQUE для уникальных наборов данных и применяйте ON DUPLICATE KEY UPDATE, чтобы хранить дату обновленной, например, для того, чтобы знать, когда строка была в последний раз изменена.
  • Используйте тип данных INT для сохранения целых чисел. Если вы не укажете размер типа данных, MySQL сделает это за вас.
Основы оптимизации

Для эффективной оптимизации мы должны применять три подхода к вашему приложению:

  1. Анализ (логирование медленных запросов, изучение системы, анализ запросов и проектирование базы данных)
  2. Требования к исполнению (сколько пользователей)
  3. Ограничения технологий (скорость железа, неправильное использование MySQL)
Анализ может быть сделан несколькими путями. Сначала мы рассмотрим наиболее очевидные способы, чтобы заглянуть под капот вашей MySQL, в котором выполняются запросы. Самый первый инструмент оптимизации в вашем арсенале это EXPLAIN. Если добавить этот оператор перед вашим запросом по SELECT, результат запроса будет таким:

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

Колонка possible_keys покажет индексы, в которые MySQL имел доступ, чтобы выполнить запрос. Иногда нужно назначить индексы, чтобы запрос выполнялся быстрее. Колонка Extra покажет, были ли использованы дополнительные WHEREили ORDER BY. Наиболее важно обратить внимание, есть ли Using Filesort в выводе.

Что делает Using Filesort, указано в справке MySQL:

MySQL должен выполнить дополнительный проход, чтобы понять, как вернуть строки в отсортированном виде. Это сортировка происходит проходом по всем строкам в соответствии с типом объединения и сохраняет ключ к сортировке и указатель на строку для всех строк, совпадающих с условным выражением WHERE. Ключи сортируются и строки возвращаются в нужном порядке.
Лишний проход замедлит ваше приложение, этого нужно избегать, чего бы это ни стоило. Другой критический результат Extra, который мы должны избегать - это Using temporary. Он говорит о том, что MySQL пришлось создать временную таблицу для выполнения запроса. Очевидно, это ужасное использования MySQL. В таком случае результат запроса должен быть сохранен в Redis или Memcache и не выполняться пользователями лишний раз.

Чтобы избежать проблемы с Using Filesort мы должны увериться, что MySQL использует INDEX. Сейчас указано несколько ключей в possible_keys, из которых можно выбирать, но MySQL может выбрать только один индекс для финального запроса. Также индексы могут быть составлены из нескольких колонок, также вы можете ввести подсказки (хинты) для оптимизатора MySQL, указывая на индексы, что вы создали.

Хинтинг индексов

Оптимизатор MySQL будет использовать статистику, основанную на запросах таблиц, чтобы выбрать лучший индекс для выполнения запроса. Он действует достаточно просто, основываясь на встроенной статистической логике, поэтому имея несколько вариантов, не всегда делает правильный выбор без помощи хинтинга. Чтобы убедиться, что был использован правильный (или неправильный) ключ, воспользуйтесь ключевым словам FORCE INDEX, USE INDEX и IGNORE INDEX в вашем запросе. Вы можете прочитать больше о хинтинге индексов в справке MySQL .

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

В дополнение к EXPLAIN существует ключевое слово DESCRIBE. Вместе с DESCRIBE можно просматривать информацию из таблицы следующим образом:

Добавляем индекс

Для добавления индексов в MySQL надо использовать синтаксис CREATE INDEX. Есть несколько видов индексов. FULLTEXT Применяется для полнотекстового поиска, а UNIQUE - для хранения уникальных данных.

Чтобы добавить индекс в вашу таблицу, используйте следующий синтаксис:

Mysql> CREATE INDEX idx_bookname ON `books` (bookname(10)); Это создаст индекс на таблице books, которая будет использовать первые 10 букв из колонки, которая хранит названия книг и имеет тип varchar. В этом случае, любой поиск с запросом WHERE на название книги с совпадением до 10 символов будет давать такой же результат, как и просмотр всей таблицы от начала до конца.

Композитные индексы

Индексы имеют большое влияние на скорость выполнения запросов. Только назначения главного уникального ключа недостаточно - композитные ключи являются реальной областью применения в настройке MySQL, что иногда требует некоторых A/B проверок с использованием EXPLAIN.

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

Mysql> CREATE INDEX idx_composite ON users (username, active); Как только мы создали ключ на основе колонки username, в котором хранится имя пользователя и колонки active типа ENUM, определяющий, активен ли его аккаунт. Теперь все оптимизировано для запроса, который будет использовать WHERE для поиска валидного имени пользователя с активным аккаунтом (active = 1).

Насколько быстра ваша MySQL?

Включим профилирование, чтобы подробнее рассмотреть MySQL запросы. Это можно сделать, выполнив команду set profiling=1, после чего для просмотра результата надо выполнить show profiles.

Если вы используете PDO, выполните следующий код:

$db->query("set profiling=1"); $db->query("select headline, body, tags from posts"); $rs = $db->query("show profiles"); $db->query("set profiling=0"); // отключить профилирование после выполнения запроса $records = $rs->fetchAll(PDO::FETCH_ASSOC); // получить результаты профилирования $errmsg = $rs->errorInfo(); //Отлавливаем некоторые ошибки здесь То же самое можно сделать с помощью mysqli:

$db = new mysqli($host,$username,$password,$dbname); $db->query("set profiling=1"); $db->query("select headline, body, tags from posts"); if ($result = $db->query("SHOW profiles", MYSQLI_USE_RESULT)) { while ($row = $result->fetch_row()) { var_dump($row); } $result->close(); } if ($result = $db->query("show profile for query 1", MYSQLI_USE_RESULT)) { while ($row = $result->fetch_row()) { var_dump($row); } $result->close(); } $db->query("set profiling=0"); Это вернет вам профилированные данные, содержащие время выполнения запроса во втором элементе ассоциативного массива.

Array(3) { => string(1) "1" => string(10) "0.00024300" => string(17) "select headline, body, tags from posts" } Этот запрос выполнялся 0.00024300 секунд. Это довольно быстро, поэтому не будем беспокоиться. Но когда числа становятся большими, мы должны смотреть глубже. Перейдите к вашему приложению, чтобы потренироваться на рабочем примере. Проверьте константу DEBUG в конфигурации вашей базы данных, а затем начните изучать систему, включив вывод результатов профилирования с помощью функций var_dump или print_r. Так вы сможете переходить со страницы на страницу в вашем приложении, получив удобное профилирование системы.

Полный аудит работы базы вашего сайта

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

Чтобы включить логирование в MySQL 5.1.6 используйте глобальную переменную log_slow_queries, также вы можете отметить файл для логирования с помощью переменной slow_query_log_file. Это можно сделать, выполнив следующий запрос:

Set global log_slow_queries = 1; set global slow_query_log_file = /dev/slow_query.log; Также это можно указать в файлах конфигурации /etc/my.cnf или my.ini вашего сервера.

После внесения изменений не забудьте перезагрузить MySQL сервер необходимой командой, например service mysql restart, если вы используете Linux.

В версиях MySQL после 5.6.1 переменная log_slow_queries обозначена как устаревшая и вместо нее используется slow_query_log. Также для более удобного дебаггинга можно включить вывод в таблице, задав переменной log_output значение TABLE, однако эта функция доступна только с MySQL 5.6.1.

Log_output = TABLE; log_queries_not_using_indexes = 1; long_query_time = 1; Переменная long_query_time определяет количество секунд, после которых выполнение запроса считается медленным. Значение это 10, а минимум это 0. Также можно указать миллисекунды, используя дробь; сейчас я указал одну секунду. И теперь каждый запрос, который будет выполняться дольше 1 секунды, записывается в логи в таблице.

Логирование будет вестись в таблицах mysql.slow_log и mysql.general_log вашей MySQL базы данных. Чтобы выключить логирование, измените log_output на NONE.

Логирование на рабочем сервере

На рабочем сервере, который обслуживает клиентов, лучше применять логирование только на короткий период и для мониторинга нагрузки, чтобы не создавать лишней нагрузки. Если ваш сервис перегружен и необходимо безотлагательное вмешательство, попробуйте выделить проблему, выполнив SHOW PROCESSLIST, или обратитесь к таблице information_schema.PROCESSLIST, выполнив SELECT * FROM information_schema.PROCESSLIST;.

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

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

Логирование множества запросов

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

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

Горячий и холодный кэш

Количество запросов и нагрузка сервера имеет сильное влияние на исполнение, также может повлиять на время выполнения запросов. При разработке вы должны взять за правило, что выполнение каждого запроса должно быть не более доли миллисекунды (0.0xx или быстрее) на свободном сервере.

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

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

Исправление медленных запросов

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

Если вы пока не нашли медленного запроса, проверьте настройки long_query_time, если вы пользуетесь этим методом логирования. Иначе, проверив все ваши запросы профилирования (set profiling=1), составьте список запросов, отнимают больше времени, чем доля миллисекунд (0.000x секунд) и начнем из них.

Распространенные проблемы

Вот шесть самых распространенных проблем, которые я находил, оптимизируя MySQL запросы:

ORDER BY и filesort

Предотвращение filesort иногда невозможно из-за выражения ORDER BY. Для оптимизации сохраните результат в Memcache, или выполните сортировку в логике вашего приложения.

Использование ORDER BY вместе с WHERE и LEFT JOIN

ORDER BY очень замедляет выполнение запросов. Если это возможно, старайтесь не использовать ORDER BY. Если же вам необходима сортировка, то используйте сортировку по индексам.

Применение ORDER BY по временным колонками

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

Игнорирование индекса FULLTEXT

Использование LIKE это самый лучший способ сделать полнотекстовый поиск медленным.

Беспричинный выбор большого количества строк

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

Чрезмерное использование JOIN вместо создания композитных таблиц или представления

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

Итак

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

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

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



© 2024 beasthackerz.ru - Браузеры. Аудио. Жесткий диск. Программы. Локальная сеть. Windows