現象還原
遷移服務器後,MySQL 從 4.x 升級到 5.0,PHP 讀取中文全變問號 ??? 或亂碼 锟斤拷。根因:客戶端/連接字符集未顯式指定,MySQL 5.0+ 預設 latin1,導致編碼錯位。
一行代碼解決
原理:SET NAMES 'utf8' 等價於執行三條語句:
SET character_set_client = utf8;
SET character_set_results = utf8;
SET character_set_connection = utf8;
character_set_client:客戶端發送 SQL 的編碼
character_set_connection:連接層轉換目標編碼
character_set_results:結果集返回給客戶端的編碼
完整最佳實踐(PDO 版)
PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, // 使用真實預處理
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
// 已自動執行 SET NAMES utf8mb4
} catch (PDOException $e) {
die("連接失敗: " . $e->getMessage());
}
編碼鏈路完整檢查清單
| 環節 | 檢查項 | 正確配置 |
| 數據庫 | CREATE DATABASE charset | DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci |
| 表 | CREATE TABLE charset | DEFAULT CHARSET=utf8mb4 |
| 列 | VARCHAR/TEXT charset | CHARSET=utf8mb4(或繼承表) |
| 連接 | PHP 連接字符集 | SET NAMES utf8mb4 / DSN charset=utf8mb4 |
| PHP 文件 | 源碼文件編碼 | UTF-8 without BOM |
| HTML 輸出 | Content-Type charset | header('Content-Type: text/html; charset=utf-8') / <meta charset="utf-8"> |
| 瀏覽器 | 頁面編碼識別 | 自動檢測或強制 UTF-8 |
常見誤區
- 以為 SET NAMES 只需執行一次:長連接(如 Swoole、Workerman)需每次獲取連接時執行。
- 使用 utf8 而非 utf8mb4:MySQL
utf8 偽 utf8(最多 3 字節),無法存 Emoji、生僻字,必須用 utf8mb4。
- 只設置數據庫/表編碼,忽略連接編碼:連接編碼不匹配是亂碼首因。
- PHP 文件保存為 ANSI/GBK:導致字符串字面量編碼與連接編碼不一致。
遷移歷史數據修正腳本
setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->query("SHOW TABLES");
while ($table = $stmt->fetchColumn()) {
$cols = $pdo->query("SHOW COLUMNS FROM `$table`")->fetchAll(PDO::FETCH_ASSOC);
foreach ($cols as $col) {
if (in_array($col['Type'], ['varchar', 'text', 'char', 'tinytext', 'mediumtext', 'longtext'])) {
// 1. 先轉回二進制,再按 GBK 解讀,寫入 utf8mb4 列
$pdo->exec("ALTER TABLE `$table` MODIFY `{$col['Field']}` VARBINARY(255)");
$pdo->exec("ALTER TABLE `$table` MODIFY `{$col['Field']}` VARCHAR(255) CHARACTER SET utf8mb4");
// 實際需分批 UPDATE CONVERT(CAST(`col` AS BINARY) USING gbk) ... 視數據量而定
}
}
}
結語
中文亂碼本質是編碼鏈路斷點。核心動作:數據庫/表/列用 utf8mb4,連接用 SET NAMES utf8mb4(或 DSN charset),PHP 文件存 UTF-8,HTML 輸出聲明 charset。PDO + DSN charset 是現代 PHP 最省心、最不易出錯的方案。
- 上一篇文章: PHP opendir() 遍歷目錄完整指南:遞歸掃描、過濾、SPL 現代化方案
- 下一篇文章: PHP 統計中文字符長度正確姿勢:mb_strlen vs strlen vs mb_strwidth
Copyright ©
程式碼之家 All Rights Reserved