Оптимизация базы данных интернет-магазина: медленные запросы, индексы, настройка структуры таблиц

Правильный порядок оптимизации базы данных WordPress: сначала включить лог медленных запросов, чтобы найти проблему, затем проанализировать план выполнения через EXPLAIN и только потом решать, добавлять индекс или менять структуру таблиц. Порог медленного запроса — 1 секунда, ежедневно смотрим TOP...

数据库优化四步:先定位再动手DB tuning in 4 steps: locate first, tune second wp_postmeta是WordPress最大的性能黑洞,80%慢查询和它有关wp_postmeta is WP’s biggest bottleneck; 80% slow queries hit it ① 开慢日志① Enable slow log long_query_time=1long_query_time=1s ② EXPLAIN分析② EXPLAIN the SQL 看type和rowsCheck type & rows ③ 加索引③ Add indexes 复合索引优先Composite first ④ 改表结构④ Schema fix 拆postmetaSplit postmeta 慢查询阈值:超过1秒的SQL记入慢日志,每天看TOP 10Slow threshold: log queries >1s; review daily TOP 10 EXPLAIN里type=ALL就是全表扫描,必须加索引;rows越大越危险type=ALL means full table scan; rows=越大越危险 索引不是越多越好:写多读少的表加索引会拖慢写入More indexes != better; write-heavy tables slow down wp_postmeta加(meta_key,meta_value)复合索引,查询能快10倍以上(meta_key,meta_value) composite index speeds up 10x+ 反直觉:加索引不是万能药,加错了反而更慢Counter-intuitive: indexes can slow you down if mis-added 每个索引都占磁盘、拖慢INSERT/UPDATE,wp_options这种写多的表别乱加Indexes cost disk and slow INSERT/UPDATE; don’t over-index write-heavy tables 正确流程:慢日志定位 → EXPLAIN确认 → 加索引 → 对比前后耗时Right flow: log → EXPLAIN → index → compare before/after 不要凭直觉加索引,每次都用EXPLAIN验证执行计划Never guess; verify execution plan with EXPLAIN every time
Оптимизация базы данных WordPress в четыре шага: лог медленных запросов, анализ EXPLAIN, добавление индексов, изменение структуры таблиц

Оптимизация базы данных интернет-магазина: медленные запросы, индексы, настройка структуры таблиц

Сначала вывод: Правильный порядок оптимизации базы данных WordPress: сначала включить лог медленных запросов, чтобы найти проблему, затем проанализировать план выполнения через EXPLAIN и только потом решать, добавлять индекс или менять структуру таблиц. Порог медленного запроса — 1 секунда, ежедневно смотрим TOP 10. Таблица wp_postmeta — главная чёрная дыра производительности WordPress: около 80% медленных запросов связаны с ней, а составной индекс (meta_key,meta_value) ускоряет такие запросы более чем в 10 раз. Контринтуитивный вывод: индексов не бывает слишком много — каждый индекс занимает место на диске и замедляет запись, поэтому на таблицах с частой записью и редким чтением лишние индексы только вредят. Не добавляйте индексы на глаз — каждый раз проверяйте план выполнения через EXPLAIN.

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?
В конфигурационном файле MySQL — my.cnf (Linux) или my.ini (Windows) — добавьте три строки: slow_query_log=1 включает лог медленных запросов, long_query_time=1 задаёт порог в 1 секунду, slow_query_log_file=/var/log/mysql/slow.log указывает путь к файлу лога. После правок перезапустите MySQL. Через сутки выполните mysqldumpslow -s t, чтобы сгруппировать запросы по времени и найти TOP 10 самых медленных. Не оставляйте лог включённым надолго: соберите данные и выключите его или поднимите порог до 2 секунд.
Почему таблица wp_postmeta работает так медленно?
Причина в архитектуре ключ-значение: все произвольные поля хранятся в одной таблице, каждое поле — отдельная строка. Чтобы получить 20 параметров товара, нужно 20 раз сделать JOIN к этой таблице. Когда таблица разрастается до сотен тысяч строк, любая фильтрация с meta_query требует полного сканирования. Решение — составной индекс по (meta_key, meta_value), запросы ускоряются более чем в 10 раз. Если параметров товара много, лучше создать отдельную таблицу и вынести часто используемые поля туда.
Замедлит ли индексация сайт?
Да. Каждый индекс занимает место на диске, и при каждом INSERT или UPDATE MySQL обновляет все индексы сразу. Для таблиц с частым чтением и редкой записью (wp_postmeta, wp_posts) выгода от индекса намного больше затрат; для таблиц с частой записью и редким чтением (wp_options, wp_comments) лишние индексы только замедляют запись. Принцип такой: добавляйте индекс только после того, как EXPLAIN подтвердил полное сканирование, а не на глаз. После добавления индекса проверьте скорость записи.
Как читать результат EXPLAIN?
Смотрите два столбца: type и rows. Если в type стоит ALL — это полное сканирование таблицы, нужен индекс; если ref или eq_ref — индекс уже используется. Столбец rows — это примерное число строк, которые MySQL собирается просканировать: до 10 тысяч строк терпимо, а свыше 100 тысяч — уже опасно. Ещё посмотрите столбец key, чтобы понять, какой индекс реально задействован: если key равен NULL, значит, не используется ни один индекс.
Заблокирует ли таблицу добавление индекса на продакшене?
Да. Прямой ALTER TABLE ADD INDEX на большой таблице заблокирует её на несколько минут, и сайт сразу отдаст 502. На продакшене добавляйте индекс через инструменты онлайн-DDL вроде pt-online-schema-change или gh-ost — они не блокируют таблицу. Для небольших таблиц (до 10 тысяч строк) можно добавлять напрямую. Перед добавлением индекса обязательно сделайте резервную копию, а после — проверьте план выполнения через EXPLAIN.
Как часто нужно заниматься оптимизацией базы данных?
Рекомендую раз в месяц анализировать лог медленных запросов и смотреть, не изменился ли TOP 10. Рост контента, обновления плагинов, смена темы — всё это добавляет новые медленные запросы. В ежедневном мониторинге выведите на дашборд количество медленных запросов, число подключений к базе и процент попаданий в кэш, задайте пороги для оповещений. Если какая-то страница вдруг стала медленной, первым делом смотрите лог медленных запросов, а не апгрейдите сервер.

В заключение

В оптимизации базы данных самая частая ошибка — при первой же медленной странице апгрейдить сервер. Переходите с 1 ядра и 2 ГБ на 4 ядра и 8 ГБ, платите вдвое больше, а медленные запросы никуда не деваются — потому что узкое место не в CPU, а в полном сканировании без индекса. Правильный порядок всегда такой: сначала включить лог медленных запросов и найти конкретный медленный SQL, затем посмотреть план выполнения через EXPLAIN и только потом решать — добавлять индекс или менять структуру таблицы.

Три шага: первый — включите лог медленных запросов на сутки и через mysqldumpslow найдите TOP 10; второй — разберите каждый запрос через EXPLAIN, для запросов с type=ALL добавьте индекс, а в wp_postmeta в первую очередь составной индекс (meta_key, meta_value); третий — после оптимизации проверьте снова и убедитесь, что медленный SQL упал с 1 секунды до 100 миллисекунд. После этих трёх шагов вы увидите: конфигурация сервера не изменилась ни на йоту, а страницы стали быстрее в несколько раз.

Запомните одну фразу: база тормозит — сначала ищите медленный лог, а не апгрейдьте сервер. 80% медленных запросов — это полное сканирование таблицы без индекса, и один правильно добавленный индекс работает лучше, чем лишний шаг в конфигурации.

crater
crater Эксперт Crater

Официальный аккаунт организации · crater Crater

环形山 (Crater) — компания, которая помогает выходить на зарубежные рынки: разработка сайтов на WordPress, Google SEO и комплексный рост в GEO. Мы работаем с умными устройствами, промышленным оборудованием, SaaS и новой энергетикой.

Возможности продуктаБелая книга по методологииОтраслевые отчёты
965篇分享
78статей в базе знаний