根本原因是客户端连接层默认使用 latin1 或 utf8(即 utf8mb3),导致中文和 emoji 在传输中被截断或转义;须在连接时显式指定 utf8mb4,如命令行导入:mysql -u root -p --default-character-set=utf8mb4 database_name。
MySQL 连接时没设 charset=utf8mb4,中文和 emoji 全乱码
根本原因不是建表或字段没设 utf8mb4,而是客户端连接层默认用 latin1 或 utf8(mysql 的 utf8 实际是 utf8mb3),导致多语言数据在传输途中就被截断或转义。
实操上必须在连接建立时显式指定字符集:
- 命令行导入:
mysql -u root -p --default-character-set=utf8mb4 database_name - Python +
pymysql:charset='utf8mb4'必须写进connect()参数,光设collation不管用 - Node.js +
mysql2:在连接配置里加charset: 'utf8mb4',不加则默认走服务端character_set_client值,通常不是你想要的
SET NAMES utf8mb4 只对当前会话生效,导出脚本里没它就白搭
很多 SQL 导出工具(比如 phpMyAdmin、mysqldump 默认)不会自动插入 SET NAMES utf8mb4。如果 dump 文件开头没这句,哪怕数据库和表都设了 utf8mb4,导入时仍按会话默认字符集解析——大概率崩在 emoji 或越南文、阿拉伯文上。
检查 dump 文件前 10 行,确保有:
SET NAMES utf8mb4;
没有的话,手动加;或者用 mysqldump 加参数重导:
mysqldump --default-character-set=utf8mb4 -u user -p db_name > dump.sql- 注意:
--default-character-set必须放在mysqldump命令最前面,否则可能被忽略
字段级 COLLATE 比全局配置更关键,尤其涉及排序和比较
即使库、表都设了 utf8mb4_unicode_ci,只要某个 VARCHAR 字段声明时没写 COLLATE,它就继承表默认值——而旧表迁移时,字段可能还挂着 utf8mb3_general_ci,导致 WHERE name = 'café' 查不到带重音符的数据。
导入前快速确认字段编码:
SHOW FULL COLUMNS FROM table_name;
重点看 Collation 列是否全为 utf8mb4_* 开头。不是?别靠 ALTER TABLE ... CONVERT TO 一刀切,容易改坏索引长度(utf8mb4 单字符最多占 4 字节,原 utf8 索引可能超长):
- 先查索引字段最大长度:
SHOW INDEX FROM table_name; - 若某
VARCHAR(255)字段已有索引,CONVERT TO utf8mb4后实际索引长度可能达 1020 字节(255×4),超出 InnoDB 默认限制(767 字节) - 稳妥做法:
MODIFY COLUMN name VARCHAR(191) COLLATE utf8mb4_unicode_ci,再重建索引
PHP 的 mysqli 和 PDO 初始化后必须立刻执行 set_charset('utf8mb4')
只在 php.ini 里设 default_charset = "UTF-8" 完全无效——那是给 HTTP 输出用的,跟 MySQL 连接无关。
两种常见写法对比:
-
mysqli:连接后立即调用$mysqli->set_charset('utf8mb4'),返回true才算成功;不调用,INSERT含 emoji 的语句可能静默失败 -
PDO:DSN 中加;charset=utf8mb4(如mysql:host=localhost;dbname=test;charset=utf8mb4),但某些旧版 PHP 会忽略它,保险起见仍要$pdo->exec("SET NAMES utf8mb4")
最容易被忽略的是:PDO 预处理语句的 bindValue() 不会自动转码,如果绑定前变量本身是 GBK 编码(比如从老系统读的文件),utf8mb4 设置再全也没用——数据源头就得是 UTF-8。











