【数据库调优 01】小内存 VPS 跑 MySQL / PostgreSQL 的调优与省内存
2026-08-15 · DevCraft Studio
1G 内存的 VPS 默认装数据库容易 OOM。讲清 MySQL 的 buffer pool 与最大连接数、PostgreSQL 的 shared_buffers/work_mem,以及 swap、zram 与连接池省内存的思路。
VPS 数据库调优 · 共 3 篇
本篇是 数据库调优 系列的第 1 篇,专注「省内存篇」——把 MySQL(buffer pool、max_connections)和 PostgreSQL(shared_buffers、work_mem)在小内存下的关键参数、swap/zram 与连接池思路一次讲清。如果你已经调好基础参数、想深入「PostgreSQL 单库的共享内存、PgBouncer、EXPLAIN 与索引」实战,请跳到第 2 篇。
很多人图省事,在 1G 内存的小鸡上直接 apt install 装完 MySQL 或 PostgreSQL 就开跑,结果没几天机器开始卡顿,SSH 都登不进去,查日志发现是 OOM Killer 把数据库进程干掉了。根子在于:数据库的默认配置是按"有几 G 内存"的常规服务器调的,放到 512M/1G 的小机上一股脑把内存吃满,加上每来一个连接再分一笔内存,高峰期直接爆。本文把 MySQL 和 PostgreSQL 在小内存下的关键参数、swap/zram、连接池和省内存思路一次讲清。
延伸阅读
更多相关攻略推荐:Ollama AI系列(2):VPS上的AI推理与API应用、Ollama AI系列(2):VPS上的AI推理与API应用、【CPU选型 01】搭载 AMD Ryzen 9950X 的 VPS、ARM / Ampere 席卷 VPS:性价比真香还是兼容陷阱?、Ollama AI系列(2):VPS上的AI推理与API应用。
一、默认参数为什么这么吃内存
数据库占内存分两大块:全局共享缓存和每连接缓冲。以 MySQL 为例,innodb_buffer_pool_size 默认往往想占掉一大块,用来缓存表数据和索引;PostgreSQL 的 shared_buffers 默认也偏小但按习惯要设到内存的 25%。更要命的是"每连接内存"——sort_buffer_size、join_buffer_size、read_buffer_size 这类是每来一个连接按需分配的,乘上并发连接数就很可观。再加上默认 max_connections 动辄 151,每个空闲连接也吃 1–2MB,再加 performance_schema(MySQL)这种内存黑洞。三块叠加,1G 机器瞬间见底。所以调优的本质,是先砍全局缓存,再压每连接开销,最后限连接数。
举个直观例子:1 核 1G 的机器,系统本身先吃掉 200–300MB,InnoDB buffer pool 想占 256–512MB,剩下给连接和其他进程的空间已经很紧。这时候若 max_connections 还保持 151、每连接缓冲又设了几 MB,并发一上来内存立刻见底,OOM Killer 优先挑内存大户(往往是 mysqld)下手,表现就是数据库莫名其妙重启、站点间歇 502。
二、MySQL:innodb_buffer_pool_size 与最大连接数
innodb_buffer_pool_size 是 MySQL 最大的内存消费者,用来缓存 InnoDB 的数据和索引。原则是:数据库独占的机器设到总内存 70%–80%;Web 和数据库同机的 LAMP 设 50%–60%;1G 及以下的小鸡压到 256M–512M,给系统和连接留活路。buffer pool 小于 1G 时把 innodb_buffer_pool_instances 设 1,多实例在小池上反而增加开销。最大连接数方面,默认 151 太高,小机设 50 足够,纯开发环境 25 也行:
[mysqld]
innodb_buffer_pool_size = 256M
innodb_buffer_pool_instances = 1
innodb_log_buffer_size = 8M
sort_buffer_size = 512K
join_buffer_size = 512K
read_buffer_size = 512K
read_rnd_buffer_size = 512K
max_connections = 50
thread_stack = 192K
performance_schema = OFF每连接缓冲别设大,512K 量级足够,否则 max_connections 一高就乘法爆炸。performance_schema 在低配机务必关掉,能直接释放一两百 MB。粗略估算峰值内存:innodb_buffer_pool_size 加上(max_connections × 每连接内存),这个和不能超过总内存减掉给系统留的 1G 左右。改完 innodb_log_file_size 的话,记得先停库删掉旧的 ib_logfile* 再起,否则会因大小不匹配拒绝启动。
另外把 query cache 彻底关掉:MySQL 8.0 已经移除查询缓存,5.7 及更早版本里它还会带来锁竞争,低内存环境纯属负担,设 query_cache_type=0、query_cache_size=0。临时表目录可指向 tmpfs(/dev/shm)提速排序,但前提是内存真有富余,否则临时表把共享内存吃光更糟。thread_cache_size 设 4–8 让空闲连接复用线程,少一点创建开销。skip-name-resolve 也建议加上,避免每次连接都做反向 DNS 查询拖慢握手。
三、PostgreSQL:shared_buffers / work_mem / max_connections
PostgreSQL 的内存大头是 shared_buffers,经验值是总内存的 25%,设太高会挤占操作系统文件系统缓存,反而吃亏。work_mem 是每条查询每个排序/哈希节点可用的内存,注意它是"每节点"而非"每连接",高并发下会乘法放大,所以全局别设太大,复杂查询临时用 SET work_mem 单独提。max_connections 同样别拍高,配合连接池压到 50–100。一个 1G 机器的示例:
shared_buffers = 256MB
effective_cache_size = 512MB
work_mem = 4MB
maintenance_work_mem = 64MB
max_connections = 50
checkpoint_completion_target = 0.9effective_cache_size 是告诉查询规划器"系统层大概有多少缓存可用",不实际分配内存,但设错会让规划器选低效执行计划,一般设到内存的 50% 左右。maintenance_work_mem 给 VACUUM、CREATE INDEX 用,可稍大但别过头。work_mem 若设太高(比如有人设 128MB),并发查询一多内存瞬间被吃光——正确做法是默认保守,对已知大查询单独提:
SET work_mem = '256MB';
SELECT * FROM huge_table ORDER BY some_column;
RESET work_mem;PostgreSQL 还有两个容易踩的坑。一是 autovacuum 必须开着,它负责回收死元组和更新统计信息,关掉表会持续膨胀、索引失效,性能雪崩;低配机可以把 autovacuum_vacuum_scale_factor 调小(如 0.05)让它更积极。二是 random_page_cost 在 SSD 上可降到 1.1(默认 4.0 偏机械盘假设),让规划器更愿意走索引而不是全表扫描。listen_addresses 只监听 localhost 或内网,别图省事设成 '*' 把库暴露到公网,那样一旦密码弱就被拖库。
四、swap 与 zram:救命稻草也有代价
小内存机器最常见的操作是开 swap 当缓冲,避免 OOM 直接杀进程。1G 机器建个 1–2G 的 swap 文件是常规操作:
sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile但务必清醒:磁盘比内存慢几个数量级。一旦数据库开始频繁换页,CPU 会卡在 iowait,响应从毫秒级跳到秒级,这就是"swap 颠簸"。所以 swap 是兜底不是解决方案,配合把 vm.swappiness 调低(比如 10)让内核尽量用物理内存。zram 是另一种思路:用压缩块设备把一部分内存当"更快的 swap",换取的速度远好于硬盘 swap,适合内存极小又怕 OOM 的场景,但压缩本身吃 CPU,且总内存没真变大,只是延缓见底。结论:swap/zram 买的是不崩溃的缓冲时间,真正要解的是减少内存需求本身。
补充一句:zram 的大小不是越大越好,一般设为物理内存的 25%–50% 即可,留足压缩余量;设太满反而因压缩率高导致 CPU 占用飙升,在单核小鸡上同样会卡。把它当作应急缓冲,而不是日常依赖。也别忘了给 swap 设优先级(swapon 的 pri 参数),让系统优先用更快的 zram、实在不行才落硬盘。
五、连接池:pgbouncer 省连接
应用每个请求都新建一个数据库连接,连接数很快顶到 max_connections,而每个连接都占内存。连接池(PostgreSQL 用 pgbouncer,MySQL 可用 ProxySQL 或类似)在应用和数据库之间维持一小撮长连接,应用侧大量短连接复用这一小撮,实际数据库连接数大幅下降。pgbouncer 的 transaction 模式最省:一个事务结束就连放回池子给别人用。配法上把 PostgreSQL 的 max_connections 压到 50–100,pgbouncer 前端放几百个 client 连接都不怕。MySQL 侧也可用线程池或 ProxySQL 达到类似效果。省下的不仅是内存,还有频繁建连的握手开销,并发能力反而上去。
pgbouncer 配置上,pool_mode 设 transaction,default_pool_size 按后端实际连接数来(比如 20),max_client_conn 可放大到几百。应用连接串指向 pgbouncer 的端口(默认 6432)而不是 PG 的 5432,数据库端的 max_connections 就能安心压低。注意 transaction 模式下不能跨事务持有会话级状态(如临时表、SET 的会话变量),若应用依赖这些,得退到 session 模式或改代码。MySQL 侧用 ProxySQL 时同理:后端连接池设小,前端放大量客户端连接。
六、监控内存与慢查询
调完要用数据验证,别凭感觉。系统层 free -h 看剩余内存,htop 看谁在吃;MySQL 里看缓冲池命中率:
SHOW STATUS LIKE 'Innodb_buffer_pool_read%';Innodb_buffer_pool_reads 相对 read_requests 太高,说明 buffer pool 太小、老去读盘。PostgreSQL 用这条看缓冲命中率,目标 99% 以上:
SELECT sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) AS hit_ratio
FROM pg_statio_user_tables;慢查询日志是抓真凶的主工具。MySQL 开 slow_query_log + long_query_time=1,跑一两天用 mysqldumpslow 排最慢的;PostgreSQL 用 pg_stat_statements 扩展统计哪条语句耗时最多,再 EXPLAIN ANALYZE 看计划。别指望调 buffer pool 能救一个百万行全表扫描的烂 SQL——先加索引。
系统层面也别漏看:dmesg | grep -i oom 能翻出是不是发生过 OOM 杀进程;journalctl 或 /var/log/messages 里搜 'Out of memory' 能看到是被谁吃掉。htop 按内存排序一眼定位异常进程。把 free -h 和数据库内部命中率做成定时巡检(写个小脚本每天推到监控或邮件),比等用户投诉"打不开"再救火强得多。指标稳住、慢查询清零,才说明调优真到位了。
七、何时该升级配置而非硬调
调优有边际。当你在 1G 上把配置已压到极限,却仍出现:iowait 长期 10% 以上(swap 颠簸主导)、简单查询偶发秒级延迟(典型内存页缺失)、高峰期频繁 OOM——这说明已到硬件瓶颈。此时三条路:降级到更轻量的数据库(如 MariaDB 轻量模式、或低并发用 SQLite);把数据库剥离到托管 RDS/DBaaS,让专业服务背锅;或者直接把内存升到 2G 起步。对生产环境,至少 2G 内存的 VPS 才谈得上稳,1G 只适合学习、测试或极低流量。硬在 512M 上反复折腾,省下的那点钱远不够宕机时的人工和丢数据代价。