必须启用utf8mb4字符集和排序规则,因MySQL默认utf8仅支持3字节字符,无法存储Emoji等4字节Unicode字符;需全链路统一配置服务器、数据库、表、字段及客户端连接均为utf8mb4,并显式指定collation为utf8mb4_unicode_ci。

MySQL必须启用utf8mb4字符集和排序规则
MySQL默认的utf8不是真正的UTF-8,它最多只支持3字节字符,而Emoji(如 ?、?、??)需要4字节存储。不切到utf8mb4,写入会失败或被截断成问号/空格。
关键操作不是只改表,而是全链路统一:服务端连接、数据库、表、字段都得显式指定utf8mb4。常见错误是只改了表但没改连接层,结果PHP插入时仍走utf8路径。
- 确认MySQL配置文件(
my.cnf或my.ini)包含:[client] default-character-set = utf8mb4 [mysql] default-character-set = utf8mb4 [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci
- 重启MySQL后执行
SHOW VARIABLES LIKE 'character_set%';,确保character_set_server、character_set_database、character_set_client等均为utf8mb4 - 已有库/表需转换:
ALTER DATABASE db_name CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;,再对每张表执行ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
PHP连接时必须显式设置字符集为utf8mb4
仅在PDO或mysqli构造参数里写charset=utf8是无效的——它仍然指向MySQL旧的3字节utf8。必须用utf8mb4,且不能依赖SET NAMES后续设置,因为某些PDO驱动(尤其旧版本)会在prepare前忽略它。
正确做法是在DSN或连接初始化阶段硬编码指定:
立即学习“PHP免费学习笔记(深入)”;
- PDO示例:
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', $user, $pass); - mysqli面向对象示例:
$mysqli = new mysqli($host, $user, $pass, $db, $port); $mysqli->set_charset('utf8mb4'); - 如果用
mysqli_connect()函数方式,必须紧接着调用mysqli_set_charset($conn, 'utf8mb4'),不能省略
验证PHP字符串是否含合法Emoji及入库前是否被破坏
PHP本身不拦截Emoji,但某些中间环节会静默损坏:比如从HTTP POST接收时被Web服务器(如Nginx+FastCGI)或代理(如Cloudflare)转码;或者前端未声明accept-charset="utf-8"导致浏览器用ISO-8859-1提交。
最简验证方式是在插入前检查字符串长度与字节数差异:
$str = $_POST['content'] ?? '';
var_dump(strlen($str), mb_strlen($str, 'utf8')); // 若不等,说明有非ASCII多字节字符(含Emoji)
// 再确认是否真为UTF-8编码:
if (!mb_check_encoding($str, 'UTF-8')) {
// 丢弃或报错,不要强行入库
}
更稳妥的是在表单HTML中强制声明:<form accept-charset="UTF-8"></form>,并在PHP入口加mb_internal_encoding('UTF-8');统一内部编码。
注意PDO预处理语句中的隐式编码陷阱
即使DSN写了charset=utf8mb4,某些PDO MySQL驱动(特别是PHP PDO::ATTR_EMULATE_PREPARES => true(默认开启)时,仍可能把参数当作latin1处理,导致Emoji乱码。
解决方法只有两个:
- 关闭模拟预处理:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);(推荐,同时提升安全性) - 或确保PHP使用mysqlnd驱动(而非libmysql),并在php.ini中确认
mysqlnd.charset.default_charset = utf8mb4
一个容易被忽略的点:Laravel等框架若用了DB::raw()拼接SQL,且其中含Emoji字符串字面量,MySQL会按连接默认字符集解析该字面量——此时哪怕连接是utf8mb4,如果SQL里写'?'而没加_utf8mb4前缀,依然可能出错。应避免在SQL中硬编码Emoji,改用参数绑定。



















