Если ваш сайт на PHP начал тормозить, скорее всего, дело не в хостинге и не в кривых руках разработчика, а в запросах к базе данных. База данных — это сердце любого динамического сайта, и если она работает медленно, весь сайт превращается в черепаху. В этой статье я расскажу, как найти проблемные запросы, ускорить их и не сойти с ума при этом. Без сложных терминов, только практика.
Почему запросы тормозят: основные причины
Представьте, что база данных — это библиотека, а запрос — это запрос библиотекарю найти книгу. Если библиотекарь бегает по всем полкам без системы, поиск займет вечность. Так и с БД: если запрос написан неправильно или нет индексов, серверу приходится сканировать всю таблицу, чтобы найти нужные данные.
Вот главные виновники медленных запросов:
- Отсутствие индексов — как отсутствие каталога в библиотеке.
- Лишние данные — вы запрашиваете все колонки, хотя нужна одна.
- Запросы в цикле — N+1 проблема, когда вы делаете запрос для каждой записи.
- Неправильное использование JOIN — слишком много соединений или соединения без индексов.
Самое обидное, что часто эти проблемы можно исправить за пару минут, но из-за невнимательности мы теряем часы.
Индексы: ваш главный друг
Индекс — это структура, которая позволяет базе данных быстро находить нужные строки. Без индекса БД сканирует всю таблицу, а с индексом — ищет по отсортированной структуре, как по алфавиту в книге.
Допустим, у вас есть таблица пользователей:
CREATE TABLE users (
id INT PRIMARY KEY,
email VARCHAR(255),
name VARCHAR(100),
created_at TIMESTAMP
);Если вы часто ищете по email, добавьте индекс:
CREATE INDEX idx_email ON users(email);Но не переусердствуйте: каждый индекс замедляет вставку и обновление данных. Индексы — это как закладки в книге: много закладок — книга толще, и перелистывать сложнее.
Подготовленные запросы и кеширование
Подготовленные запросы (prepared statements) в PHP — это не только защита от SQL-инъекций, но и способ ускорить выполнение повторяющихся запросов. Когда вы используете PDO или MySQLi с подготовленными выражениями, база данных кеширует план выполнения запроса, что снижает нагрузку.
Пример на PDO:
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute(['user@example.com']);
$user = $stmt->fetch();А вот с кешированием результатов — можно использовать Memcached или Redis. Например, если у вас популярная страница, которая редко меняется, закешируйте результат запроса:
$cacheKey = 'user_profile_' . $userId;
$cached = $redis->get($cacheKey);
if ($cached === false) {
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$userId]);
$user = $stmt->fetch();
$redis->set($cacheKey, json_encode($user), 3600); // на 1 час
} else {
$user = json_decode($cached, true);
}Это как приготовить еду заранее и разогреть, когда нужно, вместо того чтобы готовить каждый раз с нуля.
Избегаем N+1 проблему
Классическая ошибка — запрос в цикле. Например, вы выводите список статей и для каждой статьи подтягиваете автора:
$articles = $pdo->query('SELECT * FROM articles')->fetchAll();
foreach ($articles as $article) {
$stmt = $pdo->prepare('SELECT name FROM users WHERE id = ?');
$stmt->execute([$article['author_id']]);
$author = $stmt->fetch();
echo $article['title'] . ' by ' . $author['name'];
}Если статей 100, будет 101 запрос. Это медленно. Решение — один запрос с JOIN:
$stmt = $pdo->query('SELECT articles.title, users.name FROM articles JOIN users ON users.id = articles.author_id');
$rows = $stmt->fetchAll();
foreach ($rows as $row) {
echo $row['title'] . ' by ' . $row['name'];
}Или, если JOIN не подходит, можно собрать все ID и сделать один запрос с IN:
$ids = array_column($articles, 'author_id');
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT id, name FROM users WHERE id IN ($placeholders)");
$stmt->execute($ids);
$users = $stmt->fetchAll();Анализ медленных запросов: включаем логи
MySQL умеет записывать все медленные запросы в специальный лог. Включите его, чтобы увидеть, какие запросы тормозят. В конфигурации MySQL (my.cnf) добавьте:
slow_query_log = 1
slow_query_log_file = /var/log/mysql-slow.log
long_query_time = 2Теперь все запросы, которые выполняются дольше 2 секунд, попадут в лог. Периодически проверяйте его и оптимизируйте проблемные места.
| Инструмент | Что делает |
|---|---|
| EXPLAIN | Показывает план выполнения запроса, помогает увидеть, используется ли индекс |
| mysqltuner | Скрипт, который анализирует конфигурацию MySQL и дает советы |
| Percona Toolkit | Набор инструментов для анализа и оптимизации запросов |
Что в итоге?
Оптимизация запросов — это не магия, а системный подход. Начните с малого: включите логи медленных запросов, найдите проблемные места, добавьте индексы, перепишите циклы с N+1 на JOIN. Через неделю вы заметите, как сайт летает.
И помните: не оптимизируйте то, что и так работает быстро. Сначала измерьте, потом режьте.
Студия WNDER


