在 1 GB 或 2 GB 内存的 VPS 上,只要让几项关键的 my.cnf 参数与实际内存相匹配,MySQL 和 MariaDB 就能跑得又快又稳: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 的单位是 KB(350000 约等于 340 MB)。如果正常负载下 free -h 中的 available 已低于约 150 MB,应先调低参数,而不是调高。常见版本: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 GB 与 2 GB 的推荐配置
请从与你内存相符的配置开始。两套配置都为同一台 VPS 上的 Web 服务器和 PHP 留出了空间。
1 GB VPS
# 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
2 GB VPS
# 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 上还有 Web 环境时,从内存的 25% 左右开始;只跑数据库的 VPS,1 GB 可设约 512M,2 GB 可设约 1G。
- max_connections:每个连接都可能分配自己的缓冲区(见步骤 10),所以默认的 151 在小套餐上风险很大。PHP 网站通常只需要 PHP-FPM 进程数,再加几个给计划任务和管理工具的连接。
- 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 MB 以上;MariaDB 默认已关闭。关闭后会失去部分诊断视图,对小套餐来说很划算。
- skip-name-resolve:新建连接时不再做 DNS 反查,DNS 变慢时登录也不会卡住。启用后,数据库账号必须按 IP 地址或
localhost定义,不能再用主机名。 - key_buffer_size:只缓存 MyISAM 索引(MariaDB 默认 128 MB);使用 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 GB 内存时按注释调整数值:
# --- 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 GB 的硬盘。如果这两项都用不到,MySQL 8 也支持 skip-log-bin。
innodb_redo_log_capacity 时会报 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 超过
Created_tmp_tables的约 25%:在慢查询日志中找出这些查询(步骤 7)。加索引通常比调大tmp_table_size更有效。 - Innodb_buffer_pool_reads 超过
Innodb_buffer_pool_read_requests的 1%:热数据放不进缓冲池。只有在free -h显示还有可用内存时才调大。 - Opened_tables 预热后仍快速增长:以每次约 200 的幅度调大
table_open_cache。
如果 Aborted_connects 不断上升,通常是有机器人在连 3306 端口;若无需对外开放,请按Linux 防火墙设置教程:VPS 使用 UFW 与 firewalld将其封锁。
步骤 7:用慢查询日志找出慢查询
大多数“数据库很慢”的问题,根源都是少数几条糟糕的查询,而不是配置。慢查询日志会记录每一条执行时间超过 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 入门
在慢的 SELECT 前加上 EXPLAIN,就能看到数据库如何执行它。示例使用一张假设的 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 表示用上了索引。只返回 20 行的查询却有很大的 rows 估算值,通常说明缺少索引;Extra 中出现 Using filesort 或 Using temporary,表示需要额外排序。
- 先给
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 小时后再执行;它不知道同一台 VPS 上还有 PHP 和 Web 服务器,可能建议一个让它们无处容身的缓冲池大小。请用 free -h 核对每一条建议,并且每次只改一项。请把它的输出当作待确认的问题清单,而不是操作指令。
步骤 10:避免被 OOM killer 杀掉
内存耗尽时,Linux 的 OOM killer 会终止占用内存最多的进程,通常就是数据库。调大任何参数之前,先粗略算一下最坏情况:
# 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 调到好几 MB:乘以 max_connections 之后,很快就会超过 1 GB 套餐的内存。如果某个报表确实需要较大的排序缓冲区,只在该连接中执行 SET SESSION sort_buffer_size = 8 * 1024 * 1024; 即可。
请添加 swap 作为应对短时高峰的缓冲,方法见Linux VPS 添加 Swap 交换空间教程。Swap 能防止崩溃,却不能让内存不足的数据库变快;如果调优后内存依旧紧张,真正的解决办法是升级到更大的套餐,详见VPS 升级内存、SSD 硬盘或 IP 教程。
常见问题
要不要开启查询缓存(query cache)?
不要。MySQL 8 已移除查询缓存;MariaDB 虽然保留(默认关闭),但在并发写入时会因锁竞争成为瓶颈。请改在应用层做缓存,例如使用对象缓存。
重启后修改没有生效,为什么?
通常是文件放在了未被 include 的目录、文件名不是以 .cnf 结尾,或者之后读取的文件又设置了同一个参数。mysqld --print-defaults(MariaDB 为 mariadbd --print-defaults)可以显示最终生效的参数列表,SHOW VARIABLES 则显示实际运行中的值。
512 MB 的 mini 套餐能跑数据库吗?
小网站可以,但需要 swap 和精简的配置,例如缓冲池 128M、20 个连接并关闭 performance_schema。在这种内存规格下,MariaDB 通常更省资源;访问量较大的网站,选择 1 GB 及以上的套餐会更从容。
如果数据库一再被杀掉,或者不确定该调整哪个参数,请至 https://www.justg.com/submitticket.php 提交工单,并附上服务器 IP、你的 zz-tuning.cnf 以及 free -h 的输出。JUSTG 提供 7×24 小时 AI 客服(工单),需要人工处理的问题会由工程师跟进。