首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP sleep() 導致 MySQL 連線斷開:單例模式下的閒置超時問題與解決方案

     編輯:PHP 綜合知識

問題現場

專案需求:腳本啟動後 sleep(3600)(1 小時),醒來後從資料庫讀取資料處理。測試發現:

  • sleep(10) → 讀庫成功
  • sleep(3600) → 讀庫失敗(回傳空/報錯)

根因分析

  1. 使用單例資料庫類,連線物件在腳本啟動時建立並持有
  2. MySQL 預設 wait_timeout = 28800 秒(8 小時),但某些雲端資料庫(RDS、Cloud SQL)或代理層會設定更短的閒置超時(如 60 秒、300 秒)
  3. sleep() 期間 PHP 進程阻塞,無法發送心跳封包,連線被伺服器/代理主動斷開
  4. 醒來後單例仍持有已失效的連線資源,查詢自然失敗

錯誤示範:單例持有長連線

<?php
require 'DbSingleton.php'; // 單例 PDO 連線

$data = DbSingleton::getInstance()->query('SELECT * FROM tasks')->fetchAll();
sleep(3600); // 1 小時
// 連線可能已被斷開
$data = DbSingleton::getInstance()->query('SELECT * FROM tasks')->fetchAll(); // 失敗
?>

解決方案對比

方案原理優點缺點
睡前斷開、醒來重連 手動關閉連線,睡眠後重新建立 改動最小,相容單例模式 需修改所有長睡眠點
每次查詢前檢查連線存活 封裝 ping() 或 getAttribute(PDO::ATTR_CONNECTION_STATUS),失效則重連 透明化,業務碼無感 單例類需支援重連邏輯
啟用 PDO 持久連線 new PDO(..., [PDO::ATTR_PERSISTENT => true]) PHP 自動管理連線池 易導致連線洩漏、超出 max_connections,需極謹慎
改用工作隊列 將「延遲任務」推入隊列,由 Worker 消費 架構正確,解耦、可重試、可監控 引入新基礎設施

推薦修正:單例類加入自動重連

<?php
class Db {
    private static ?PDO $pdo = null;
    private static array $config = [];

    public static function init(array $config): void {
        self::$config = $config;
    }

    public static function getPDO(): PDO {
        if (self::$pdo === null || !self::isConnected()) {
            self::connect();
        }
        return self::$pdo;
    }

    private static function connect(): void {
        $dsn = "mysql:host={$config['host']};dbname={$config['dbname']};charset={$config['charset']}";
        self::$pdo = new PDO($dsn, $config['user'], $config['pass'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false,
        ]);
    }

    private static function isConnected(): bool {
        try {
            // 輕量級 ping,不發送完整查詢
            return self::$pdo->getAttribute(PDO::ATTR_CONNECTION_STATUS) === 'connected';
        } catch (Throwable) {
            return false;
        }
    }

    // 代理查詢方法
    public static function query(string $sql, array $params = []): PDOStatement {
        $stmt = self::getPDO()->prepare($sql);
        $stmt->execute($params);
        return $stmt;
    }
}

// 使用:完全不用管 sleep 多久
Db::init([...]);
$data = Db::query('SELECT * FROM tasks')->fetchAll();
sleep(3600);
$data = Db::query('SELECT * FROM tasks')->fetchAll(); // 自動重連成功
?>

架構層面的根治

長時間 sleep() 佔用 PHP 進程、阻塞記憶體、無法水平擴展。正確做法:

  1. 主流程只負責「建立任務、計算執行時間、推入延遲隊列」
  2. 使用 Redis ZSet (score=執行時間戳) + Lua 原子取任務 或 RabbitMQ 延遲交換器 / RocketMQ 延遲消息
  3. 獨立 Worker 進程消費隊列,執行實際業務邏輯
  4. 支援失敗重試、死信隊列、監控告警、平滑部署

總結:單例模式下 sleep() 跨越 MySQL 閒置超時會導致連線失效,必須加入自動重連邏輯;但更應從架構層面移除長阻塞,採用非同步任務隊列。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved