MySQL数据库默认编码非utf8mb4,宝塔新建库多为latin1或utf8mb3,导致中文及emoji存储乱码;须同步修改库、表字符集为utf8mb4,并在PHP连接时显式指定utf8mb4编码,同时确保HTML、HTTP头及Nginx代理层均正确声明UTF-8。

MySQL 数据库默认编码不是 utf8mb4
宝塔面板新建的数据库,如果没手动改过,大概率用的是 latin1 或旧版 utf8(实际是 utf8mb3),导致中文存进去就变问号或乱码。这不是网页没设 charset 的问题,是底层根本存不进完整 UTF-8 字符(比如 emoji、生僻字)。
实操建议:
- 进宝塔 → 数据库 → 找到对应库 → 点「管理」→ phpMyAdmin 里执行:
ALTER DATABASE `<code>your_db_name</code>` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
- 再对每张表执行:
ALTER TABLE `<code>your_table_name</code>` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
- 别只改库或只改表,二者必须一致;
utf8mb4是 MySQL 对真正 UTF-8 的实现,utf8是阉割版,不能用
PHP 连接 MySQL 没指定字符集
即使数据库和表都是 utf8mb4,PHP 用 mysqli 或 PDO 连接时没显式声明编码,连接层仍会走默认(通常是 latin1),查出来的字段值还是乱码。
实操建议:
- mysqli 方式:连接后立刻执行
$mysqli->set_charset("utf8mb4"),不是在 DSN 里写charset=utf8(那只是旧版) - PDO 方式:DSN 中必须带
;charset=utf8mb4,例如:$pdo = new PDO("mysql:host=localhost;dbname=test;charset=utf8mb4", $user, $pass); - 检查
phpinfo()里的default_charset,它只影响 HTTP header,跟 MySQL 连接无关,别被它误导
HTML 页面没声明正确的 Content-Type charset
浏览器渲染时靠 HTML 里的 <meta> 或 HTTP header 判断编码。如果页面返回的是 text/html 但没声明 charset=utf-8,Chrome 可能按 GBK 解析中文,直接显示为方块或叠字。
实操建议:
- 确保 HTML
<head>最顶部有:<meta charset="utf-8">
(注意是utf-8,不是UTF8或utf8) - 检查 Nginx/Apache 实际返回的 HTTP header:用浏览器开发者工具看 Response Headers 里的
Content-Type,必须含; charset=utf-8 - 宝塔里 PHP 设置页有个「强制所有 PHP 输出 utf-8」开关,开了它会在 header 加
Content-Type: text/html; charset=utf-8,但只对 PHP 文件生效,静态 HTML 不管用
宝塔 Nginx 配置里 proxy_pass 后端没透传 charset
如果你用 Nginx 做反向代理(比如前端 Vue SPA + 后端 PHP API),而 proxy_pass 指向的后端返回了 Content-Type: application/json 却没带 charset=utf-8,某些浏览器解析 JSON 时会误判编码,导致接口返回的中文字段显示乱码。
实操建议:
- 在 Nginx 的
location块里加:proxy_set_header Accept-Charset "utf-8";
- 更稳妥的是让后端 API 显式设置 header:
header('Content-Type: application/json; charset=utf-8'); - 别依赖 Nginx 自动补 charset —— 它不会给
application/json这类 MIME 类型自动加,只对text/*类型有默认行为
乱码从来不是单点问题,而是数据库、连接层、HTTP 传输、浏览器渲染四层编码链里任一环掉链子。最容易被忽略的是 PDO DSN 里漏写 charset=utf8mb4,或者以为改了数据库编码就万事大吉,结果 PHP 连接还是走默认 latin1。

















