高流量下頻繁 `mysql_connect` 會耗盡資料庫連線數。單例模式確保**全域唯一連線實例**,配合延遲連線、字元集設定、錯誤處理,構建穩健的資料庫存取層基石。
<?php
class Nmdb {
private $link;
static private $_instance;
// 私有建構子:建立連線、設定字元集
private function __construct(string $host, string $user, string $pass) {
$this->link = @mysqli_connect($host, $user, $pass); // 改用 mysqli
if (!$this->link) throw new Exception("連線失敗: " . mysqli_connect_error());
mysqli_set_charset($this->link, 'utf8mb4');
}
private function __clone() {} // 禁止克隆
public function __wakeup() { throw new Exception("不可反序列化"); } // 禁止反序列化
// 靜態工廠:取得唯一實例
public static function getInstance(string $host, string $user, string $pass): self {
if (false === (self::$_instance instanceof self)) {
self::$_instance = new self($host, $user, $pass);
}
return self::$_instance;
}
// 選擇資料庫
public function selectDb(string $db): bool {
return mysqli_select_db($this->link, $db);
}
// 執行查詢 (自動重連邏輯可加在這裡)
public function query(string $sql) {
$result = mysqli_query($this->link, $sql);
if (!$result) throw new Exception("SQL錯誤: " . mysqli_error($this->link) . " | SQL: $sql");
return $result;
}
// 取單行關聯陣列
public function fetchAssoc($result): ?array {
return mysqli_fetch_assoc($result);
}
// 取所有行
public function fetchAll($result): array {
$rows = [];
while ($row = mysqli_fetch_assoc($result)) $rows[] = $row;
return $rows;
}
// 關閉連線 (通常由腳本結束自動回收,長運行腳本可手動呼叫)
public function close(): bool {
return mysqli_close($this->link);
}
}
?>
<?php
$db = Nmdb::getInstance('localhost', 'user', 'pass');
$db->selectDb('my_db');
$rows = $db->fetchAll($db->query("SELECT * FROM articles WHERE status=1 LIMIT 10"));
foreach ($rows as $row) {
echo $row['title'] . "
";
}
// 驗證單例
$db2 = Nmdb::getInstance('localhost', 'user', 'pass');
var_dump($db === $db2); // true
?>
class DbManager {
private static $instances = [];
public static function get(string $name, array $config): mysqli {
if (!isset(self::$instances[$name])) {
$mysqli = new mysqli($config['host'], $config['user'], $config['pass'], $config['db']);
if ($mysqli->connect_errno) throw new Exception($mysqli->connect_error);
$mysqli->set_charset($config['charset'] ?? 'utf8mb4');
self::$instances[$name] = $mysqli;
}
return self::$instances[$name];
}
}
// 使用
$mainDb = DbManager::get('main', ['host'=>'...', 'user'=>'...', 'pass'=>'...', 'db'=>'main']);
$logDb = DbManager::get('log', ['host'=>'...', 'user'=>'...', 'pass'=>'...', 'db'=>'log']);
| 優點 | 缺點 | 現代替代 |
|---|---|---|
| 全域唯一、節省連線 | 全域狀態難測試、隱性耦合 | **依賴注入 (DI) 容器** 管理連線生命週期 |
| 延遲初始化 | 多資料庫需改容器模式 | **連線池** (ProxySQL, PgBouncer, Swoole 協程池) |
| 實現簡單 | 單點故障 (連線斷無自動重連) | **ORM** (Eloquent, Doctrine) 內建連線管理 |
單例資料庫類是從「每頁一連線」邁向「專業連線管理」的關鍵一步,理解其本質與局限,才能在架構演進中做出正確選擇。