MySQL 4.1 引入多語系支援,字元集設定層層疊加(編譯預設 → 配置檔 → 啟動參數 → 資料庫 → 表 → 欄位),加上連線層的 character_set_client/connection/results 三重轉換,導致中文亂碼頻發。本文深入剖析機制,給出 SET NAMES 統一設定法與 PHP 應用層最佳實踐。
編譯預設 latin1
→ my.cnf default-character-set (如 utf8)
→ 啟動參數 --default-character-set
→ character_set_server
→ CREATE DATABASE 時預設 character_set_database
→ CREATE TABLE 時預設表字元集
→ 定義欄位時預設欄位字元集
→ 實際儲存編碼
客戶端送資料 → 1. 視為 character_set_client 編碼 → 2. 轉為 character_set_connection 編碼 → 3. 轉為資料庫/表/欄位儲存編碼 → 寫入磁碟。
讀取時:儲存編碼 → 4. 轉為 character_set_results 編碼 → 送給客戶端。
若第 1 步假設錯誤(如客戶端送 UTF-8,伺服器以 latin1 解讀),後續轉換皆會產生錯誤碼位,造成「??」或亂碼。
character_set_client = latin1,誤以為資料是 latin1 → 轉為 character_set_connection = latin1 → 再轉為 UTF-8 存入 → 部分字元損壞成 ??。character_set_results = latin1 → 再次錯誤轉換 → 前端顯示亂碼。-- 客戶端送 UTF-8,資料庫存 UTF-8
SET NAMES 'utf8'; -- MySQL 5.5+ 建議用 utf8mb4
-- 等同於:
SET character_set_client = 'utf8';
SET character_set_connection = 'utf8';
SET character_set_results = 'utf8';
SET character_set_client = 'gbk';
SET character_set_connection = 'utf8'; -- 或 gbk 皆可,轉換路徑不同
SET character_set_results = 'gbk';
[mysqld]
default-character-set = utf8mb4
character_set_server = utf8mb4
collation_server = utf8mb4_unicode_ci
[client]
default-character-set = utf8mb4
<?php
$mysqli = new mysqli('localhost', 'user', 'pwd', 'db');
if ($mysqli->connect_errno) die($mysqli->connect_error);
// 根據頁面 charset 決定
$charset = 'utf8'; // 或從設定讀取
$mysqli->set_charset($charset); // 內部會送 SET NAMES
?>
$pdo = new PDO('mysql:host=localhost;dbname=db;charset=utf8mb4', 'user', 'pwd');
// DSN 中 charset 參數會自動送 SET NAMES(PHP 5.3.6+)
mysql_connect(...);
mysql_query("SET NAMES 'utf8'"); // 每次連線後執行
latin1 改為 utf8mb4,預設校對 utf8mb4_0900_ai_ci。utf8 別名指向 utf8mb3(最多 3 位元組),新專案請直接使用 utf8mb4(支援 Emoji、補充字元)。charset=utf8mb4。SHOW VARIABLES LIKE 'character_set_%';
SHOW VARIABLES LIKE 'collation_%';
/* 關鍵變數應皆為 utf8mb4:
character_set_client
character_set_connection
character_set_database
character_set_results
character_set_server
collation_connection
collation_database
collation_server */
| 誤區 | 正確觀念 |
|---|---|
| 資料庫建立時指定 UTF-8 就萬事大吉 | 連線層字元集未設定,資料寫入/讀出仍會雙重轉換錯誤 |
| SET NAMES 每次查詢前都執行 | 連線建立後執行一次即可,重複執行浪費效能 |
| utf8 就夠了 | utf8 實為 utf8mb3,無法儲存 Emoji 與部份生僻字,請用 utf8mb4 |
| PHP header('Content-Type: text/html; charset=utf-8') 就能解決 | 只影響瀏覽器解碼,不影響 PHP→MySQL 傳輸編碼 |
徹底理解字元集流向,在連線建立時統一宣告 SET NAMES utf8mb4(或 DSN 指定 charset),即可從根源杜絕中文亂碼。