調整 apache / mysql 的 max connection 避免記憶體不足(Out of Memory, OOM)

在 ubuntu linux 的 wordpress 主機上架 mysql + apache, 偶爾會遇到 web server crash, 已加入 2GB 的 swap 一樣無解。

結論來說, 在只有 1GB RAM 的主機上安裝 wordpress, 我修改編輯 MySQL 設定檔, 指令:

sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

內容:

max_connections = 400

我修改編輯 Apache 設定檔, 指令:

sudo nano /etc/apache2/mods-available/mpm_prefork.conf

內容:

MaxRequestWorkers 50
MaxConnectionsPerChild 1000

根據你提供的 /var/log/syslog 記錄,MySQL 會被重啟的原因是 記憶體不足(Out of Memory, OOM),導致 Linux 核心啟動了 OOM Killer,強制結束了 mysqld 進程。這可從以下關鍵日誌判斷:

Jun  3 15:44:43 ubuntu-s-blog-03 kernel: oom-kill:constraint=CONSTRAINT_NONE,...,task=mysqld,pid=8350
Jun  3 15:44:43 ubuntu-s-blog-03 kernel: Out of memory: Killed process 8350 (mysqld)

為什麼會被 OOM Kill?

Linux 當記憶體資源耗盡時,會自動啟用 OOM Killer 終止佔用大量記憶體但可重啟的進程,以避免整個系統崩潰。在這裡 mysqld 可能是記憶體占用最高的進程,因此被選中。


如何避免 MySQL 被 OOM Kill?

✅ 1. 增加可用記憶體

  • 升級實體機器或虛擬機的記憶體容量(RAM)。
  • 確認是否有記憶體限制(如 cgroup 限制或 docker 容器限制): systemctl show mysql.service | grep Memory

✅ 2. 設定 swap 空間

如系統記憶體過小,確保你有適當的 swap 空間:

free -h   # 查看 swap 使用狀況

若無 swap,可新增:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

✅ 3. 降低 MySQL 記憶體使用

編輯 MySQL 設定檔(如 /etc/mysql/my.cnf/etc/mysql/mysql.conf.d/mysqld.cnf),調整以下參數:

[mysqld]
innodb_buffer_pool_size = 256M      # 預設可能是幾百 MB,可適度調低
max_connections = 100              # 若過高,會占大量記憶體
query_cache_size = 0               # 建議停用

重新啟動 MySQL:

sudo systemctl restart mysql

✅ 4. 設定 oom_score_adj

讓系統避免優先殺掉 MySQL:

# 建立 systemd override 設定
sudo systemctl edit mysql.service

加入以下內容:

[Service]
OOMScoreAdjust=-500

然後重啟 systemd 設定與 MySQL:

sudo systemctl daemon-reexec
sudo systemctl restart mysql

✅ 5. 找出耗記憶體的其他進程

你可以透過 htoptop 查看哪個程序佔用過多記憶體,有時不是 MySQL,而是其他服務造成系統整體 OOM:

ps aux --sort=-%mem | head -n 15

建議總結:

預防方法操作建議
增加記憶體或 swap增加 RAM 或新增 swap 空間
降低 MySQL 記憶體需求調整 innodb_buffer_pool_size, max_connections 等參數
降低被 OOM Kill 機率設定 OOMScoreAdjust=-500
觀察整體記憶體使用使用 htopps 監控其他程序

如果你希望我幫忙分析目前 MySQL 使用了多少記憶體,或協助調整具體參數,可提供 free -hps aux --sort=-%mem | head -n 20 輸出內容,我可以進一步建議。


你執行的指令輸出顯示了 MySQL 服務當前的記憶體使用與限制狀況,以下是重點分析:


🔍 目前記憶體狀態解析

欄位名稱解釋
MemoryCurrent=181497856MySQL 當前佔用約 181 MB 的記憶體。這並不算高,代表 OOM 當下 MySQL 的實際 RSS 是比較低的(可能已經釋放一部分)。
MemoryAccounting=yes系統啟用了記憶體統計。這是好事,能追蹤單個服務記憶體使用情況。
MemoryMax=infinity沒有限制 MySQL 的記憶體使用上限。
MemorySwapMax=infinity同樣未限制 swap 的使用。
MemoryLimit=infinity沒有設定總記憶體限制。

✅ 結論:目前沒有針對 MySQL 的記憶體限制,OOM Kill 並非 systemd 層面的限制所致

這表示造成 OOM Kill 的根本原因可能是:

🚨 整台主機的總記憶體不足(包括其他進程的佔用)


✅ 接下來建議你進行以下動作:

1. 🔍 檢查整體記憶體與 swap 使用狀況

free -h

2. 🔍 查找當時記憶體佔用前幾名的進程

ps aux --sort=-%mem | head -n 20

特別關注是不是有:

  • PHP-FPM(常見於 WordPress 主機)
  • Java / Node / Python 程式吃掉大量記憶體
  • 長時間運行的備份或同步程序

📌 如果你希望立即減少 OOM 風險:

✅ 建議 1:限制 MySQL 的最大記憶體使用(例如 512 MB)

可用 systemd cgroup 設定:

sudo systemctl edit mysql.service

加入以下內容:

[Service]
MemoryMax=512M

然後重新載入與重啟:

sudo systemctl daemon-reexec
sudo systemctl restart mysql

這樣即使系統吃緊,MySQL 也不會吃掉過多記憶體,但同時要搭配調整 MySQL 自己的設定(如 innodb_buffer_pool_size)避免啟動失敗。


✅ 建議 2:設置 OOM score 避免 MySQL 被優先殺死

sudo systemctl edit mysql.service

加入:

[Service]
OOMScoreAdjust=-500

這樣 OOM 發生時系統會選擇其他進程(如 cache-heavy script)先殺。


✅ 建議 3:新增或擴增 swap 空間

如果實體記憶體不夠,設置至少 1~2GB 的 swap 空間 可以避免突然 OOM:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

如果你想從 MySQL 自身設定(mysqld.cnf 下手,來降低記憶體使用、防止 OOM,可以調整以下幾個核心參數。這些主要影響的是 MySQL 在 InnoDB 引擎中的快取、查詢緩衝、連線數等資源分配。


✅ 建議修改的 mysqld.cnf 參數(防止佔用過多記憶體)

路徑範例:

sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

加入或修改以下內容(在 [mysqld] 區段下):

[mysqld]
# 降低 buffer pool 記憶體用量
innodb_buffer_pool_size = 128M       # 預設可能是 256M~1G,可根據 RAM 降低

# 降低連線對記憶體的需求
max_connections = 50                # 預設常常是 151~200,這會分配大量 thread 和 buffer

# 避免不必要的查詢快取
query_cache_type = 0
query_cache_size = 0

# 限制內部快取區大小
sort_buffer_size = 1M
join_buffer_size = 1M
read_buffer_size = 1M
read_rnd_buffer_size = 1M

# 限制 thread 快取與堆疊大小
thread_stack = 192K
thread_cache_size = 4

# 限制 temporary table 使用的記憶體
tmp_table_size = 16M
max_heap_table_size = 16M

🧠 為什麼這些參數重要?

參數作用說明
innodb_buffer_pool_sizeInnoDB 的主要記憶體快取,太大會佔用大量記憶體。
max_connections每個連線都會產生一堆 buffer,連線太多會吃爆記憶體。
query_cache_*查詢快取常被禁用,因為會影響效能,且在新版 MySQL 預設已關。
*_buffer_size每個連線都會有這些 buffer,值太大乘上連線數會爆炸。
thread_cache_size控制 Thread 資源重用,不宜太高。
tmp_table_size超過此大小的臨時表會寫入磁碟,設定太大會吃記憶體。

✅ 套用設定並重啟 MySQL

sudo systemctl restart mysql

✅ 驗證是否套用成功

mysql -u root -p -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"

或:

mysqladmin variables -u root -p | grep buffer

✅ 建議依系統記憶體調整的配置範例:

系統總記憶體建議 innodb_buffer_pool_sizemax_connections
512MB64M~128M30~50
1GB128M~256M50~80
2GB256M~512M100~150

你目前的 innodb_buffer_pool_size = 134217728,換算為:

134217728 bytes = 128 MB

這代表你已經手動或預設限制了 InnoDB 的快取為 128MB,這是非常保守的設定,適合記憶體只有 512MB~1GB 的小型機器,因此:


✅ 初步結論:

MySQL 本身記憶體設定已經很節省,不是主因造成 OOM


✅ 推測你的系統可能還有這些問題:

  1. 整體記憶體不足(MySQL + PHP-FPM / Apache / nginx / crons / 其他程式吃太多)
  2. 沒有啟用 swap 或 swap 太小
  3. MySQL 雖然 pool 小,但同時連線數太多(例如 max_connections 很大,每條連線佔用額外 buffer)
  4. 某些查詢導致大量 temporary table 或排序操作吃記憶體

✅ 建議你繼續檢查以下幾個方向:

🔍 1. 查看目前最大允許連線數與平均使用量

mysql -u root -p -e "SHOW VARIABLES LIKE 'max_connections';"
mysql -u root -p -e "SHOW STATUS LIKE 'Max_used_connections';"

max_connections 是 151,而 Max_used_connections 只有 10~20,就可以調降設定:

max_connections = 50

🔍 2. 查目前各類 buffer 設定(單連線可能吃掉很多)

mysql -u root -p -e "SHOW VARIABLES LIKE '%buffer%';"

特別注意:

  • sort_buffer_size
  • join_buffer_size
  • read_buffer_size
  • read_rnd_buffer_size

這些都是 per-thread 分配 的快取,每條連線都會各自分一份。

即使每個只有 1MB,100 條連線就可能額外吃掉 400MB。

你可以設定保守值,例如:

sort_buffer_size = 512K
join_buffer_size = 512K
read_buffer_size = 512K
read_rnd_buffer_size = 512K

🔍 3. 確認是否有 swap,可用以下指令:

swapon --show
free -h

若沒有 swap,可參考我前面提供的指令新增一個 swapfile(可防止系統直接 OOM Kill)。


✅ 小結

你目前 innodb_buffer_pool_size = 128MB 已經很節省,應該進一步優化以下幾點

  1. 調低 max_connections,例如設為 50 以下
  2. 調小每個連線的 buffer size(sort/join/read buffer)
  3. 確保系統有啟用 swap
  4. (選擇性)透過 systemd 設定 OOMScoreAdjust=-500,避免 MySQL 被優先殺掉

如果你願意貼上 SHOW VARIABLES LIKE '%buffer%';SHOW STATUS LIKE 'Max_used_connections'; 的輸出,我可以進一步幫你微調設定。


這裡的資訊非常關鍵,顯示你目前 MySQL 記憶體壓力的「根本問題」:


❗ 你設定了 max_connections = 4000,但實際只用到 140

這代表:

  • MySQL 預留了支援 4000 條連線所需的潛在記憶體空間
  • 即使實際只有 140 條連線在用,MySQL 還是會根據 max_connections 分配內部資源池(如 Thread Cache、Memory Pool 等)
  • 當伺服器記憶體不足時,Linux OOM Killer 極可能選擇記憶體佔用最高的 mysqld 來殺

✅ 解決方案:立刻調整 max_connections

根據你目前 Max_used_connections = 140,建議保留一些餘裕,設定為:

max_connections = 200

這已經遠高於你曾經的最高同時連線數,足夠支援高峰。


✅ 如何修改

sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

[mysqld] 區段加上或修改:

max_connections = 200

儲存後,重啟 MySQL:

sudo systemctl restart mysql

🧠 延伸建議(可選)

  1. 若你使用某些 ORM 框架(如 Laravel、Django),請確認是否有不當的連線池配置。
  2. 加上 swap(若還沒啟用),讓系統在記憶體不足時能撐住,不直接觸發 OOM。

✅ 調整後的效益

項目原始狀態調整後
max_connections4000✅ 200(實際只用 140)
潛在記憶體佔用非常高顯著降低
OOM 發生機率非常高大幅下降
系統穩定性容易崩潰✅ 提升

如果你有多個服務(如 PHP-FPM)同時吃記憶體,我也可以幫你一併評估哪個才是主因,或是否需要調整 systemd 的保護參數(像 MemoryMax=OOMScoreAdjust=)。如果需要的話可以貼上 free -h 給我參考記憶體總量。


從你提供的 MySQL buffer 設定來看,整體已經很節省,但有幾個關鍵調整可以幫你進一步「防止 OOM(Out of Memory)」:


✅ 重點分析與建議:

🔹 max_connections = 4000(⚠️ 非常危險)

  • 這會導致 MySQL 預留大量 memory pool 給潛在連線使用,即使目前只有 140 條連線
  • 每條連線會額外吃掉下列 buffer:
Buffer 名稱大小per-connection
join_buffer_size256 KB✅ 是
read_buffer_size128 KB✅ 是
read_rnd_buffer_size256 KB✅ 是
sort_buffer_size256 KB✅ 是
net_buffer_length16 KB✅ 是

假設這 5 個 buffer 加總為約 912KB,一旦 1000 條連線啟動,就會佔用:

912KB * 1000 ≈ 900MB(僅 per-thread buffer,不含 InnoDB)

🔧 強烈建議

max_connections = 200

🔹 innodb_buffer_pool_size = 128MB(✅ 很節省,建議保留)

  • 適合低記憶體伺服器(如 1GB RAM 以下)
  • 如果你的伺服器有更多記憶體(例如 2GB 以上),可以適當調高,例如 256MB 或 512MB 來提升效能

🔹 innodb_log_buffer_size = 16MB(✅ 合理)

  • 除非你大量進行批量寫入操作,否則這大小很安全

🔹 sort_buffer_size = 256KBjoin_buffer_size = 256KBread_buffer_size = 128KB(✅ 都是精簡設定)

  • 建議保留現狀
  • 沒有看到過大的設定,很安全

✅ 結論與最佳化方案

項目當前狀況建議做法
max_connections4000改為 200
innodb_buffer_pool_size128MB可保留,視記憶體調升
各類 buffer(join/sort)非常小✅ 保持現狀
Swap不明若無,建議新增 swap

✍️ 調整方法(mysqld.cnf)

編輯 MySQL 設定檔:

sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

新增或修改以下內容:

[mysqld]
max_connections = 200

儲存並重啟 MySQL:

sudo systemctl restart mysql

(選擇性)啟用或增加 swap(防止記憶體爆掉)

若你的機器沒有 swap,請執行:

sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

如你想一起檢查你的記憶體配置、可用 swap、Apache/Nginx/PHP-FPM 等服務佔用情形,也可以貼上 free -htop -o %MEM 的輸出,我可以幫你全面看系統資源瓶頸。


根據你提供的 top -o %MEM、記憶體與 swap 使用狀況,這台伺服器正在記憶體壓力邊緣運作,以下是具體建議:


🔍 目前系統狀態總結:

🧠 實體記憶體狀況(965MB RAM):

  • 已使用: 659.2MB
  • 可用: 64.1MB(非常低,快爆掉
  • buff/cache: 241.8MB

🔁 Swap 狀況:

  • 總共:2048MB(已啟用,👍)
  • 已使用:22.4MB(代表 Linux 正在用 swap 撐住)
  • available memory:71.9MB(可做為 emergency memory)

🧨 問題核心:MySQL + Apache 佔用太多記憶體

🟥 MySQL(PID 747)佔用:

  • 454MB(46%)
  • 對 1GB RAM 來說太高了
  • 雖然 innodb_buffer_pool_size 只有 128MB,但實際佔用更大(來自 thread buffers)

🟧 Apache2 每個處理程序:

  • 每條大約 50~80MB
  • 活動中有超過 10 條進程,合計約佔 500~600MB
  • 與 MySQL 加總已經吃掉 >90% 記憶體

✅ 解決方案建議:

限制 Apache2 同時連線數(大幅降記憶體消耗)

修改 Apache 設定(預設用 mpm_prefork 模式):

sudo nano /etc/apache2/mods-available/mpm_prefork.conf

修改或加入以下內容(控制佔用記憶體上限):

<IfModule mpm_prefork_module>
    StartServers          2
    MinSpareServers       2
    MaxSpareServers       4
    MaxRequestWorkers    20
    MaxConnectionsPerChild 1000
</IfModule>

然後重啟 Apache:

sudo systemctl restart apache2

降低 MySQL 最大連線數(避免記憶體預留過大)

你的目前設定是 max_connections = 4000,非常危險。請修改為:

[mysqld]
max_connections = 100

然後重啟 MySQL:

sudo systemctl restart mysql

③ (選用)調整 Apache 使用的 MPM 模式

改用 event 模式(比 prefork 更省記憶體):

sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
sudo systemctl restart apache2

⚠️ 注意:若你在用 PHP module 模式(非 PHP-FPM),會需要改成 FPM 搭配 event。


④ 檢查是否有必要服務可關閉

例如:

  • snapd
  • modemmanager
  • multipathd
  • unattended-upgrades

你可用這些指令查詢與停用:

systemctl disable --now snapd
systemctl disable --now ModemManager

✅ 最佳化後的期望記憶體佔用:

項目預估使用量(MB)
MySQL200 ~ 250MB
Apache2150 ~ 300MB
其他系統100MB
總計500 ~ 650MB

這樣會讓你有 >300MB 的剩餘空間,讓系統不會吃到 swap 或 OOM。


如果你願意,我也可以幫你生成調整後的 mysqld.cnfmpm_prefork.conf 範例。是否要一起產出?

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *