PHP 操作 MySQL 經歷過 `mysql_*` (已移除)、`mysqli`、`PDO` 三個時代。現代項目**強制推薦 PDO**:支援預處理防注入、多數據庫抽象、異常處理、事務管理。本文提供從連接、查詢、顯示、編碼處理到錯誤處理的完整最佳實踐範例。
現代標準:PDO 連接與查詢
<?php
// config.php - 單例獲取 PDO 實例
function getPdo(): PDO {
static $pdo = null;
if ($pdo instanceof PDO) return $pdo;
$host = '127.0.0.1';
$db = 'infosystem';
$user = 'root';
$pass = 'your_secure_password';
$charset = 'utf8mb4'; // 支援 Emoji
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$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 $charset COLLATE utf8mb4_unicode_ci",
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (PDOException $e) {
// 生產環境記錄日誌,不輸出細節
error_log("DB Connection Failed: " . $e->getMessage());
die('資料庫連接失敗,請稍後再試');
}
return $pdo;
}
// index.php - 讀取並顯示
require 'config.php';
$pdo = getPdo();
try {
$stmt = $pdo->query("SELECT dept_name, emp_name, pc_name FROM info ORDER BY id DESC LIMIT 50");
$rows = $stmt->fetchAll();
} catch (PDOException $e) {
error_log("Query Failed: " . $e->getMessage());
die('數據查詢失敗');
}
?>
員工信息列表
部門員工信息
| 部門名稱 | 員工姓名 | PC 名稱 |
| = htmlspecialchars($row['dept_name']) ?> |
= htmlspecialchars($row['emp_name']) ?> |
= htmlspecialchars($row['pc_name']) ?> |
預處理防注入 (帶參數查詢)
<?php
$dept = $_GET['dept'] ?? '';
$stmt = $pdo->prepare("SELECT * FROM info WHERE dept_name = ? LIMIT 20");
$stmt->execute([$dept]);
$rows = $stmt->fetchAll();
// ? 佔位符自動轉義,杜絕 SQL 注入
?>
事務處理 (轉帳、批量寫入)
<?php
try {
$pdo->beginTransaction();
$pdo->exec("UPDATE accounts SET balance = balance - 100 WHERE user = 'A'");
$pdo->exec("UPDATE accounts SET balance = balance + 100 WHERE user = 'B'");
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
error_log("Transaction Failed: " . $e->getMessage());
}
中文亂碼徹底解決 (三步走)
- 資料庫/表/欄位:`CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`
- 連接層:DSN 帶 `charset=utf8mb4` + `SET NAMES utf8mb4` (PDO `MYSQL_ATTR_INIT_COMMAND`)
- 輸出層:`header("Content-Type: text/html; charset=UTF-8")` + HTML `` + 文件保存為 **UTF-8 無 BOM**
舊項目遷移提醒:若歷史數據為 `latin1` 存儲實際 UTF-8 內容 (雙重編碼),需先 `ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4`,或導出 SQL 文件手動修正後導入。
舊版 mysqli 寫法 (僅供維護參考)
<?php
$mysqli = new mysqli('localhost', 'root', 'pwd', 'infosystem');
if ($mysqli->connect_errno) die('連接失敗: ' . $mysqli->connect_error);
$mysqli->set_charset('utf8mb4');
$sql = "SELECT dept_name, emp_name, pc_name FROM info";
$res = $mysqli->query($sql);
if (!$res) die('查詢失敗: ' . $mysqli->error);
echo "| 部門 | 員工 | PC |
";
while ($row = $res->fetch_assoc()) {
echo "| {$row['dept_name']} | {$row['emp_name']} | {$row['pc_name']} |
";
}
echo "
";
$res->free();
$mysqli->close();
?>
極簡 mysqli 過渡版 (解決原文中的 GB2312 亂碼)
<?php
$link = mysqli_connect("localhost", "root", "password", "infosystem");
if (!$link) die("連接失敗!");
mysqli_set_charset($link, "utf8mb4"); // 關鍵:替換原文的 SET NAMES GB2312
$q = "SELECT * FROM info";
$rs = mysqli_query($link, $q);
if (!$rs) die("查詢失效!");
echo "| 部門名稱 | 員工姓名 | PC名稱 |
";
while ($row = mysqli_fetch_array($rs)) {
echo "| {$row[1]} | {$row[2]} | {$row[3]} |
";
}
echo "
";
mysqli_free_result($rs);
mysqli_close($link);
?>
常見錯誤對照表