CREATE DATABASE ... CHARSET utf8mb4、CREATE TABLE ... CHARSET utf8mb4、col VARCHAR(255) CHARSET utf8mb4SET NAMES utf8mb4(或 charset=utf8mb4 DSN 參數)header('Content-Type: text/html; charset=utf-8') 或 Web 服務器默認配置<meta charset="utf-8">(HTML5)或 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">**只要任一環節不一致,亂碼必現。**
-- my.cnf / my.ini
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
init_connect = 'SET NAMES utf8mb4'
[client]
default-character-set = utf8mb4
[mysql]
default-character-set = utf8mb4
重啟 MySQL 後驗證:
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';
CREATE DATABASE myapp DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
bio TEXT
) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
$dsn = 'mysql:host=127.0.0.1;dbname=myapp;charset=utf8mb4';
$pdo = new PDO($dsn, 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, // 真正預處理
]);
舊 mysqli 寫法:
$mysqli = new mysqli('127.0.0.1', 'user', 'pass', 'myapp');
$mysqli->set_charset('utf8mb4'); // 關鍵!
declare(encoding='UTF-8'),PHP 7.1+ 默認源碼編碼為 UTF-8。<?php
// 入口文件最頂部(在任何輸出前)
header('Content-Type: text/html; charset=utf-8');
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>...</title>
</head>
...
Nginx/Apache 也可全局設置:
# Nginx
charset utf-8;
# Apache
AddDefaultCharset utf-8
-- 1. 單表轉換(不鎖表、無停機,需 Percona Toolkit pt-online-schema-change)
-- 2. 小表可直接:
ALTER TABLE `tbl` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 3. 僅修改默認編碼(不轉換現有數據):
ALTER TABLE `tbl` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 4. 修改單字段:
ALTER TABLE `tbl` CHANGE `col` `col` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
警告:若原數據實際為 GBK 卻被誤存為 latin1,直接 CONVERT TO 會產生雙重編碼錯誤。需先 ALTER TABLE ... CHANGE col col BLOB → CHANGE col col TEXT CHARSET utf8mb4 兩步走,或編寫腳本按行 iconv('GBK', 'UTF-8', $row) 回寫。
| 現象 | 典型原因 | 修復 |
|---|---|---|
| (菱形問號) | UTF-8 數據被當作 Latin1/GBK 讀取 | 檢查連接層 SET NAMES、響應頭、HTML meta |
| 錦袂 (多字節亂碼) | GBK 數據被當作 UTF-8 讀取 | 確認數據庫實際編碼、連接層 charset |
| ??? (問號) | 字符不在目標字符集中(如 utf8 存 Emoji) | 升級至 utf8mb4 |
| 正常入庫、讀出亂碼 | 連接層編碼與表編碼不一致 | 統一 SET NAMES utf8mb4 |
**「全鏈路 utf8mb4」是唯一標準答案**:
數據庫 utf8mb4 → 連接 SET NAMES utf8mb4 → PHP 源碼 UTF-8 無 BOM → HTTP 頭 charset=utf-8 → HTML <meta charset="utf-8">。
新項目直接按此標準落地;存量項目按遷移清單逐表轉換,徹底告別亂碼。