在 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 客服(工單),需要人工處理的問題會由工程師跟進。