瀏覽器 → HTTP Header → HTML Meta → PHP 源文件 → PHP-MBstring → MySQL 連接 → MySQL 表/列字符集 → 存儲引擎
任一節點編碼不匹配,即產生 、???、亂碼。
| 層級 | 配置項 | 標準值 | 驗證命令 |
|---|---|---|---|
| 瀏覽器/HTTP | Content-Type Header | text/html; charset=utf-8 | curl -I url | grep -i content-type |
| HTML | <meta charset="utf-8"> | UTF-8 | View Source 檢查 |
| PHP 源文件 | 編輯器保存編碼 | UTF-8 無 BOM | file -bi *.php | grep utf-8 |
| PHP-MBstring | php.ini | internal_encoding=utf-8 default_charset=utf-8 | php -i | grep mbstring |
| MySQL 連接 | PDO/mysqli 連接參數 | charset=utf8mb4 | SHOW VARIABLES LIKE 'character_set_%' |
| 數據庫/表/列 | 字符集/校對 | utf8mb4 / utf8mb4_unicode_ci | SHOW CREATE TABLE t |
$dsn = "mysql:host=127.0.0.1;dbname=app_db;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 COLLATE utf8mb4_unicode_ci"
];
$pdo = new PDO($dsn, 'user', 'pass', $options);
$mysqli = new mysqli('127.0.0.1', 'user', 'pass', 'app_db', 3306);
if ($mysqli->connect_errno) {
throw new Exception("Connect failed: " . $mysqli->connect_error);
}
$mysqli->set_charset('utf8mb4'); // 必須在查詢前調用
// 或
$mysqli->query("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci");
$link = mysql_connect('127.0.0.1', 'user', 'pass');
mysql_select_db('app_db', $link);
mysql_query("SET NAMES utf8", $link); // 老版本無 utf8mb4
mysql_query("SET CHARACTER SET utf8", $link);
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
init_connect = 'SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci'
skip-character-set-client-handshake # 強制忽略客戶端請求字符集,以服務器為準
-- 創建庫
CREATE DATABASE app_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 修改現有庫
ALTER DATABASE app_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 修改表 (含所有列)
ALTER TABLE `user` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 僅修改表默認 (不影響現有列)
ALTER TABLE `user` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
utf8mb4_unicode_ci:基於 Unicode 9.0/14.0 排序規則,多語言準確,略慢utf8mb4_0900_ai_ci:MySQL 8.0 默認,基於 Unicode 9.0,性能更好,支援音標/重音不敏感utf8mb4_general_ci:舊版簡易校對,不準確 (如 ß=s, Œ=oe),不推薦新項目原因:PHP 連接未設置字符集,MySQL 默認 latin1 發送數據。
解決:連接後立即執行 SET NAMES utf8mb4 或 PDO DSN 加 charset=utf8mb4。
原因:表/列字符集為 latin1/utf8,無法存儲 Emoji/生僻字 (需 utf8mb4)。
解決:ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4。
原因:導出文件編碼與導入會話字符集不符。
解決:
• 導出:mysqldump --default-character-set=utf8mb4 ... > backup.sql
• 導入:mysql --default-character-set=utf8mb4 db < backup.sql
• 文件編碼確認為 UTF-8 (Notepad++/VS Code 右下角可見)
-- 1. 查看當前會話字符集
SHOW VARIABLES LIKE 'character_set_%';
SHOW VARIABLES LIKE 'collation_%';
-- 2. 檢查表實際字符集
SHOW CREATE TABLE `user`;
-- 3. 檢查列字符集
SHOW FULL COLUMNS FROM `user` WHERE Collation IS NOT NULL;
-- 4. 十六進制查看存儲內容
SELECT HEX(column_name), column_name FROM `user` WHERE id=1;
-- 中文 '測試' 在 utf8mb4 下為 E6B58BE8AF95 (6 字節)
-- 在 latin1 下通常為 2 字節亂碼
mysqldump --default-character-set=latin1 --skip-set-charset db > backup_latin1.sqlCHARSET=latin1 替換為 CHARSET=utf8mb4,COLLATE=latin1_swedish_ci 替換為 COLLATE=utf8mb4_unicode_ciCREATE DATABASE new_db CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;mysql --default-character-set=utf8mb4 new_db < backup_latin1.sqlHEX(col) 確認中文 3-4 字節/字符,Emoji 4 字節堅持「全鏈路 utf8mb4」,亂碼問題將成為歷史。新項目從第一行代碼開始強制統一,舊項目按遷移手冊分批修正,技術債一次清零。