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

Медленный запрос к базе данных может замедлить работу всего вашего PHP-приложения. Даже запрос, выполняющийся 500 мс, при запуске 100 раз за одну загрузку страницы добавляет 50 секунд времени ожидания. Я собрал 10 наиболее эффективных методов оптимизации MySQL, которые помогли мне ускорить реальные проекты. 

  1. Правильно используйте индексы Индексы — это самый важный элемент оптимизации. Добавляйте индексы для колонок, используемых в WHERE, JOIN и ORDER BY: 
 -- Check existing indexes
SHOW INDEX FROM users;

-- Add index to frequently searched column
ALTER TABLE users ADD INDEX idx_email (email);

-- Composite index for multiple columns
ALTER TABLE orders ADD INDEX idx_user_date (user_id, created_at);
 
  1. Используйте EXPLAIN для анализа запросов Всегда используйте EXPLAIN, чтобы понять, как MySQL выполняет ваш запрос: 
 EXPLAIN SELECT * FROM orders WHERE user_id = 5 AND status = 'pending';
 

Обращайте внимание на:
  type=ALL  → полное сканирование таблицы (ПЛОХО)
  type=ref  или  type=range  → используется индекс (ХОРОШО)

  1. Выбирайте только то, что вам нужно 
 // BAD - fetches all columns
$users = $db->query("SELECT * FROM users WHERE active = 1")->fetchAll();

// GOOD - fetch only needed columns
$users = $db->query("SELECT id, name, email FROM users WHERE active = 1")->fetchAll();
 
  1. Решите проблему N+1 запросов 
 // BAD: N+1 queries
$users = $db->query("SELECT * FROM users")->fetchAll();
foreach ($users as $user) {
    $posts = $db->query("SELECT * FROM posts WHERE user_id = {$user['id']}")->fetchAll();
}

// GOOD: Single JOIN query
$results = $db->query("
    SELECT u.id, u.name, p.title
    FROM users u
    LEFT JOIN posts p ON p.user_id = u.id
")->fetchAll();
 
  1. Всегда используйте пагинацию для больших выборок 
 -- Page 1 (20 results)
SELECT * FROM products
WHERE category_id = 5
ORDER BY created_at DESC
LIMIT 20 OFFSET 0;

-- For very large tables, use cursor pagination
SELECT * FROM products
WHERE id > :last_id
ORDER BY id ASC
LIMIT 20;
 
  1. Избегайте использования функций на индексируемых колонках 
 -- BAD: function prevents index usage
SELECT * FROM orders WHERE YEAR(created_at) = 2026;

-- GOOD: range query uses index
SELECT * FROM orders
WHERE created_at >= '2026-01-01'
AND created_at < '2027-01-01';
 
  1. Используйте подходящие типы данных 
 CREATE TABLE products (
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  price DECIMAL(10,2) NOT NULL,   -- not FLOAT for money
  is_active TINYINT(1) DEFAULT 1, -- not VARCHAR for booleans
  description TEXT                 -- only TEXT when needed
);
 
  1. Оптимизируйте JOIN-запросы 
 -- Ensure JOIN columns are indexed
ALTER TABLE orders ADD INDEX idx_customer_id (customer_id);

-- Prefer INNER JOIN over LEFT JOIN when possible
SELECT o.id, c.name
FROM orders o
INNER JOIN customers c ON c.id = o.customer_id
WHERE o.status = 'pending';
 
  1. Кэшируйте результаты запросов в PHP 
 function getCachedQuery($db, $key, $sql, $params = [], $ttl = 300) {
    $cacheFile = sys_get_temp_dir() . '/db_' . md5($key) . '.cache';

    if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < $ttl) {
        return unserialize(file_get_contents($cacheFile));
    }

    $stmt = $db->prepare($sql);
    $stmt->execute($params);
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);

    file_put_contents($cacheFile, serialize($result));
    return $result;
}
 
  1. Отслеживайте медленные запросы 
 -- Enable slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
 

Быстрый чек-лист
 [ ] Добавьте индексы для колонок в WHERE, JOIN, ORDER BY
 [ ] Запустите EXPLAIN для медленных запросов
 [ ] Замените SELECT * на конкретные колонки
 [ ] Исправьте проблему N+1 запросов с помощью JOIN-ов
 [ ] Добавьте LIMIT ко всем запросам
 [ ] Используйте диапазоны вместо функций на индексируемых колонках
 [ ] Используйте правильные типы данных
 [ ] Кэшируйте повторяющиеся запросы