Это пятая статья серии «Эксплуатация VPS / DevOps-база». К этому моменту у нас уже живёт стек: сервер на ногах (Анатомия VPS), сервисы заводятся (systemd), nginx отдаёт HTTPS (Nginx), PHP-FPM считает детей и пишет slowlog (PHP-FPM). Но где-то под всем этим лежит то, без чего WordPress не может ни-че-го: таблицы с постами, пользователями, настройками. В Части VI мы увидели, что тормозит PHP, и часто там на верхушке трейса сидел get_posts() → mysqli-фрейм — то есть база держала воркер занятым. Пора спуститься на самый нижний этаж прикладного стека.
Под WordPress на VPS чаще всего стоит MariaDB (форк MySQL, ставится по умолчанию в репозиториях Ubuntu/Debian как mysql-server — не пугайтесь, это MariaDB). Она — программа, которая хранит все ваши данные в структурированном виде и отвечает на SQL-запросы от PHP. На одной машине с гигабайтами памяти она же и самый крупный потребитель RAM, и самое частое узкое место, и самое критичное для бэкапа — потерять базу без копий = потерять весь сайт. В этой статье: поставим и «первично закроем» MariaDB (bind-address, сокет вместо TCP, mysql_secure_installation); создадим пользователя и БД только под один сайт (не root!); разберём движок InnoDB и самый важный параметр — размер буферного пула под вашу память; научимся делать бэкап mysqldump так, чтобы он не встал в лок на живой базе (--single-transaction), и обязательно проверять его восстановлением; и, наконец, будем понимать, что делать когда «база висит на одном запросе» — SHOW PROCESSLIST, зависший SELECT, аккуратный KILL.
Термины, которые введём один раз: СУБД (система управления базами данных) — программа, которая хранит данные в таблицах и даёт к ним доступ через SQL-запросы; MySQL/MariaDB — СУБД. SQL (Structured Query Language) — язык запросов к базе (
SELECT,INSERT…). InnoDB — «движок хранения» (storage engine) — тот, кто физически хранит строки на диске и следит за транзакциями; у MariaDB/MySQL есть и другие (MyISAM), но для WordPress годится только InnoDB, см. Часть III. Транзакция — блок изменений, который либо применяется целиком, либо не применяется вовсе (атомарность). Сокет — как у PHP-FPM: путь-файл/var/run/mysqld/mysqld.sock, через который локальный клиент приходит в базу без TCP-порта. Dump (дамп) — выгрузка всей базы (или части) в SQL-текст, чтобы восстановить её на другой машине. Все домены/имена условные; подставьте свои.
Часть I. Ставим MariaDB и закрываем первичный контур
1. Установка и проверка
# ставим сервер (в Ubuntu/Debian пакет mysql-server фактически ставит MariaDB)
sudo apt update
sudo apt install -y mariadb-server
# убедимся, что юнит systemd живёт (см. Части II про то, как читать systemctl)
sudo systemctl status mariadb --no-pager | head -n 8
# какая версия реально стоит
mariadb --versionЮнит mariadb.service из коробки уже enable и start — база живёт после перезагрузки (вот почему выученный в Части II systemctl enable --now — это не «магия», а нормальный рабочий инструмент).
2. Первый вход и почему пока можно
Клиент mariadb (он же mysql — симлинк) подключается от имени вашего unix-пользователя через сокет и по умолчанию получает полные права root-а базы — на Ubuntu/Debian это устроено через auth_socket (сверяется uid процесса клиента). Так что sudo mariadb заходит «в базу как root» без пароля, и это нормально только для административных действий, не для WordPress:
# вход как системный root базы
sudo mariadb
# внутри клиента:
MariaDB [(none)]> SELECT VERSION(), USER(); # версия и как вы вошли
MariaDB [(none)]> SHOW DATABASES; # что уже есть (information_schema, mysql, performance_schema, …)3. mysql_secure_installation — выключатель «раздать всё всем»
В установочных образах есть тестовые БД и разрешённый анонимный вход. mysql_secure_installation задаст привычные вопросы и закроет их:
# интерактивная «первичная гигиена»
sudo mysql_secure_installationСпрашивает и что отвечать (по-русски смысл):
- Set root password? Обычно «нет» — у вас на Ubuntu/Debian работает
auth_socket, root-базы через пароль нет, вход черезsudo mariadb. Пароль root базы — это отдельная тема (например, если вы потом поменяете аутентификацию root). Ставьте только если понимаете, зачем. - Remove anonymous users? — Y (анонимный вход = дыра).
- Disallow root login remotely? — Y (root базы ходит только с
localhost). - Remove test database? — Y (она ничего не стоит, но доступна всем).
- Reload privilege tables — Y.
После этой процедуры ваша база уже «не как из коробки», а как «свой сервер под одним хозяином».
4. bind-address и сокет: где именно слушать
Главная «дверь» сервера — в /etc/mysql/mariadb.conf.d/50-server.cnf (или /etc/mysql/mysql.conf.d/mysqld.cnf, в зависимости от дистрибутива — смотрите !includedir в /etc/mysql/my.cnf).
# /etc/mysql/mariadb.conf.d/50-server.cnf
[mysqld]
bind-address = 127.0.0.1 # слушаем только локально; наружу из интернета не стучаться
# skip-networking # (экстремальный вариант) — только сокет, вообще без TCP-порта
# socket = /var/run/mysqld/mysqld.sock # unix-сокет, через него ходит wp на этой же машинеЧто выбрать на одной машине:
bind-address = 127.0.0.1— база слушает TCP только на локальном (loopback) интерфейсе. Подключиться с чужой машины нельзя. WordPress на этой же машине ходит через сокет (или через127.0.0.1— одинаково безопасно).skip-networking— TCP вообще не открывается. Абсолютно локальный вариант. Удобен, если вы точно уверены, что ничего извне не подключится (и что мониторинг/репликация вам не нужны).
Не открывайте 0.0.0.0 (слушать «со всех интерфейсов») — иначе 3306 влезет в internet, и через день вы увидите в SHOW PROCESSLIST чужие IP с попытками войти. Если же нужен доступ извне (например, GUI с ноутбука) — делайте SSH-туннель (ssh -L 3306:127.0.0.1:3306 deploy@сервер), а не открытие 3306.
Проверка, что мы сделали правильно:
# пересоздать конфиг и перезапустить юнит
sudo systemctl restart mariadb
# какие адреса реально слушает база
sudo ss -tulpn | grep -E '3306|mysqld'
# ожидаем: 127.0.0.1:3306 (или отсутствие TCP при skip-networking) и файл сокета
sudo ls -l /var/run/mysqld/5. Куда и как смотрим логи
MariaDB пишет в syslog (в Ubuntu/Debian) или в свой /var/log/mysql/error.log. Читать через journalctl, как учили в Части V про systemd:
sudo journalctl -u mariadb -n 40 --no-pager # последние строчки сервераЧек-лист части
- Открыли `bind-address = 0.0.0.0` «чтобы работало из IDE» — и получили перебор паролей у 3306; лечится `127.0.0.1` + SSH-туннель.
- Забыли `mysql_secure_installation` — живут анонимы и test-база.
- `sudo mariadb` оставили как способ входа WordPress (root базы в `wp-config.php`) — см. Часть II.
Часть II. Пользователь и база под сайт, а не «от root со всем доступом»
WordPress хочет одну базу и одного пользователя. На сервере, где живут несколько сайтов, у каждого — свой пользователь и своя база. Так вы держите границы.
1. Создаём базу и пользователя под один сайт
# заходим как админ базы
sudo mariadb-- создаём БД с кириллической/эмодзи-совместимой кодировкой (utf8mb4 для WP — обязательно)
CREATE DATABASE wp_example
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
-- создаём пользователя, которому разрешено ходить только через localhost (socket/loopback)
CREATE USER 'wp_example'@'localhost' IDENTIFIED BY 'СВОЙ_длинный_пароль_из_менеджера_паролей';
-- выдаём права, и только на ЭТУ базу
GRANT ALL PRIVILEGES ON wp_example.* TO 'wp_example'@'localhost';
FLUSH PRIVILEGES;
-- проверяем, что всё именно так
SHOW GRANTS FOR 'wp_example'@'localhost';
EXIT;
Почему именно так:
- utf8mb4 — WordPress без неё ломается на эмодзи, китайских символах, длинных диакритиках. Ставьте с самого начала: поменять потом — отдельная болезненная операция.
'wp_example'@'localhost'— связывает пользователя и источник подключения. Даже если пароль утечёт, зайти с чужого IP не получится (только через сокет/локальный интерфейс этой машины).GRANT … ON wp_example.*— пользователь имеет права только на эту базу, и не видит остальные (SHOW DATABASESу него покажет только свою,mysqlиinformation_schema). Компрометацияwp-config.php(частый путь) = потеря одной базы, а не всего сервера.
2. wp-config.php на стороне WordPress
// /var/www/example.test/wp-config.php
define('DB_NAME', 'wp_example');
define('DB_USER', 'wp_example');
define('DB_PASSWORD', 'СВОЙ_длинный_пароль');
define('DB_HOST', 'localhost'); // ← сокет; mariadb клиент сам возьмёт socket из my.cnf
// для явного сокета (не обязательно): define('DB_HOST', 'localhost:/var/run/mysqld/mysqld.sock');Права на wp-config.php: только владелец (site_example или deploy) читает, 640/600. Это не тема этой статьи, но без этого пароль утек через соседний сайт — всё остальное бесполезно. См. Части V про права файлов и authorized_keys.
3. Проверка «под тем пользователем, кто будет жить»
Не верьте на глаз. Проверьте, что wp_example реально входит и видит только свою базу:
# заходим как wp_example (пароль введёт интерактив; в CI/скриптах — MYSQL_PWD env)
mysql -u wp_example -p -e "SHOW DATABASES; USE wp_example; SELECT COUNT(*) FROM wp_posts;"
Если видите wp_example, information_schema и ничего лишнего — границы держатся. Если бы у вас был там mysql и performance_schema с полными правами — вы бы дали пользователю больше, чем нужно.
4. Что делать с правами, когда надо «чуть шире»
Иногда WordPress-плагину нужно CREATE TEMPORARY TABLES или LOCK TABLES. Добавляем точечно, а не «GRANT ALL на все базы»:
GRANT CREATE TEMPORARY TABLES ON wp_example.* TO 'wp_example'@'localhost';
Убираем через REVOKE ... ON wp_example.* FROM .... Иерархия проста: чем меньше прав у боевого пользователя — тем меньше площадь атаки.
Чек-лист части
- В `wp-config.php` стоит `DB_USER=’root’` — любая утечка конфига = полный контроль над сервером БД.
- `DB_HOST=’%’` или `’внешний.ip’` — пользователь видит базу извне; ставим `localhost`.
- Кодировка базы не `utf8mb4` — эмодзи крэшат сохранение постов.
- `GRANT ALL ON *.*` (на все базы) «для простоты» — убивает изоляцию нескольких сайтов.
Часть III. InnoDB и самый важный параметр — буферный пул
InnoDB — движок хранения, с которым WordPress работает корректно. Если вдруг какая-то таблица стоит на движке MyISAM (старый, без транзакций), база теряет согласованность при падении и хуже кэшируется. Проверим и поправим, а потом займёмся памятью.
1. Убеждаемся, что таблицы — InnoDB
-- движки всех таблиц базы
USE wp_example;
SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES
WHERE TABLE_SCHEMA='wp_example';
-- глобальный дефолт движка для новых таблиц
SHOW VARIABLES LIKE 'default_storage_engine';
Если увидите MyISAM — переводим на InnoDB: ALTER TABLE wp_posts ENGINE=InnoDB; (на живой базе — только в окно тишины; это перестройка таблицы). Дефолт ставим навсегда в конфиге (см. ниже): default_storage_engine=InnoDB.
2. innodb_buffer_pool_size — кэш данных в RAM
Буферный пул — это область памяти, куда InnoDB кладёт «горячие» страницы данных и индексов. Каждый запрос читает не с диска, а из пула, если данные там уже есть. Пул маленький — горячие индексы не влезают → диск под нагрузкой дёргается, WordPress деградирует. Пул слишком большой — RAM съедена, OOM-killer (из Части V) кого-то убьёт.
Сколько ставить на WordPress на одной машине (ориентир, не догма):
- на VPS 1–2 ГБ RAM: отдайте базе столько, чтобы суммарно PHP-воркеры + пул + ОЗУ оставили запас системе. Часто 60–80% всей машины уходит базе, потому что она одна. Но при 1 ГБ реалистичнее 512M–768M, не «70%».
- если база влезает в RAM целиком (небольшой WordPress-блог), пул = весь размер данных + 20% на разрастание.
# /etc/mysql/mariadb.conf.d/99-wp.cnf (свой файл, чтобы apt не затёр дефолты)
[mysqld]
# движок
default_storage_engine = InnoDB
innodb_buffer_pool_size = 768M
innodb_log_file_size = 128M # размер журнала (WAL) для записи
innodb_flush_log_at_trx_commit = 1 # 1 = макс. надёжность (конформность с ACID)
innodb_flush_method = O_DIRECT # обходить ОС-кэш (меньше двойного дублирования)
После изменения — рестарт сервиса и проверка, что параметр принял:
sudo systemctl restart mariadb
mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
3. Ещё параметры, которые полезно тронуть под WordPress
innodb_log_file_size(по умолчанию мал). Больше — быстрее крупные записи (импорты, массовые обновления), дольше восстановление после сбоя. Для одного сайта хватает128M–256M.innodb_flush_log_at_trx_commit— режим сброса журнала:1= писать на диск при каждой транзакции (данные не теряются при отказе),2= писать в ОС-кэш раз в секунду (быстрее, но потеря ~1 секунды при отказе самого сервера, при отказе MySQL данные сохранятся). Для WordPress на одной машине оставляйте1;2оправдан, если это неоплачиваемый тестовый сервер и вы готовы потерять секунду изменений.innodb_file_per_table = ON(дефолт в новых версиях) — каждой таблице свой файл.ibd. Управляет размером базы и удалением данных (не остаётся «дыр» в общем табличном пространстве).
4. Как понять, что пул малав
-- индексы, которые реально используются, и их размер
SHOW STATUS LIKE 'Innodb_buffer_pool_read%';
-- «битые» (промахи) кэш-пула на общее число чтений
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads'; -- чтения с диска
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests'; -- попыток читать (из кэша или диска)
Примерный «хит-рейт» = (read_requests - buffer_pool_reads) / read_requests. Здорово — > 99%. Если Innodb_buffer_pool_reads растёт постоянно — горячие данные не влезают, увеличивайте пул (если память позволяет).
Чек-лист части
- Часть таблиц осталась на MyISAM → WordPress не получает транзакционной целостности и хуже кэшируется.
- `innodb_buffer_pool_size` выставлен «по учебнику — 70% RAM» на машину, где ещё и 12 воркеров PHP живут рядом → не хватает памяти, OOM.
- `innodb_log_file_size` изменили без остановки сервера/удаления старых `ib_logfile*` → сервер не поднимется.
- `innodb_flush_log_at_trx_commit=2` «для скорости» на боевом сайте — потеря последних секунд при жёстком отказе.
Часть IV. Резервная копия mysqldump --single-transaction: снимаем «на лету»
Бэкап базы — это mysqldump: выгрузка дампа (SQL-инструкций CREATE+INSERT) в файл. Простая команда бэкапит InnoDB-базу с блокировкой таблиц, то есть сайт висит на всё время выгрузки. Правильный бэкап живой базы — --single-transaction.
1. Что делает --single-transaction и почему это нужно
InnoDB поддерживает «снимок» (snapshot) на старте транзакции: дамп начинается с одной согласованной точки, при этом база продолжает принимать изменения. Без флага — блокировка, сайт «висит»; с ним — согласованная копия без даунтайма. (Дополнение: если в базе есть MyISAM-таблицы — они всё равно блокируются, --lock-tables=false не спасёт; ещё раз аргумент, почему MyISAM убираем, см. Часть III.)
# снимаем согласованную копию всей базы без остановки сайта
mysqldump --single-transaction \
--routines --triggers --events \
--default-character-set=utf8mb4 \
--user=wp_example \
wp_example > /var/backups/wp_example.sql
Разбор флагов:
--single-transaction— снимок без блокировок.--routines --triggers --events— не забыть функции, триггеры и события (WordPress-плагины используютcron-события базы, без этого флага они не попадут в дамп и после восстановления «перестанет часть автоматизации»). Это отдельная грабля.--default-character-set=utf8mb4— кодировка совпадает с базой.--user=...— пользователь, которому хватит правSELECT,LOCK TABLES,SHOW VIEW(для--single-transactionне нуженSUPER/EVENTглобально, но для дампа событий нуженEVENT-привилегия — уwp_exampleеё может не быть, см. ниже про бэкап «от админа»).
2. Дамп + gzip, чтобы не засорять диск
# в один конвейер: mysqldump | gzip
mysqldump --single-transaction --routines --triggers --events \
--default-character-set=utf8mb4 -u wp_example wp_example \
| gzip > /var/backups/wp_example_$(date +%F).sql.gz
SQL-текст сжимается в 3–5 раз, как любой текст.
3. Автоматизация: cron или systemd-таймер
Копия «раз в сутки» не должна делаться вручную. Здесь работают оба инструмента, которые уже знаем:
cron— системный планировщик задач (ставите строку15 3 * * * deploy /usr/local/bin/backup-mariadb.sh, «в 03:15 ежедневно»). Минимально и надёжно.systemd-таймер — более «нативный» способ (юнит.timer+.service; механика в Части II). Удобно логирование через journalctl.
Пример скрипта (/usr/local/bin/backup-mariadb.sh, права 700, владелец deploy), с ротацией: держать последние N копий, чтобы не завалить диск:
#!/usr/bin/env bash
set -euo pipefail
DATE=$(date +%F)
OUT=/var/backups/mysql/wp_example_${DATE}.sql.gz
KEEP=14
# сокет и пароль под конкретного пользователя базы (пароль — в ~/.my.cnf, НЕ в скрипте!)
# ~/.my.cnf (права 600): [client] user=wp_example password=...
mkdir -p /var/backups/mysql
gzip -c < <(mysqldump --single-transaction --routines --triggers --events \
--default-character-set=utf8mb4 wp_example) > "$OUT"
# ротация: удаляем старше KEEP-1
ls -1t /var/backups/mysql/wp_example_*.sql.gz | tail -n +$((KEEP+1)) | xargs -r rm -f
# (опционально) переслать копию на другую машину/облако — rsync/rclone/объект
⛔ Пароль — не в скрипте. Хранить его в открытом скрипте
755, который читает любой пользователь системы, — способ отдать пароль всем. Правильно:~/.my.cnf(chmod 600, секция[client]сuser/password), тогдаmysqldumpчитает автоматически. Отдельный способ — «плагин-аутентификация» илиmysql_config_editor. (В этой теме мы в рамках одной машины; в контексте Ветки B это тот же принцип, что «никогда не светить ключ/пароль в коде, только в защищённой форме» — секреты в конфиг, права600.)
4. Что бэкапить «обязательно», чтобы восстановление было полным
База — это только строки. Для полного восстановления сайта вместе с БД держите ещё: файлы wp-content/ (тема, плагины, загрузки) и wp-config.php. mysqldump их не берёт — их отдельно архивируют (tar -czf). Это тема разовой статьи про резервное копирование, здесь фиксируем: бэкап сайта = дамп базы плюс файлы; без файлов вы восстановите пустой каркас WordPress.
Чек-лист части
- Бэкап `mysqldump` без `—single-transaction` на живой базе → таблицы блокируются, сайт висит на время дампа.
- Забыли `—routines —triggers —events` → после восстановления не хватает триггеров/событий, «что-то перестало работать само».
- Пароль базы зашит в скрипт `755` → любой процесс в системе его прочитает.
- Ротация копий забыта → дамп-на-дамп → диск кончается → сервер падает (см. финальную статью про «диск»).
- Бэкап хранится **на том же диске**, что база → сбой диска = потеряли и базу, и копию. Копию — на другую машину/объект.
Часть V. Проверка бэкапа восстановлением: единственная вещь, которая спасает
Это скучная, но обязательная часть. Дамп лежит, а уверенности, что он рабочий, нет до того дня, когда он реально нужен. Тогда выясняется, что он битый. Проверяем регулярным восстановлением, а не верой.
1. Тестовое восстановление в «песочную» БД
Никогда не проверяем бэкап на боевой базе. Создаём отдельную песочную и разворачиваем дамп туда:
# локально или на другой машине (песочница) — разворачиваем дамп
sudo -u mysql sh -c "gunzip -c /var/backups/mysql/wp_example_$(date +%F).sql.gz | \
mysql -e 'CREATE DATABASE IF NOT EXISTS wp_example_restore' ; mysql wp_example_restore"
# или, если дамп распакован:
mysql -e "CREATE DATABASE wp_example_restore CHARACTER SET utf8mb4"
gunzip -c wp_example.sql.gz | mysql --database=wp_example_restore
2. Сверка счётчиков
USE wp_example_restore;
SELECT COUNT(*) FROM wp_posts; -- сверить с боевой
SELECT COUNT(*) FROM wp_options; -- ключевые таблицы
SELECT COUNT(*) FROM wp_users;
-- структура в порядке? таблицы есть? ключи на месте?
SHOW TABLES;
Совпадение COUNT(*) с боевой базой (или с той датой, на которую дамп) — минимальная проверка целостности. Для более строго — прогнать CHECKSUM TABLE wp_posts, wp_options на боевой и восстановленной за один и тот же срез (учтите: CHECKSUM чувствителен к порядку, но с --single-transaction на тихой базе совпадёт).
3. А что реально стоит проверить
- Восстанавливается без ошибок (нет
ERROR 1064синтаксис в середине — иногда дамп обрывается из-за кончившегося диска). wp_optionsсодержит настройки сайта (если пусто — дамп не захватил часть).- Есть
AUTO_INCREMENT, триггеры,--routines(SHOW CREATE TABLE wp_postsна восстановленной покажет триггеры;SHOW EVENTS— события). - Кодировка: кириллица/эмодзи не «кракозябрами».
4. Дрессировка «вручную» и автоматизация
- Минимум раз в неделю — прогон
gunzip | mysql <песочница>+ сверкаCOUNT. У вас уже естьcron/systemd-таймер из Части IV: добавьте второй шаг-проверку в тот же скрипт. - Раз в квартал — полноценный «fire drill»: на тестовом хосте поднимите WordPress из дампа + файлов и пройдитесь по сайту руками. Это единственная проверка, которая ловит «технически дамп валиден, но сайт не поднимается» (например, потерянные таблицы миграции).
Практическое правило: бэкап без проверки восстановления — это не бэкап, а файл, который когда-то был бэкапом. Проверка — часть процедуры, не «если успею».
Чек-лист части
- Восстановление на боевую базу «на пробу» → перезаписали живой сайт дампом. Проверка — **только** на песочную.
- Дамп проверяется только визуально (`ls -la` есть файл) → содержимое не проверялось.
- Нет проверки «дамп на другой машине разворачивается» (зависимость от версии mysql, от прав) → проверка на копии-на-копии не ловит реального переноса.
Часть VI. Когда «база висит» на одном запросе: PROCESSLIST, KILL и связка с PHP-FPM
Самый частый «жареный» случай на одном VPS: PHP-FPM тормозит, slowlog (см. Часть VI) показывает, что воркер завис в get_posts()/mysqli. Смотрим, что делает база.
1. SHOW FULL PROCESSLIST — кто чем занят прямо сейчас
SHOW FULL PROCESSLIST;
| Критичная колонка | Что значит |
|---|---|
Id | id коннекта — этим снимаете зависший запрос через KILL id |
User | какой пользователь БД; видите wp_example и «завис» — это ваш сайт |
Host | откуда коннект (localhost = ваш WP; незнакомый IP — см. Часть II) |
db | какая база |
Command | состояние; Sleep = ждут, Query/Execute = работают |
Time | сколько секунд в текущем состоянии — главный индикатор зависания |
State | фаза; Sending data (долго) = скорее всего полный скан таблицы / нет индекса |
Info | сам SQL-запрос (обрезан без FULL) |
Как читать: сортируйте глазами по Time. Один запрос с Command=Query, State крутится долго и Info — какой-то «непонятный» SELECT из плагина — вот он, виновник «тормозов сайта и PHP».
2. Аккуратно снимаем зависший запрос — KILL
-- снимаем КОНКРЕТНЫЙ зависший коннект, не трогая сервер
KILL 8123; -- тот Id из PROCESSLIST
Это не рестарт mariadb.service, а вырывание одного зависшего соединения. Сервер остаётся живым, остальные запросы обслуживаются. Киллите тот процесс, Info у которого выглядит как «этот жуткий SELECT» и Time уже минуты.
⚠️ Килл vs причина: сняли зависание — сайт вздохнул, но если запрос вернётся (плагины снова «клик»), проблема вернётся.
KILL— это «открыть закупорку», а чинить надо на уровне запроса/индекса (см. Оптимизация баз данных и Метрики и мониторинг из серии про архитектуру).
3. SHOW ENGINE INNODB STATUS — глубокий снимок
Когда висит не «запрос» в смысле SQL-текста, а что-то внутри движка (блокировки, длинная транзакция), смотрим:
SHOW ENGINE INNODB STATUS\G
В разделе TRANSACTIONS ищете «lock wait» / самую длинную живую транзакцию. На WordPress частая причина блокировки — не сама база, а зависший UPDATE wp_options ... autoload (много плагинов) или «длинная» транзакция, оставленная кривым скриптом. Ищите «самую старую» транзакцию — она держит замок и остальные ждут.
4. max_connections и связка с числом PHP-воркеров
Есть ловушка, которая проявляется именно на одном VPS: PHP-FPM открыл столько соединений к базе, сколько у него детей (каждый воркер держит свой коннект). Если одновременно и соседний сайт тоже ходит, вы упрётесь в потолок.
SHOW VARIABLES LIKE 'max_connections';
SHOW GLOBAL STATUS LIKE 'Threads_connected'; -- сколько сейчас занято
SHOW GLOBAL STATUS LIKE 'Max_used_connections'; -- сколько когда-либо было занято (пиковый за всю жизнь сервера)
Дефолт max_connections обычно 151. У вас pm.max_children (Часть IV про PHP-FPM) из, допустим, 12 (основной) + 5 + 5 по пулам = 22. Но при DDoS на второй сайт и их воркеры разгоняются — Max_used_connections упрётся. Лечится: поднимите max_connections под ваш суммарный максимум детей + запас, или держите pm.* в узде (не больше, чем реально нужно, см. Части IV–V). Связь: PHP-FPM-дети ↔ Threads_connected ↔ max_connections должны сходиться в бюджете, иначе «база отвергает клиентов» (ERROR 1040: Too many connections) — и это не база сломалась, а коннектов открыли больше, чем потолок.
# /etc/mysql/mariadb.conf.d/99-wp.cnf (дополнение)
[mysqld]
max_connections = 80 # суммарно детей по всем пулам + запас
wait_timeout = 120 # сколько секунд коннект в состоянии Sleep ждут перед закрытием
interactive_timeout = 120
wait_timeout чистит «уснувшие» коннекты от PHP-FPM, если те не держат connection pool. Ставьте выше, чем типичная «пауза между запросами» вашего движка, чтобы не рвать живые; ниже — чтобы не копить мёртвых.
5. Быстрый порядок действий при «БД висит»
# 1) что вообще сейчас в базе и сколько коннектов занято
mysql -e "SHOW GLOBAL STATUS LIKE 'Threads_connected'; SHOW GLOBAL STATUS LIKE 'Max_used_connections';"
# 2) список процессов, глазами на самый долгий
mysql -e "SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, LEFT(INFO,40) q FROM information_schema.PROCESSLIST WHERE COMMAND='Query' ORDER BY TIME DESC LIMIT 10;"
# 3) сняли конкретный зависший коннект
mysql -e "KILL 8123;"
# 4) если сервер совсем не отвечает и коннекты не снять (заблокирован и вход) — крайний шаг
sudo systemctl restart mariadb
Рестарт сервиса — крайний шаг, он рвёт все запросы (включая ваши бэкапы/админку). До него всегда пробуйте снять Id.
Чек-лист части
- Панический `systemctl restart mariadb` вместо `KILL <Id>` → потеряли все текущие коннекты и не поняли, что было.
- Подняли `max_connections`, но не подумали о памяти: каждый коннект = буферные потоки; 80 коннектов на 1 ГБ VPS = не хватит.
- `ERROR 1040: Too many connections` — это не «сбой базы», а исчерпание `max_connections`; смотрите `Threads_connected` и `pm.*` (детей PHP-FPM).
- `KILL` «зависшего» запроса из плагина, не разобравшись, что он делал → потеряли транзакцию плагина (например, half-finished импорт). Смотрите `Info` перед тем, как киллить.
Инструмент под задачу
Соберите корректный запрос под свой префикс и проверьте, ложится ли он на нужный индекс, — с учётом ловушек prepare() и ORDER BY. Попробуйте инструмент прямо здесь, на своих данных:
Чек-лист граблей этой части
- В `wp-config.php` стоит `DB_USER=’root’`.
- `mysqldump` без `—single-transaction` блокирует живую базу.
- Бэкап без `—routines —triggers —events` не захватывает триггеры/события.
- Бэкап хранится на том же диске, что база.
- Проверка восстановления никогда не прогонялась → «дамп есть, но он точно битый?»
- «БД висит» лечится рестартом сервиса вместо `KILL <Id>`.
- `innodb_buffer_pool_size` «по учебнику», без учёта PHP-детей на той же машине → OOM.
Материал написан простым языком; любой англоязычный термин (MariaDB, СУБД, SQL, InnoDB, storage engine, транзакция, unix-сокет, дамп,
--single-transaction, PROCESSLIST, KILL, buffer pool,max_connections, wait_timeout) объясняется при первом употреблении. Конфиги — для Ubuntu/Debian с MariaDB изapt; конкретные пути (/etc/mysql/mariadb.conf.d/против/etc/mysql/mysql.conf.d/), имена юнитов (mariadb.serviceпротивmysql.service) и версия (10.x/11.x) сознательно условны — проверяйте фактические черезmariadb --version,SHOW VARIABLES LIKE .... Домены/пароли/пути условные (example.test,wp_example); подставляйте свои. Секреты (пароль базы) — только в~/.my.cnfс правами600, не в открытом скрипте. Тонкие механизмы (бинарный логbinlogи point-in-time recovery, горячее резервное копирование через LVM-снапшоты, репликация мастер-реплика,--all-databasesдля всего сервера) упомянуты вскользь или не раскрыты — это следующий уровень, когда базовый контур уже живёт и стабильно бэкапится.
👤 Зарегистрируйтесь, чтобы добавить материал в закладки.
