PHP 7+ 移除了舊 mysql_* 擴展,現代開發必須掌握 mysqli(面向過程/對象雙模式)與 PDO(數據庫抽象層)兩大主流方案。本文涵蓋擴展安裝驗證、連接代碼模板、字符集設置防亂碼、預處理防注入、錯誤處理模式等核心知識,並給出選型建議。
<?php
// 檢查擴展是否加載
foreach (['mysqli', 'pdo_mysql'] as $ext) {
echo $ext . ': ' . (extension_loaded($ext) ? '✅ 已加載' : '❌ 未加載') . "\n";
}
// 輸出示例:
// mysqli: ✅ 已加載
// pdo_mysql: ✅ 已加載
常見安裝問題(Linux + PHP-FPM):
php.ini 中啟用:extension=mysqli、extension=pdo_mysql(無需 .dll 後綴)。extension_dir 路徑正確(php -i | grep extension_dir)。systemctl restart php8.3-fpm(版本號按實際調整)。Class 'mysqli' not found,檢查是否為 CLI 與 FPM 不同 php.ini(php --ini vs php-fpm -i)。<?php
$mysqli = new mysqli('127.0.0.1', 'user', 'pass', 'dbname', 3306);
if ($mysqli->connect_errno) {
die("連接失敗: {$mysqli->connect_error}");
}
// 關鍵:設置字符集,防止中文亂碼
$mysqli->set_charset('utf8mb4'); // 等同於 SET NAMES utf8mb4
// 查詢
$sql = "SELECT id, name FROM users WHERE status = ? AND created_at > ?";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param('is', 1, '2024-01-01'); // i=integer, s=string
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
echo "{$row['id']}: {$row['name']}\n";
}
$stmt->close();
$mysqli->close();
$link = mysqli_connect('127.0.0.1', 'user', 'pass', 'dbname');
if (!$link) die('連接失敗: ' . mysqli_connect_error());
mysqli_set_charset($link, 'utf8mb4');
$result = mysqli_query($link, "SELECT * FROM users");
while ($row = mysqli_fetch_assoc($result)) { /* ... */ }
mysqli_free_result($result);
mysqli_close($link);
<?php
$dsn = 'mysql:host=127.0.0.1;dbname=dbname;charset=utf8mb4';
$options = [
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", // 雙重保險
];
try {
$pdo = new PDO($dsn, 'user', 'pass', $options);
// 預處理查詢
$stmt = $pdo->prepare("SELECT id, name FROM users WHERE status = ? AND created_at > ?");
$stmt->execute([1, '2024-01-01']);
foreach ($stmt as $row) {
echo "{$row['id']}: {$row['name']}\n";
}
} catch (PDOException $e) {
error_log("DB Error: {$e->getMessage()}");
die('服務器內部錯誤');
}
| 層級 | 操作 | 說明 |
|---|---|---|
| 數據庫/表/列 | CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci | 存儲層面支持完整 Unicode(含 Emoji) |
| 連接層 | SET NAMES utf8mb4 / $mysqli->set_charset('utf8mb4') / DSN charset=utf8mb4 | 客戶端↔服務器通信編碼一致 |
| 應用輸出 | header('Content-Type: text/html; charset=utf-8'); | 瀏覽器正確解碼響應體 |
⚠️ 絕對不要用 utf8(MySQL 別名,實為 utf8mb3,不支持 4 字節字符),**統一用 utf8mb4**。
| 模式 | mysqli | PDO |
|---|---|---|
| 默認 | 返回 false,需手動檢查 $mysqli->error | 靜默,需檢查 errorCode()/errorInfo() |
| 推薦 | mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); 拋出 mysqli_sql_exception | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION 拋出 PDOException |
| 維度 | mysqli | PDO |
|---|---|---|
| 數據庫支持 | 僅 MySQL/MariaDB | 12+ 驅動 |
| API 風格 | 雙模式,略顯雜亂 | 純 OO,統一規範 |
| 預處理性能 | 原生預處理,略快 | 默認模擬,關閉模擬後持平 |
| 異步查詢 | 支持 mysqli::poll | 不支持(需 Swoole/ReactPHP) |
| 命名參數綁定 | 不支持(僅 ? 佔位符) | 支持 :name 可讀性強 |
| 事務嵌套/保存點 | 支持 | 支持 |
| 生態/框架支持 | Laravel/ThinkPHP 底層多用 PDO | 幾乎所有現代框架默認 |
結論:新項目首選 PDO(可移植、異常模式、命名參數);極致性能/異步/純 MySQL 老項目維護用 mysqli。
<?php
class Database {
private static ?PDO $instance = null;
public static function getInstance(): PDO {
if (self::$instance === null) {
$dsn = 'mysql:host=127.0.0.1;dbname=app;charset=utf8mb4';
self::$instance = new PDO($dsn, 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_PERSISTENT => true, // 長連接,慎用(需配合 wait_timeout)
]);
}
return self::$instance;
}
// 禁用克隆/序列化
private function __construct() {}
private function __clone() {}
public function __wakeup() {}
}
// 使用
$pdo = Database::getInstance();
$stmt = $pdo->query("SELECT VERSION()");
echo $stmt->fetchColumn();
告別 mysql_*,擁抱 mysqli/PDO。核心鐵律:**預處理防注入、utf8mb4 全鏈路、異常模式處理錯誤**。PDO 作為統一抽象層,是現代 PHP 應用的數據庫訪問基石。