PHP + MySQL 中文亂碼是新手最常遇到的坑。核心原因只有一個:編碼在「資料庫、連線、頁面」三層不一致。本文系統整理編碼三層架構、各層設定方法、常見錯誤場景與對應解法,一次徹底解決亂碼問題。
| 層級 | 對象 | 設定方式 | 檢查指令 |
|---|---|---|---|
| 1. 儲存層 | 資料庫、資料表、欄位 | 建立時指定 CHARSET=utf8mb4、COLLATE=utf8mb4_unicode_ci | SHOW CREATE TABLE tblSHOW VARIABLES LIKE 'character_set%' |
| 2. 傳輸層 | PHP ↔ MySQL 連線 | DSN charset=utf8mb4、SET NAMES utf8mb4、mysqli_set_charset | SELECT @@character_set_client, @@character_set_connection, @@character_set_results |
| 3. 表現層 | HTML 頁面、HTTP 回應 | <meta charset="utf-8">、header('Content-Type: text/html; charset=utf-8') | 瀏覽器開發工具 Network → Response Headers |
鐵律:三層必須完全一致(皆為 utf8mb4 / utf-8),缺一不可。
-- 建庫
CREATE DATABASE `my_db` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 建表
CREATE TABLE `users` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(50) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 修改既有表/欄位
ALTER TABLE `users` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `users` MODIFY `name` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
為何用 utf8mb4? MySQL utf8 實為 utf8mb3,僅支援 3 位元組字符(基本多文種平面),無法儲存 Emoji、生僻字**(需 4 位元組)**。utf8mb4 完整支援 Unicode。
$dsn = 'mysql:host=127.0.0.1;dbname=my_db;charset=utf8mb4';
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', // 連線建立時自動執行
];
$pdo = new PDO($dsn, 'user', 'pass', $options);
$mysqli = new mysqli('127.0.0.1', 'user', 'pass', 'my_db');
if ($mysqli->connect_errno) die('連線失敗');
$mysqli->set_charset('utf8mb4'); // 必須在查詢前呼叫
$link = mysql_connect('127.0.0.1', 'user', 'pass');
mysql_select_db('my_db', $link);
mysql_query("SET NAMES 'utf8mb4'", $link); // 或 'utf8' 若舊版不支援 mb4
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"> <!-- HTML5 簡寫,等同 http-equiv -->
<title>中文測試</title>
</head>
<body>...</body>
</html>
或 PHP 輸出 Header(需在任何輸出前):
header('Content-Type: text/html; charset=utf-8');
| 現象 | 原因 | 解決 |
|---|---|---|
存入資料庫變 ???? 或亂碼 | 連線層未設定 SET NAMES utf8mb4,MySQL 用預設 latin1 接收 | PDO DSN 加 charset、mysqli set_charset、舊版 mysql_query("SET NAMES utf8mb4") |
讀出顯示 ??? 或亂碼 | HTML 頁面編碼非 UTF-8(如 Big5/GBK),或 Header 未宣告 | 頁面加 <meta charset="utf-8">,PHP header('Content-Type: text/html; charset=utf-8') |
| 資料庫裡是正確中文,但頁面顯示亂碼 | 連線層、儲存層正確,只有表現層錯 | 修正 HTML meta / HTTP Header |
| 頁面編碼正確,但資料庫存入亂碼 | 表現層、連線層正確,儲存層編碼錯(表/欄位非 utf8mb4) | ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4 |
Emoji 存入變 ???? 或截斷 | 資料表/欄位仍為 utf8 (utf8mb3) | 全庫轉 utf8mb4,連線層同步 utf8mb4 |
| PHP 檔案本身編碼非 UTF-8 | 編輯器儲存為 Big5/ANSI,字串常數編碼錯 | IDE 設定「UTF-8 無 BOM」儲存所有 .php 檔 |
| Apache/Nginx 強制預設編碼 | AddDefaultCharset 或 charset 指令覆蓋 PHP Header | 關閉或覆蓋:.htaccess: AddDefaultCharset Off 或虛擬主機設定 charset utf-8 |
SHOW CREATE TABLE users 確認 CHARSET=utf8mb4。SELECT @@character_set_client, @@character_set_connection, @@character_set_results,三者皆為 utf8mb4。Content-Type: text/html; charset=utf-8。INSERT 一筆含中文/Emoji → SELECT → var_dump → 瀏覽器顯示,逐層驗證。若受限於舊系統必須維持 utf8 (utf8mb3) 或 gbk/big5:
SET NAMES gbk、charset=gbk)。修改 my.cnf / my.ini:
[client]
default-character-set = utf8mb4
[mysql]
default-character-set = utf8mb4
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
init_connect = 'SET NAMES utf8mb4'
重啟 MySQL 後,新建連線預設即為 utf8mb4,但PHP 端仍需顯式設定,不可依賴伺服器預設。
CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_cicharset=utf8mb4 + MYSQL_ATTR_INIT_COMMAND=SET NAMES utf8mb4 / mysqli->set_charset('utf8mb4')<meta charset="utf-8"> + header('Content-Type: text/html; charset=utf-8')AddDefaultCharset 或設為 utf-8。三層一致,亂碼自消。遇到問題按「儲存→傳輸→表現」順序逐層驗證,必能定位根因。