На VPS с 1 или 2 ГБ ОЗУ MySQL и MariaDB работают быстро и стабильно, если несколько параметров my.cnf соответствуют реальному объему памяти: innodb_buffer_pool_size, max_connections, лимиты временных таблиц и performance_schema. В этой инструкции — готовые конфигурации для обоих объемов, безопасное применение и проверка в Debian, Ubuntu, Rocky Linux и AlmaLinux, а также поиск медленных запросов, которые никакая настройка не исправит.
mysql запрашивает пароль, добавьте -u root -p. В MariaDB 11 (Debian 13), если команды mysql… нет, используйте вариант с именем mariadb… (mariadb, mariadb-dump, mariadb-dumpslow).Шаг 1: Узнайте версию и текущее потребление памяти
Некоторые параметры есть только в MySQL или только в MariaDB, поэтому сначала выясните, какой сервер и какая версия у вас установлены и сколько памяти он занимает сейчас.
mysql --version # MariaDB 11 (Debian 13): mariadb --version
mysql -e "SELECT VERSION();"
free -h
ps -o pid,rss,cmd -C mysqld,mariadbd # RSS = memory really used, in KB
RSS указан в килобайтах (350000 — это примерно 340 МБ). Если при обычной нагрузке available в выводе free -h уже меньше 150 МБ, сначала уменьшайте параметры, а не увеличивайте. Типичные версии: в Debian 12 — MariaDB 10.11, в Debian 13 — MariaDB 11.8, в Ubuntu 22.04/24.04 доступны MySQL 8.0 или MariaDB.
Шаг 2: Сначала сделайте резервную копию баз и конфигурации
Изменение конфигурации не трогает данные, а вот ошибка во время тестов — может. Сначала сделайте полный дамп и скопируйте каталог конфигурации. Если диск почти заполнен, освободите место по инструкции Диск заполнен в Linux: как найти и освободить место на VPS.
df -h / # enough free space for the dump?
mkdir -p /root/db-backup
mysqldump --all-databases --single-transaction --routines --events \
| gzip > /root/db-backup/all-$(date +%F).sql.gz
# MariaDB 11 (Debian 13): use mariadb-dump with the same options
cp -a /etc/mysql /root/db-backup/etc-mysql-$(date +%F)
# Rocky / AlmaLinux: cp -a /etc/my.cnf /etc/my.cnf.d /root/db-backup/
ls -lh /root/db-backup
--single-transaction делает согласованную копию таблиц InnoDB без блокировки сайта (на MyISAM это не распространяется, поэтому такие таблицы выгружайте в тихое время).
Шаг 3: Найдите, где хранится конфигурация
Не редактируйте большой файл по умолчанию. Создайте собственный небольшой файл в каталоге include: он читается после стандартных, поэтому его значения имеют приоритет, а обновления пакетов его не перезапишут.
| Система | Каталог include | Основной файл сервера |
|---|---|---|
| Debian 12/13, Ubuntu 22.04/24.04 с MariaDB | /etc/mysql/mariadb.conf.d/ | 50-server.cnf |
| Ubuntu 22.04/24.04 с MySQL 8.0 | /etc/mysql/mysql.conf.d/ | mysqld.cnf |
| Rocky Linux / AlmaLinux (MariaDB или MySQL) | /etc/my.cnf.d/ | mariadb-server.cnf или mysql-server.cnf |
В любом случае создайте в этом каталоге файл zz-tuning.cnf. Файлы читаются в алфавитном порядке, поэтому префикс zz- гарантирует, что ваш загрузится последним; имя обязательно должно заканчиваться на .cnf.
Шаг 4: Рекомендуемые параметры для 1 ГБ и 2 ГБ
Начните с профиля под ваш объем ОЗУ. Оба оставляют место для веб-сервера и PHP на том же VPS.
VPS с 1 ГБ
# zz-tuning.cnf - 1 GB VPS, database shares the server with nginx/Apache and PHP
[mysqld]
performance_schema = OFF
skip-name-resolve
innodb_buffer_pool_size = 256M
max_connections = 40
thread_cache_size = 8
tmp_table_size = 32M
max_heap_table_size = 32M
table_open_cache = 400
table_definition_cache = 400
key_buffer_size = 16M
# 1 = safest (default), see Step 5
innodb_flush_log_at_trx_commit = 1
VPS с 2 ГБ
# zz-tuning.cnf - 2 GB VPS, database shares the server with nginx/Apache and PHP
[mysqld]
performance_schema = OFF
skip-name-resolve
innodb_buffer_pool_size = 512M
max_connections = 80
thread_cache_size = 16
tmp_table_size = 64M
max_heap_table_size = 64M
table_open_cache = 1000
table_definition_cache = 800
key_buffer_size = 32M
# 1 = safest (default), see Step 5
innodb_flush_log_at_trx_commit = 1
- innodb_buffer_pool_size — кеш InnoDB для данных и индексов, самый важный параметр. Известное правило «70–80% ОЗУ» относится к серверам только под базу. Если на том же VPS работает веб-стек, начните примерно с 25% ОЗУ; VPS только под базу может выделить около 512M при 1 ГБ и 1G при 2 ГБ.
- max_connections — каждое подключение может выделить собственные буферы (шаг 10), поэтому значение по умолчанию 151 на маленьком тарифе рискованно. PHP-сайту редко нужно больше, чем число воркеров PHP-FPM плюс несколько подключений для cron и инструментов администрирования.
- tmp_table_size и max_heap_table_size — лимит временных таблиц в памяти (
GROUP BY,ORDER BY,DISTINCT). Действует меньшее значение, поэтому задавайте их одинаковыми. Большие значения в основном лишь повышают расход памяти на запрос в худшем случае. - table_open_cache и table_definition_cache — нескольких сотен хватает для VPS с парой сайтов; увеличивайте, если
Opened_tablesпродолжает расти (шаг 6). - performance_schema = OFF — в MySQL 8 она включена по умолчанию и может занимать больше 100 МБ, в MariaDB уже выключена. Вы теряете часть диагностических представлений — разумная цена для маленького тарифа.
- skip-name-resolve — никаких DNS-запросов при новых подключениях, поэтому вход не зависает, когда DNS тормозит. Учетные записи после этого должны задаваться по IP-адресу или
localhost, а не по имени хоста. - key_buffer_size — кеширует только индексы MyISAM (в MariaDB по умолчанию 128 МБ); сайтам на InnoDB нужно совсем немного.
Перед включением skip-name-resolve выведите список учетных записей; если в host указано имя, например app.example.com, пересоздайте эту запись с IP-адресом (например, 203.0.113.10). В MySQL 8 заодно проверьте, сколько места занимают бинарные логи:
mysql -e "SELECT user, host FROM mysql.user;"
mysql -e "SHOW BINARY LOGS;" # MySQL 8: how much space binary logs use
Строки для вашей версии сервера
Некоторые параметры есть только в одном из серверов. Добавьте подходящий блок в тот же файл в раздел [mysqld] и для 1 ГБ скорректируйте значения, как указано в комментариях:
# --- MySQL 8.0.30 or newer only (Ubuntu MySQL 8.0, MySQL 8.4) ---
# Redo log: default 100M is fine on 1 GB; 256M for write-heavy sites on 2 GB
innodb_redo_log_capacity = 256M
# RAM cap for internal temporary tables: 64M on 1 GB, 128M on 2 GB
temptable_max_ram = 128M
# Keep binary logs 3 days instead of the default 30
binlog_expire_logs_seconds = 259200
# --- MariaDB, or MySQL older than 8.0.30 ---
# Redo log: default 96M (MariaDB) is fine on 1 GB; 128M-256M on 2 GB
innodb_log_file_size = 128M
Бинарные логи нужны для репликации и восстановления на момент времени; за 30 дней логи загруженного сайта могут заполнить диск на 20 ГБ. Если вам не нужно ни то, ни другое, MySQL 8 также принимает skip-log-bin.
unknown variable 'innodb_redo_log_capacity', а MySQL 8 отвергнет query_cache_size, потому что кеш запросов из него удален. Если после изменения сервер не запускается, посмотрите журнал (шаг 6), исправьте строку и запустите снова.Шаг 5: innodb_flush_log_at_trx_commit — честно о компромиссе
Этот параметр определяет, сколько подтвержденных данных можно потерять, если у VPS пропадет питание или упадет ядро. Выбирайте его по тому, что ваш сайт может позволить себе потерять, а не по бенчмаркам.
- 1 (по умолчанию) — журнал redo сбрасывается на диск при каждом коммите, подтвержденные данные не теряются. Для магазинов, биллинга, учетных записей и всего, что связано с деньгами.
- 2 — при коммите данные передаются ОС, а на диск сбрасываются примерно раз в секунду. Сбой ОС или VPS может стоить до ~1 секунды транзакций; падение только базы — ничего. Подходит для блогов, кешей и статистики.
- 0 — запись и сброс примерно раз в секунду, поэтому даже падение самой базы может стоить ~1 секунды. Редко лучше, чем 2; не рекомендуется.
Значение 2 может заметно ускорить множество мелких записей, но это обмен надежности на скорость. Если сомневаетесь, оставьте 1; объединение мелких записей в одну транзакцию в приложении — более безопасный выигрыш.
Шаг 6: Безопасно примените настройки и проверьте результат
Проверьте файлы, перезапустите службу и сразу посмотрите журнал. Используйте имя службы вашего сервера.
# 1. Show the options the server will read (MariaDB: mariadbd --print-defaults)
mysqld --print-defaults
# MySQL 8.0.16+ only: check the option files without starting the server
mysqld --validate-config
# 2. Restart and read the log
# Service name: mariadb (MariaDB) | mysql (MySQL on Ubuntu) | mysqld (MySQL on Rocky/Alma)
systemctl restart mariadb
systemctl status mariadb --no-pager
journalctl -u mariadb -n 30 --no-pager
Затем убедитесь, что новые значения действуют, и посмотрите счетчики:
mysql -e "SHOW VARIABLES WHERE Variable_name IN ('innodb_buffer_pool_size','max_connections','tmp_table_size','performance_schema','skip_name_resolve');"
mysql -e "SHOW GLOBAL STATUS WHERE Variable_name IN ('Uptime','Max_used_connections','Created_tmp_tables','Created_tmp_disk_tables','Opened_tables','Innodb_buffer_pool_reads','Innodb_buffer_pool_read_requests','Aborted_connects');"
Счетчики статуса накапливаются с момента последнего запуска, поэтому смотрите их не раньше чем через сутки обычной нагрузки (Uptime указан в секундах).
- Max_used_connections близко к
max_connections: приложение открывает слишком много подключений или запущено слишком много воркеров PHP. Исправьте это, прежде чем поднимать лимит. - Created_tmp_disk_tables больше ~25% от
Created_tmp_tables: найдите эти запросы в журнале медленных запросов (шаг 7). Индекс обычно помогает больше, чем увеличениеtmp_table_size. - Innodb_buffer_pool_reads больше 1% от
Innodb_buffer_pool_read_requests: рабочий набор данных не помещается в пул. Увеличивайте его, только еслиfree -hпоказывает свободную доступную память. - Opened_tables после прогрева продолжает быстро расти: увеличивайте
table_open_cacheшагами примерно по 200.
Растущий Aborted_connects часто означает, что боты добрались до порта 3306; если он не должен быть открыт наружу, закройте его, как описано в статье Настройка файрвола в Linux: UFW и firewalld на VPS.
Шаг 7: Найдите медленные запросы через slow query log
Большинство жалоб «база тормозит» — это несколько плохих запросов, а не конфигурация. Журнал медленных запросов записывает каждый запрос, выполняющийся дольше long_query_time секунд.
# Turn the slow query log on now (until the next restart)
mysql -e "SET GLOBAL slow_query_log = 1; SET GLOBAL long_query_time = 1;"
mysql -e "SHOW VARIABLES LIKE 'slow_query_log_file';"
Чтобы журнал оставался включенным после перезапуска, добавьте в zz-tuning.cnf строки:
# Add to zz-tuning.cnf to keep it after restarts
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 1
# Rocky / AlmaLinux with MariaDB: /var/log/mariadb/mysql-slow.log
Новое значение long_query_time действует только для новых подключений. Дайте журналу собрать данные за сутки обычной нагрузки, затем получите сводку:
# Top 10 query patterns by total time (MariaDB 11: mariadb-dumpslow)
mysqldumpslow -s t -t 10 /var/log/mysql/mysql-slow.log
# Turn the log off again when you are done
mysql -e "SET GLOBAL slow_query_log = 0;"
mysqldumpslow группирует похожие запросы, заменяя числа на N, а строки на 'S', и показывает, сколько раз выполнялся каждый и сколько времени он занял. Начинайте сверху: запрос, выполненный 10 000 раз по 0,2 секунды, обходится гораздо дороже разового на 5 секунд. Пока журнал включен, он растет, поэтому потом выключите его или убедитесь, что его ротирует конфигурация в /etc/logrotate.d/.
Шаг 8: Основы индексов и EXPLAIN
Добавьте EXPLAIN перед медленным SELECT, чтобы увидеть, как сервер его выполняет. В примере используется условная таблица orders:
EXPLAIN SELECT id, total FROM orders
WHERE customer_id = 42 ORDER BY created_at DESC LIMIT 20;
SHOW INDEX FROM orders;
ALTER TABLE orders ADD INDEX idx_customer_created (customer_id, created_at);
-- Run EXPLAIN again: type should change from ALL to ref, key = idx_customer_created
EXPLAIN SELECT id, total FROM orders
WHERE customer_id = 42 ORDER BY created_at DESC LIMIT 20;
В выводе type = ALL вместе с key = NULL означает полный просмотр таблицы, а ref, eq_ref или range — что используется индекс. Большая оценка rows для запроса, который возвращает 20 строк, указывает на отсутствующий индекс, а Using filesort или Using temporary в Extra — на дополнительную сортировку.
- Сначала индексируйте столбцы из
WHERE(сравниваемые через=— в начале), затем столбец изORDER BY. - Не применяйте функции к индексированным столбцам:
WHERE DATE(created_at) = '2026-11-01'не может использовать индекс, а диапазонcreated_at >= '2026-11-01' AND created_at < '2026-11-02'— может. - Не индексируйте все подряд: каждый индекс замедляет запись и занимает память буферного пула.
Шаг 9: mysqltuner как второе мнение
mysqltuner анализирует счетчики сервера и предлагает возможные улучшения. Он есть в репозиториях Debian и Ubuntu; в Rocky/AlmaLinux обычно доступен из EPEL.
apt install mysqltuner # Debian / Ubuntu
mysqltuner
Пользуйтесь им осторожно: запускайте не раньше чем через 24 часа обычной нагрузки и помните, что он ничего не знает о PHP и веб-сервере на том же VPS и может предложить буферный пул, который не оставит им места. Сверяйте каждый совет с free -h и меняйте по одному параметру за раз. Воспринимайте его вывод как список вопросов, а не как инструкцию.
Шаг 10: Не доводите до OOM killer
Когда заканчивается ОЗУ, OOM killer в Linux завершает процесс, занимающий больше всего памяти, — обычно это база данных. Прежде чем что-то увеличивать, грубо оцените худший случай:
# Per-connection buffers (about 2-3 MB per connection with defaults)
mysql -e "SHOW VARIABLES WHERE Variable_name IN ('sort_buffer_size','join_buffer_size','read_buffer_size','read_rnd_buffer_size','thread_stack');"
# Rough worst case, 1 GB profile:
# buffer pool 256 MB + overhead ~60 MB + 40 connections x ~2.5 MB ~100 MB
# = ~420 MB, leaving ~500 MB for the OS, web server and PHP
# Has the kernel already killed the database?
journalctl -k | grep -i -E 'out of memory|killed process'
Реальный расход обычно ниже, потому что эти буферы выделяются только тогда, когда они нужны запросу; при этом каждый запрос может еще использовать временные таблицы до tmp_table_size, так что рассчитывайте на пик. Никогда не поднимайте глобально sort_buffer_size или join_buffer_size до нескольких мегабайт: умноженные на max_connections, они быстро превысят тариф с 1 ГБ. Если большой буфер сортировки нужен одному отчету, выполните SET SESSION sort_buffer_size = 8 * 1024 * 1024; только в этом подключении.
Добавьте swap как страховку от кратковременных пиков — см. Как добавить swap на Linux VPS. Swap предотвращает падение, но не делает быстрой базу, которой не хватает ОЗУ; если после настройки памяти по-прежнему мало, настоящее решение — тариф побольше, см. Как увеличить RAM, SSD или добавить IP на VPS.
Частые вопросы
Стоит ли включить кеш запросов (query cache)?
Нет. Из MySQL 8 он удален, а в MariaDB (где он выключен по умолчанию) при параллельной записи становится узким местом из-за блокировок. Кешируйте на стороне приложения, например с помощью объектного кеша.
После перезапуска мои изменения не применились. Почему?
Обычно файл лежит в каталоге, который не подключается, его имя не заканчивается на .cnf, или файл, прочитанный позже, снова задает тот же параметр. mysqld --print-defaults (в MariaDB — mariadbd --print-defaults) показывает итоговый список параметров, а SHOW VARIABLES — то, что действует на самом деле.
Можно ли запустить базу на тарифе mini с 512 МБ?
Для небольшого сайта — да, со swap и экономными настройками: например, буферный пул 128M, 20 подключений и выключенная performance_schema. При таком объеме MariaDB обычно легче; более нагруженным сайтам удобнее тариф от 1 ГБ.
Если базу продолжает завершать OOM killer или вы не уверены, какое значение менять, создайте тикет по адресу https://www.justg.com/submitticket.php и приложите IP сервера, ваш zz-tuning.cnf и вывод free -h. В JUSTG работает AI-поддержка 24/7 по тикетам, а вопросы, требующие участия человека, подхватывают инженеры.