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

解決 PHP Warning: mysql_query() Access denied for user 'ODBC'@'localhost' — 連線管理、單例模式與 PDO 遷移指南

     編輯:PHP 綜合知識

經典錯誤:Warning: mysql_query(): Access denied for user 'ODBC'@'localhost' (using password: NO)。根因通常是 重複實例化導致連線關閉、未檢查連線狀態、使用已棄用 `mysql_*` 函數。本文解析錯誤機制、提供單例連線管理、PDO 遷移完整範例、連線池最佳實踐。

一、錯誤重現與分析

<?php
class MysqlClass {
    public $link;
    public function __construct($host='localhost',$user='root',$pw='',$db='mysql') {
        $this->link = mysql_connect($host,$user,$pw);
        mysql_select_db($db);
    }
    public function query($sql){ return mysql_query($sql); }
    public function __destruct(){ mysql_close($this->link); }
}
$db = new MysqlClass();
$db = new MysqlClass(); // 第二次建構 -> __destruct 關閉第一個連線資源
$db->query("SELECT * FROM user"); // $this->link 已失效,PHP 回退 ODBC 預設使用者
?>

流程:第一次 `new` 建立連線資源 `#1`;第二次 `new` 建立 `#2`,但指派給同一變數 `$db`,導致 `#1` 失去參考,觸發 `__destruct` 關閉 `#1`;實際上 `$db->link` 指向 `#2`,但若 `#2` 連線失敗 (參數錯誤) 則為 `false`,`mysql_query` 無有效連線,改用預設 ODBC 使用者 -> 報錯。

二、修正方案:單例模式 + 連線狀態檢查

<?php
class Db {
    private static ?self $instance = null;
    private \mysqli $mysqli;
    
    private function __construct() {
        $this->mysqli = new mysqli('localhost', 'root', 'pass', 'db');
        if ($this->mysqli->connect_errno) {
            throw new \RuntimeException('DB 連線失敗: '.$this->mysqli->connect_error);
        }
        $this->mysqli->set_charset('utf8mb4');
    }
    
    public static function getInstance(): self {
        return self::$instance ??= new self();
    }
    
    public function getConnection(): \mysqli { return $this->mysqli; }
    
    public function query(string $sql): \mysqli_result|bool {
        if ($this->mysqli->ping() === false) $this->reconnect();
        return $this->mysqli->query($sql);
    }
    
    private function reconnect(): void {
        $this->mysqli->close();
        $this->mysqli = new mysqli('localhost', 'root', 'pass', 'db');
    }
    
    // 禁止複製
    private function __clone() {}
    public function __wakeup() {}
}

// 使用
$mysqli = Db::getInstance()->getConnection();
$result = $mysqli->query("SELECT * FROM user");
?>

三、PDO 遷移:現代標準、預處理、異常模式

<?php
class PdoDb {
    private static ?\PDO $pdo = null;
    
    public static function getPdo(): \PDO {
        if (self::$pdo === null) {
            $dsn = 'mysql:host=localhost;dbname=test;charset=utf8mb4';
            self::$pdo = new \PDO($dsn, 'root', 'pass', [
                \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
                \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
                \PDO::ATTR_EMULATE_PREPARES => false, // 真實預處理
                \PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci",
            ]);
        }
        return self::$pdo;
    }
    
    public static function query(string $sql, array $params = []): \PDOStatement {
        $stmt = self::getPdo()->prepare($sql);
        $stmt->execute($params);
        return $stmt;
    }
}

// 使用
$users = PdoDb::query("SELECT * FROM users WHERE status = ? AND created_at > ?", [1, '2024-01-01'])->fetchAll();
$stmt = PdoDb::query("INSERT INTO logs (msg, level) VALUES (?, ?)", ['登入成功', 'info']);
?>

四、連線池與持久連線 (PHP-FPM 環境)

<?php
// PDO 持久連線 (跨請求復用,減少握手)
$pdo = new \PDO($dsn, $user, $pass, [
    \PDO::ATTR_PERSISTENT => true,
    // ... 其他屬性
]);
// 注意:持久連線可能導致 "Too many connections",需配合 MySQL max_connections、PHP-FPM pm.max_children 規劃
?>

生產建議:使用 ProxySQL 或 PgBouncer (PostgreSQL) 作為連線池中介,而非依賴 PHP 層持久連線。

五、常見錯誤對照表

錯誤訊息原因解決
Access denied for user 'ODBC'@'localhost'無有效連線資源,回退 ODBC檢查連線變數是否被覆蓋/關閉,改用單例/PDO
MySQL server has gone away連線逾時 (wait_timeout) 或封包過大查詢前 `ping()` 重連、調大 `max_allowed_packet`
Packets out of order多執行緒共用同一連線每執行緒獨立連線,或用連線池
Commands out of sync多重查詢未釋放結果集`$stmt->free_result()` 或 `fetchAll()` 後再查詢
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved