Оптимизация базы данных интернет-магазина: медленные запросы, индексы, настройка структуры таблиц
1. Почему база данных WordPress тормозит
WordPress использует MySQL в качестве базы данных. Со стандартной структурой таблиц небольшой сайт работает нормально, но по мере роста контента он становится всё медленнее. Причина не в конфигурации сервера, а в двух особенностях архитектуры: таблица wp_postmeta хранит все произвольные поля в одной ключ-значение таблице, поэтому для получения параметров одного товара нужно несколько JOIN; таблица wp_options смешивает все настройки вместе, и при каждой загрузке админки приходится сканировать её целиком.
Одна страница WordPress в среднем обращается к базе данных 12–20 раз, и большинство этих запросов идёт к wp_postmeta. Если у вас установлено 10 плагинов и каждый пишет произвольные поля в wp_postmeta, эта таблица может разрастись до сотен тысяч строк. Тогда любой запрос страницы с meta_query вынужден сканировать сотни тысяч строк — отсюда и берутся медленные запросы.
| Типичные источники медленных запросов | Задействованные таблицы | Типичные симптомы | Доля |
|---|---|---|---|
| Фильтрация записей по произвольным полям | wp_postmeta | Страница фильтрации товаров грузится дольше 3 секунд | около 40% |
| Загрузка настроек плагинов в админке | wp_options | После каждого сохранения админка на несколько секунд показывает белый экран | около 20% |
| Страницы архивов по меткам и рубрикам | wp_term_relationships | При большом числе записей архивы замедляются | около 15% |
| Запросы к таблицам комментариев и пользователей | wp_comments/wp_users | Список комментариев или управление пользователями тормозит | около 10% |
| Сложные запросы самих плагинов | Собственные таблицы плагинов | Какая-то конкретная страница с функционалом особенно медленная | около 15% |
Когда база данных тормозит, кэш страниц может частично это скрыть — при попадании в кэш запросы к базе вообще не выполняются. Но при промахе кэша (сразу после публикации записи, при заходе на некешированную страницу) медленные запросы становятся видны. Поэтому оптимизацию базы данных и стратегию кэширования нужно рассматривать вместе, см.Трёхуровневая стратегия кэширования для интернет-магазина. Если база данных уже тормозит настолько, что это влияет на Core Web Vitals, приоритеты оптимизации см. в Оптимизация Core Web Vitals.
2. Лог медленных запросов: как найти медленный SQL
Первый шаг к оптимизации базы данных — не добавить индекс, а найти, какой именно SQL тормозит. На глаз это не угадать: вам кажется, что медленный один запрос, а на деле — совсем другой. В MySQL есть встроенный лог медленных запросов: включите его, и все SQL, превысившие порог, будут записаны.
В конфигурации MySQL (my.cnf или my.ini) добавьте три строки: slow_query_log=1, long_query_time=1, slow_query_log_file=/var/log/mysql/slow.log. Это значит: включить лог медленных запросов, записывать запросы дольше 1 секунды, писать лог в указанный файл. Через сутки работы агрегируйте лог по типам запросов с помощью mysqldumpslow или pt-query-digest и найдите TOP 10 самых медленных SQL.
| Шаг | Действия | Инструмент/команда | Цель |
|---|---|---|---|
| 1. Включить медленный лог | Добавить три строки в my.cnf | Перезапустить MySQL | Запись медленных SQL |
| 2. Поработать сутки | Обычный трафик сайта в течение дня | — | Сбор реальных медленных запросов |
| 3. Агрегация и анализ | Группировка по отпечатку SQL | mysqldumpslow -s t | Найти TOP 10 |
| 4. EXPLAIN по каждому | Посмотреть план выполнения | EXPLAIN SQL-запрос | Проверить, используется ли индекс |
| 5. Повторный замер после оптимизации | Сравнить время до и после | SHOW PROFILE или замер времени | Убедиться в результате |
Пример из практики: вы замечаете, что страница фильтра товаров работает особенно медленно. Открываете лог медленных запросов — и TOP 1 там SQL с meta_query, который ищет по произвольному полю в таблице wp_postmeta. Анализируете через EXPLAIN: в столбце type стоит ALL (полное сканирование таблицы), в столбце rows — 120 000 строк. То есть этот SQL каждый раз прочёсывает всю wp_postmeta. Вот тут и становится ясно, что нужно добавить индекс на поле meta_key, а не апгрейдить сервер.
Не держите лог медленных запросов включённым постоянно — он сам создаёт нагрузку на IO. Соберите данные за сутки и выключите, либо поднимите порог до 2 секунд, чтобы писались только самые медленные. Для повседневного мониторинга используйте инструменты мониторинга производительности базы данных (например, Prometheus+Grafana), см.Чек-лист мониторинга и оповещений сайта.
Три. Оптимизация индексов: на какие поля их добавлять
В результатах EXPLAIN важнее всего два столбца: type и rows. Если в type стоит ALL — запрос сканирует всю таблицу, индекс обязателен. Если ref или eq_ref — индекс уже используется, трогать не нужно. Столбец rows показывает, сколько строк MySQL планирует просмотреть: чем больше число, тем хуже. До 10 000 строк терпимо, свыше 100 000 — пора оптимизировать.
В базе WordPress индексы нужнее всего таблице wp_postmeta. По умолчанию там есть только индекс по post_id, а запросы часто фильтруют по meta_key и meta_value. Добавьте составной индекс (meta_key, meta_value) — запросы с фильтром по произвольному полю ускорятся более чем в 10 раз. Важно: именно составной индекс, а не отдельный по meta_key. Если добавить только meta_key, при фильтрации по meta_value всё равно придётся обращаться к таблице.
| Таблица | Сценарий запроса | Рекомендуемый индекс | Результат |
|---|---|---|---|
| wp_postmeta | Фильтр по meta_key+meta_value | (meta_key, meta_value(191)) | Ускорение более чем в 10 раз |
| wp_postmeta | Выборка meta по post_id | Индекс по post_id уже есть | Трогать не нужно |
| wp_options | Поиск настроек по option_name | Индекс по option_name уже есть | Трогать не нужно |
| wp_comments | Выборка комментариев по post_id | Индекс по comment_post_ID уже есть | Трогать не нужно |
| wp_posts | Выборка по post_type + post_status | Составной индекс уже есть | Трогать не нужно |
| Собственные таблицы плагинов | Фильтрация по дате и статусу | Смотрите EXPLAIN и решайте | Добавлять по необходимости |
Контринтуитивный вывод: многие при виде медленного запроса сразу бросаются добавлять индекс, но индексы не бесплатны. Каждый индекс занимает место на диске, а при каждом INSERT или UPDATE MySQL обновляет все индексы сразу. У таких таблиц, как wp_options, где записей больше, чем чтений (настройки сохраняются при каждом действии в админке), лишние индексы только замедлят запись. Правильный подход такой: таблицам с частым чтением и редкой записью (wp_postmeta, wp_posts) индексы добавляйте смело; таблицам с частой записью и редким чтением (wp_options, wp_comments) — осторожно, лучше вообще обойтись без них.
Прежде чем добавлять индекс, обязательно протестируйте на staging. Если выполнить ALTER TABLE прямо на продакшене, большая таблица может заблокироваться на несколько минут, и сайт сразу отдаст 502. Для продакшена добавляйте индексы через инструменты онлайн-DDL вроде pt-online-schema-change или gh-ost — они не блокируют таблицу. Опыт crater по обслуживанию баз данных зарубежных сайтов — здесь Технологический стек crater и четыре собственных плагина.
4. Оптимизация структуры таблиц: подводные камни wp_postmeta и wp_options
Некоторые проблемы индексами не решить — нужно менять структуру таблиц. Именно так устроен wp_postmeta с его хранением «ключ — значение»: все произвольные поля лежат в одной таблице, и чтобы получить характеристики одного товара, приходится делать несколько JOIN. Когда товаров становятся тысячи, а произвольных полей — десятки, таблица раздувается до сотен тысяч строк, и от оптимизации индексов уже мало толку.
Здесь есть два пути. Первый — заменить postmeta собственной таблицей: вынести часто запрашиваемые произвольные поля в отдельную таблицу, хранить их по типам и обращаться к нужной таблице напрямую. Второй — использовать механизм autoload в wp_options: включить автозагрузку для настроек, которые читаются часто, а пишутся редко, чтобы не обращаться к таблице options каждый раз. Для B2B-сайта первый путь подойдёт сайтам со сложными параметрами товаров, второй — сайтам с большим числом плагинов.
| Вопрос | Симптом | Что улучшать | Риск |
|---|---|---|---|
| Раздувание wp_postmeta | Страницы фильтров тормозят, в таблице больше 100 000 строк | Вынести частые поля в свою таблицу | Правка кода, нужно тестирование |
| Слишком много autoload в wp_options | Админка тормозит, autoload больше 1 МБ | Удалить настройки неиспользуемых плагинов | Перед удалением сделайте бэкап |
| Слишком много черновиков и ревизий в wp_posts | Список записей тормозит, копятся мусорные ревизии | Удалить авторевизии и черновики | Перед удалением проверьте |
| Раздувание wp_commentmeta | Список комментариев тормозит | Отключить автосохранение спам-комментариев | Влияние небольшое |
| Большая таблица wp_users | Управление пользователями тормозит | Удалить неактивных пользователей | Легко удалить лишнее |
Пример: у промышленного сайта 50 товаров, у каждого — 20 пользовательских параметров (материал, мощность, размеры, точность и т. д.). Все они лежат в wp_postmeta, и чтобы открыть карточку товара, приходится JOIN’ить wp_postmeta 20 раз. Правильно — создать свою таблицу wp_product_specs и хранить все 20 параметров колонками в одной строке: тогда выборка параметров товара — это один SELECT без JOIN. Работы немного, а прирост производительности — на порядок.
Перед изменением структуры таблиц обязательно сделайте резервную копию базы данных. О том, как проверять бэкапы и восстановление, читайте в разделе Бэкап и восстановление сайта. Если базу уже взломали, порядок очистки и защиты описан в разделе Что делать после взлома сайта.
5. Проверка и поддержка после оптимизации
После добавления индекса или изменения структуры таблиц нельзя просто закрыть задачу. Нужно сделать три вещи. Первое: снова прогнать медленный SQL через EXPLAIN и убедиться, что в колонке type вместо ALL стоит ref или eq_ref. Второе: сравнить фактическое время выполнения до и после — убедиться, что медленный запрос действительно ускорился. Третье: неделю последить за логом медленных запросов — не появились ли там новые. Оптимизация часто переносит узкое место с одного участка на другой.
| Что проверяем | Способ проверки | Критерий нормы | Что делать, если проверка не пройдена |
|---|---|---|---|
| План выполнения EXPLAIN | Снова прогнать медленный SQL через EXPLAIN | type больше не ALL, rows упало на порядок | Индекс не работает — проверьте типы полей |
| Фактическое время запроса | Замерить время SQL напрямую | Снизилось с секунды и больше до 100 мс и меньше | Индекс подобран неверно — проанализируйте заново |
| Лог медленных запросов | Посмотреть лог через 3 дня | Старые медленные запросы исчезли или их стало заметно меньше | Появились новые медленные запросы — оптимизируйте дальше |
| Скорость записи | Замерить время INSERT/UPDATE | Запись заметно не замедлилась | Слишком много индексов — удалите лишние |
| Время загрузки страниц сайта | Замер в Lighthouse | LCP при промахе кэша<1.5秒 | Есть другие узкие места |
Оптимизация базы данных — не разовая работа. Рост контента, обновления плагинов и смена темы добавляют новые медленные запросы. Раз в месяц разбирайте лог медленных запросов и смотрите, изменился ли TOP 10. В регулярном обслуживании оптимизация базы — обязательный пункт, см.Шесть задач на месяц в период обслуживания сайта。独立站技术栈的整体选型框架见 Технологический стек crater, о том, как увязать обновление версии PHP с настройкой базы данных, — в разделе Руководство по поддержке сайта.
FAQ: частые вопросы
Как включить лог медленных запросов MySQL в WordPress?
Почему таблица wp_postmeta работает так медленно?
Замедлит ли индексация сайт?
Как читать результат EXPLAIN?
Заблокирует ли таблицу добавление индекса на продакшене?
Как часто нужно заниматься оптимизацией базы данных?
В заключение
В оптимизации базы данных самая частая ошибка — при первой же медленной странице апгрейдить сервер. Переходите с 1 ядра и 2 ГБ на 4 ядра и 8 ГБ, платите вдвое больше, а медленные запросы никуда не деваются — потому что узкое место не в CPU, а в полном сканировании без индекса. Правильный порядок всегда такой: сначала включить лог медленных запросов и найти конкретный медленный SQL, затем посмотреть план выполнения через EXPLAIN и только потом решать — добавлять индекс или менять структуру таблицы.
Три шага: первый — включите лог медленных запросов на сутки и через mysqldumpslow найдите TOP 10; второй — разберите каждый запрос через EXPLAIN, для запросов с type=ALL добавьте индекс, а в wp_postmeta в первую очередь составной индекс (meta_key, meta_value); третий — после оптимизации проверьте снова и убедитесь, что медленный SQL упал с 1 секунды до 100 миллисекунд. После этих трёх шагов вы увидите: конфигурация сервера не изменилась ни на йоту, а страницы стали быстрее в несколько раз.
Запомните одну фразу: база тормозит — сначала ищите медленный лог, а не апгрейдьте сервер. 80% медленных запросов — это полное сканирование таблицы без индекса, и один правильно добавленный индекс работает лучше, чем лишний шаг в конфигурации.
