專案運行一段時間後,MySQL 報錯:
SQLSTATE[HY000] [1040] Too many connections mysqli::real_connect(): (HY000/1040): Too many connections
檢查 SHOW PROCESSLIST 發現大量 Sleep 狀態連線佔用,且未隨請求結束釋放。
CI 3 預設資料庫配置 $db['default']['pconnect'] = TRUE 啟用**持久連線** (mysqli::pconnect / PDO::ATTR_PERSISTENT)。持久連線在請求結束時**不關閉**,而是歸還給進程連線池供下一請求複用。但在 PHP-FPM / Apache Prefork 模式下:
max_connections (預設 151) 快速耗盡wait_timeout (預設 8 小時) 才會被 MySQL 主動斷開application/config/database.php$db['default'] = array(
'dsn' => '',
'hostname' => 'localhost',
'username' => 'your_user',
'password' => 'your_pass',
'database' => 'your_db',
'dbdriver' => 'mysqli',
'dbprefix' => '',
'pconnect' => FALSE, // <=== 關鍵:設為 FALSE
'db_debug' => (ENVIRONMENT !== 'production'),
'cache_on' => FALSE,
'cachedir' => '',
'char_set' => 'utf8mb4',
'dbcollat' => 'utf8mb4_unicode_ci',
'swap_pre' => '',
'encrypt' => FALSE,
'compress' => FALSE,
'stricton' => FALSE,
'failover' => array(),
'save_queries' => TRUE
);
SHOW STATUS LIKE 'Threads_connected'; 應隨請求波動,不再單調遞增SHOW PROCESSLIST 無大量 Sleep 連線堆積$this->db->close()?CI 3 資料庫類別的析構函數 __destruct() 會自動檢查:
// system/database/DB_driver.php 簡化邏輯
public function __destruct() {
if ($this->conn_id && !$this->pconnect) {
$this->close(); // 非持久連線自動關閉
}
}
// close() 內部呼叫 mysqli_close() 或 PDO=null
設定 pconnect = FALSE 後,每個 HTTP 請求結束時,PHP 執行垃圾回收 → 呼叫析構函數 → 自動關閉連線,**完全無需手動干預**。
| 參數 | 建議值 | 說明 |
|---|---|---|
MySQL wait_timeout | 300 (5分鐘) | 快速回收閒置連線,防止殭屍連線佔用 |
MySQL max_connections | 500-1000 | 根據預估峰值 Worker 數 × 1.5 設定 |
PHP-FPM pm.max_children | 依記憶體計算 | 每子進程約 30-50MB,留足系統記憶體 |
CI db_debug | FALSE (生產) | 避免錯誤頁面洩露 SQL 結構 |
CI save_queries | FALSE (生產) | 節省記憶體,除非需要 Profiler |
若關閉持久連線後仍面臨連線建立開銷過大 (如極高 QPS),建議引入連線池中間件:
# 1. 確認配置生效 grep -r "pconnect" application/config/ # 2. 監控連線數變化 (持續 10 分鐘) watch -n 2 "mysql -e \"SHOW STATUS LIKE 'Threads_connected';\"" # 3. 壓力測試 ab -n 10000 -c 100 https://your-site.com/api/test # 4. 檢查錯誤日誌 tail -f /var/log/mysql/error.log tail -f application/logs/log-$(date +%Y-%m-%d).php
核心修復:application/config/database.php 設定 'pconnect' => FALSE。
原理:關閉持久連線,讓每個請求結束時自動歸還連線給 MySQL,避免 Worker 進程累積閒置連線耗盡 max_connections。
副作用:每請求多一次 TCP 握手 + TLS 握手 (若啟用 SSL) 開銷 (~1-3ms),絕大多數業務可忽略。
進階:高併發場景配合 ProxySQL 或遷移至 Swoole 常駐模式徹底解決連線開銷問題。