Базы Данных 11 мин чтения 2026-08-23

MySQL / MariaDB грузит процессор на 100%: практический поиск тяжелых запросов, оптимизация индексов и тюнинг my.cnf

Полный инженерный мануал по устранению 100% загрузки процессора в MySQL и MariaDB: быстрый перехват зависших транзакций, разбор лога медленных запросов, чтение EXPLAIN, проектирование покрывающих индексов и эталонный конфиг my.cnf под разные объемы RAM.

Инженерная редакция SysKit Проверено инженерами SysKit.ru

Ситуация, когда процесс mysqld утилизирует все доступные ядра процессора на 100%, парализует веб-приложения: страницы интернет-магазинов начинают открываться по 15–30 секунд, Nginx возвращает ошибки 504 Gateway Time-out, а очередь клиентских соединений лавинообразно растет.

В 90% случаев причина 100% CPU кроется не в нехватке физических ядер VDS, а в нескольких неотфильтрованных тяжелых запросах, выполняющих полное сканирование таблиц (Full Table Scan), неоптимальных сортировках во временных файлах на диске (Using filesort) или дефолтных параметрах my.cnf, рассчитанных на серверы двадцатилетней давности.

1. Симптомы 100% CPU и экстренная реанимация сервера (First Aid)

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

Экспресс-диагностика через консоль Linux:

# Смотрим распределение потоков процесса mysqld по ядрам
pidstat -t -p $(pidof mysqld) 1 5

# Проверяем задержки ввода-вывода (I/O Wait)
iostat -xz 1 5

Просмотр активных запросов в MySQL CLI:

-- Заходим в консоль MySQL
SHOW FULL PROCESSLIST;

-- Либо выборка только активных запросов, выполняющихся дольше 2 секунд:
SELECT id, user, host, db, command, time, state, LEFT(info, 100) AS query 
FROM information_schema.processlist 
WHERE command != 'Sleep' AND time > 2 
ORDER BY time DESC;
⚠️
Опасные состояния потоков в Processlist:
  • Copying to tmp table / Converting HEAP to MyISAM: запрос создал временную таблицу в памяти, превысил лимиты и теперь сбрасывается на диск.
  • Sorting result / Using filesort: сортировка миллионов строк без использования B-Tree индекса.
  • Sending data: сервер читает и обрабатывает строки для запроса (свидетельствует о Full Table Scan).
  • Locked / Waiting for table metadata lock: запрос заблокирован долгой транзакцией или операцией ALTER TABLE.

Экстренное завершение зависшего запроса (Kill Query):

Если один «дикий» аналитический запрос положил весь продакшен, снимите его выполнение по ID из Processlist:

-- Прервать выполнение только запроса (соединение остается живым):
KILL QUERY 148290;

-- Либо принудительно разорвать соединение с клиентом:
KILL 148290;

2. Включение и глубокий анализ Slow Query Log

Чтобы не ловить медленные запросы вручную в реальном времени, настройте встроенный Slow Query Log. Он с миллисекундной точностью протоколирует все запросы, превышающие заданный порог времени или не использующие индексы.

Конфигурация журнала в my.cnf:

Откройте конфигурационный файл /etc/mysql/mysql.conf.d/mysqld.cnf (или /etc/my.cnf в CentOS/AlmaLinux) и добавьте в секцию [mysqld]:

[mysqld]
# Включение журнала медленных запросов
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log

# Порог срабатывания: логировать все запросы дольше 0.5 секунды
long_query_time = 0.5

# Логировать запросы без индексов (даже если они выполняются быстрее порога)
log_queries_not_using_indexes = 1

# Игнорировать таблицы с количеством строк менее 100
min_examined_row_limit = 100

Включение на лету без перезапуска службы MySQL:

SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.5;
SET GLOBAL log_queries_not_using_indexes = 'ON';

Агрегация и поиск главных виновников через mysqldumpslow:

Сырой лог может содержать миллионы строк. Используйте утилиту mysqldumpslow для группировки одинаковых запросов по шаблону:

# Топ-10 самых частых медленных запросов (сортировка по количеству вызовов)
mysqldumpslow -s c -t 10 /var/log/mysql/mysql-slow.log

# Топ-10 запросов, съевших больше всего суммарного процессорного времени
mysqldumpslow -s t -t 10 /var/log/mysql/mysql-slow.log

# Топ-10 запросов с наибольшим средним числом просканированных строк
mysqldumpslow -s r -t 10 /var/log/mysql/mysql-slow.log
💡
Профессиональный инструмент: Percona pt-query-digest
Для глубокого аудита установите пакет percona-toolkit и запустите pt-query-digest /var/log/mysql/mysql-slow.log. Утилита построит наглядный отчет с распределением нагрузки по времени суток, 95-м перцентилем задержек и хэшами SQL-паттернов.

3. Чтение плана выполнения EXPLAIN: поиск Full Table Scan

Получив проблемный SQL-запрос, подставьте перед ним команду EXPLAIN (а в MySQL 8.0+ — EXPLAIN ANALYZE), чтобы увидеть, как оптимизатор СУБД строит план выполнения.

EXPLAIN SELECT o.id, o.created_at, u.name 
FROM orders o 
JOIN users u ON o.user_id = u.id 
WHERE o.status = 'paid' AND o.created_at >= '2026-01-01' 
ORDER BY o.created_at DESC LIMIT 20;

Ключевые колонки вывода EXPLAIN:

Поле Значение Оценка производительности
type ALL Критично: Полное сканирование таблицы (Full Table Scan). Перебираются все строки с диска.
type index / range Приемлемо: Сканирование дерева индекса целиком (index) или выборка по диапазону (range: >=, BETWEEN, IN).
type ref / eq_ref / const Идеально: Точечная выборка по первичному ключу или уникальному индексу (1–2 чтения).
key NULL vs name_idx Если NULL — индекс не выбран оптимизатором.
rows Число (напр. 500 000) Примерное число строк, которое MySQL обязан прочитать для построения результата.
Extra Using filesort / Using temporary Тяжелая сортировка и создание временных таблиц в памяти/на диске.
Extra Using index Покрывающий индекс (Covering Index): данные взяты прямо из RAM без обращения к таблице.

Типичные ошибки в SQL-запросах, убивающие индексы:

  • Обертывание индексированного поля в функцию:
    ❌ WHERE DATE(created_at) = '2026-08-23' (индекс B-Tree не используется, так как функция вычисляется для каждой строки).
    ✅ WHERE created_at >= '2026-08-23 00:00:00' AND created_at <= '2026-08-23 23:59:59'.
  • Неявное приведение типов (Type Casting):
    ❌ WHERE phone = 79991234567 (если поле phone имеет тип VARCHAR, MySQL преобразует каждую строку таблицы в число, отключая индекс).
    ✅ WHERE phone = '79991234567'.
  • Поиск по подстроке с ведущим процентом:
    ❌ WHERE title LIKE '%сервер%' (B-Tree индекс ищет только по началу строки).
    ✅ Использовать полнотекстовый индекс FULLTEXT(title) и конструкцию MATCH(title) AGAINST('сервер').

4. Проектирование правильных B-Tree и покрывающих индексов

Если запрос фильтрует по нескольким полям и сортирует результат, одиночных индексов на каждую колонку недостаточно. Требуется составной индекс (Composite Index), созданный по правилу левого префикса:

Формула составного индекса: (Равенства ➔ Диапазоны ➔ Сортировка)

Пример оптимизации медленного запроса заказов:

-- Запрос:
SELECT id, user_id, amount, created_at 
FROM orders 
WHERE status = 'completed' AND created_at >= '2026-01-01' 
ORDER BY created_at DESC;

-- ❌ Плохой индекс (одиночный):
ALTER TABLE orders ADD INDEX idx_status (status);

-- ✅ Идеальный составной покрывающий индекс:
ALTER TABLE orders ADD INDEX idx_status_created_covering (status, created_at, user_id, amount, id);
🚀
Сила покрывающего индекса (Covering Index):
Когда все поля из секций SELECT, WHERE и ORDER BY включены в индекс, MySQL считывает данные на 100% из оперативной памяти пула InnoDB, не совершая ни одного дискового чтения строк таблицы (в плане EXPLAIN появится отметка Using index). Время выборки падает с 2.8 секунд до 0.003 секунды!

5. Тюнинг my.cnf под объем RAM: буферы InnoDB и временные таблицы

После оптимизации структуры запросов необходимо выделить СУБД достаточный объем оперативной памяти. Главное правило: активные данные и индексы должны целиком помещаться в RAM.

Эталонные параметры для файла /etc/mysql/mysql.conf.d/mysqld.cnf:

[mysqld]
# -------------------------------------------------------------
# 1. ПУЛ БУФЕРОВ INNODB (Ключевой параметр производительности)
# Выделяйте 60-70% от всей RAM для выделенного сервера БД,
# либо 40-50%, если на VDS также работают Nginx и PHP-FPM.
# -------------------------------------------------------------
innodb_buffer_pool_size = 4G          # Для сервера с 8 GB RAM
innodb_buffer_pool_instances = 4      # 1 инстанс на каждый 1 GB пула
innodb_log_file_size = 512M           # 25% от размера buffer_pool
innodb_log_buffer_size = 64M

# -------------------------------------------------------------
# 2. ОПТИМИЗАЦИЯ ДИСКОВОГО ВВОДА-ВЫВОДА (I/O)
# -------------------------------------------------------------
# Значение 2 дает прирост скорости записи в 5-10 раз на NVMe VDS:
# сброс на диск происходит раз в секунду, а не на каждую транзакцию.
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
innodb_io_capacity = 2000             # Для быстрых NVMe SSD
innodb_io_capacity_max = 4000
innodb_file_per_table = 1

# -------------------------------------------------------------
# 3. ВРЕМЕННЫЕ ТАБЛИЦЫ В ПАМЯТИ (Предотвращение Disk I/O)
# -------------------------------------------------------------
tmp_table_size = 128M
max_heap_table_size = 128M

# -------------------------------------------------------------
# 4. БУФЕРЫ НА СОЕДИНЕНИЕ (Осторожно, умножаются на max_connections!)
# -------------------------------------------------------------
max_connections = 150
join_buffer_size = 2M
sort_buffer_size = 2M
read_rnd_buffer_size = 1M

# -------------------------------------------------------------
# 5. КЭШ ОТКРЫТЫХ ТАБЛИЦ И ПОТОКОВ
# -------------------------------------------------------------
table_open_cache = 4000
table_definition_cache = 2000
thread_cache_size = 50

Сводная таблица сайзинга innodb_buffer_pool_size под тариф VDS:

Объем RAM на VDS Стек на одном сервере (LEMP) Выделенный сервер БД (Only DB) innodb_buffer_pool_instances
2 GB RAM 512 MB – 768 MB 1.2 GB 1
4 GB RAM 1.5 GB – 2.0 GB 2.8 GB 2
8 GB RAM 3.5 GB – 4.5 GB 5.5 GB – 6.0 GB 4
16 GB RAM 8.0 GB – 10.0 GB 12.0 GB 8

6. Автоматический аудит базы через MySQLTuner

Перед ручной правкой конфига рекомендуется дать серверу проработать под рабочей нагрузкой минимум 24–48 часов, после чего запустить скрипт MySQLTuner:

# Скачивание и запуск официального скрипта аудита
wget http://mysqltuner.pl/ -O mysqltuner.pl
chmod +x mysqltuner.pl
perl mysqltuner.pl --user root --password "ВАШ_ПАРОЛЬ"

На какие секции отчета обратить особое внимание:

  • Storage Engine Statistics: убедитесь, что все таблицы переведены в InnoDB. Если остались устаревшие MyISAM — конвертируйте их через ALTER TABLE tbl_name ENGINE=InnoDB;.
  • InnoDB Buffer Pool Hitrate: должен быть выше 99.5%. Если он ниже — объем буферного пула недостаточен и запросы постоянно уходят в чтение с диска.
  • Temporary Tables: процент временных таблиц, созданных на диске (Created_tmp_disk_tables), должен быть менее 15%. Если больше — увеличивайте tmp_table_size и max_heap_table_size.
  • Table Cache Hitrate: если показатель ниже 80% — увеличивайте table_open_cache.

7. Когда оптимизации мало: требования к vCPU и дискам VDS

Если база данных превышает 50–100 ГБ, а поток транзакций превышает 500+ запросов в секунду, даже идеально оптимизированные B-Tree индексы требуют высокой параллельности вычислений и быстрого дискового ввода-вывода.

✅ Обязательные требования к железу под MySQL:

  • Высокочастотные ядра (High Frequency CPU 3.8–4.5+ GHz): выполнение одного сложного SQL-запроса происходит в один поток. Частота ядра важнее их количества.
  • NVMe SSD в RAID-10: скорость случайного чтения (4K Random Read) должна быть выше 30 000 IOPS.
  • Достаточный запас RAM: размер оперативной памяти должен превышать суммарный размер активных индексов.

❌ Типичные аппаратные ловушки дешевых тарифов:

  • HDD или медленные SATA SSD: приводят к взрывному росту I/O Wait до 40-70%, при котором процессор простаивает в ожидании данных.
  • Shared vCPU с жестким троттлингом: при превышении лимита хостер принудительно урезает такт процессора до 10-20%.
  • Дефицит Swap: при нехватке памяти ядро Linux мгновенно убивает mysqld через OOM Killer.

Рекомендуемые VDS-провайдеры

Отказоустойчивые сервера с быстрыми NVMe и каналом до 1 Гбит/с

Выбор для Баз Данных
⚡

Timeweb Cloud

Высокочастотные ядра до 5.0 ГГц, Managed MySQL/MariaDB кластеры, быстрые NVMe накопители до 50 000 IOPS и автоматические ежедневные бэкапы.

Конфигурация
от 2 vCPU / 4 GB RAM / 40 GB NVMe
Tier III Enterprise
🔷

Selectel Cloud

Облачные базы данных с гарантированным SLA 99.95%, изолированные vCPU без оверселлинга, прямой канал 1 Гбит/с в дата-центрах Москвы и СПб.

Конфигурация
от 2 vCPU / 4 GB RAM / 50 GB NVMe
Надежность и простота
🟠

Beget VPS

Посекундная тарификация ресурсов, удобное управление базами в 1 клик, встроенный мониторинг нагрузки CPU и круглосуточная поддержка 24/7.

Конфигурация
от 2 vCPU / 2 GB RAM / 30 GB NVMe

8. Итоговый чек-лист оптимизации MySQL/MariaDB

📋
Пошаговый алгоритм действий при 100% CPU:
  1. Выполнить SHOW FULL PROCESSLIST; и при необходимости убить зависший запрос через KILL QUERY <id>;.
  2. Включить Slow Query Log с порогом long_query_time = 0.5 и собрать лог за 24 часа.
  3. Агрегировать топ-10 запросов через mysqldumpslow -s t /var/log/mysql/mysql-slow.log.
  4. Прогнать каждый проблемный запрос через EXPLAIN и убедиться в отсутствии type: ALL и Using filesort.
  5. Создать составные или покрывающие индексы по формуле (WHERE_равенство, WHERE_диапазон, ORDER_BY).
  6. Выставить innodb_buffer_pool_size в 50–70% от свободной RAM сервера.
  7. Перевести innodb_flush_log_at_trx_commit = 2 для VDS на NVMe.
  8. Запустить mysqltuner.pl и проверить отсутствие создания временных таблиц на диске.

⚡ Нужен высокоскоростной сервер для тяжелой базы данных?

Разверните VDS с гарантированными высокочастотными ядрами до 5.0 ГГц, быстрыми NVMe дисками и защитой от перегрузок у проверенных российских хостинг-провайдеров.

Выбрать сервер с NVMe под БД →

Инструменты SysKit по теме статьи

Бесплатные утилиты для проверки и диагностики вашего сервера

Вопросы и ответы (FAQ)

Популярные вопросы вебмастеров и сисадминов по данной теме

Почему после добавления индекса MySQL все равно делает Full Table Scan (type: ALL)?
Оптимизатор MySQL оценивает кардинальность данных. Если по условию запроса выбирается более 15–20% от общего числа строк таблицы, оптимизатор считает, что последовательное чтение всей таблицы с диска выполнится быстрее, чем случайные прыжки по дереву индекса. Вторая частая причина — использование функций на поле (например, WHERE YEAR(date) = 2026) или несовпадение кодировок таблиц при JOIN.
Безопасно ли ставить innodb_flush_log_at_trx_commit = 2 на продакшене?
Да, для 99% веб-проектов и CMS это безопасно. Сброс лога транзакций на диск происходит операционной системой раз в секунду. При аварийном падении службы MySQL данные не теряются вообще. Потеря транзакций за 1 секунду возможна только при внезапном отключении питания всего сервера.
Можно ли менять innodb_buffer_pool_size без перезагрузки MySQL?
Начиная с MySQL 5.7 и MariaDB 10.2.2+, параметр innodb_buffer_pool_size является динамическим. Вы можете изменить его размер на лету командой SET GLOBAL innodb_buffer_pool_size = 4294967296 (4GB). Процесс ресайза происходит асинхронно кусками (chunks).

Читайте также в блоге SysKit

Смежные руководства и полезные технические статьи