主从复制乱码主因是字符集不一致:主库utf8mb4而从库client/connection/results为latin1,导致存储错误编码;需统一配置utf8mb4并验证hex值。

主从复制中出现乱码,大概率是字符集不一致
MySQL 主从复制本身不校验字符集,只要 SQL 语句能执行就照搬。如果主库用 utf8mb4,从库默认是 latin1 或 utf8(非真正的 UTF-8),写入时看似成功,但实际存储的是错误编码的字节,读出来就是问号或乱码。
常见现象包括:
- 主库
SELECT正常显示中文,从库查出来是???或 Mojibake(如“懔) - 从库
SHOW CREATE TABLE显示表字符集是utf8mb4,但SHOW VARIABLES LIKE 'character_set%'中character_set_client、character_set_connection、character_set_results却是latin1 - 主库插入带 emoji 的数据,从库报错
Incorrect string value: '\xF0\x9F\x92\xA9'...
SET NAMES 不是万能的,它只影响当前连接的三个变量
SET NAMES utf8mb4 等价于同时执行:
SET character_set_client = utf8mb4; SET character_set_connection = utf8mb4; SET character_set_results = utf8mb4;
但它不会改变:character_set_database、character_set_server、表/列定义的字符集,也不影响已存在的数据编码。所以仅靠应用层执行 SET NAMES,无法修复从库历史乱码,也不能替代服务端配置。
关键点:
- 该命令必须在每次连接建立后、执行业务 SQL 前执行——很多 ORM(如早期 Django、PHP mysqli)会自动做,但自定义连接池或脚本容易漏掉
- 如果客户端发送的是
latin1编码字节,却执行SET NAMES utf8mb4,MySQL 会错误地把这串字节当 utf8mb4 解码,导致双编码(double-encoded)乱码 - 在 binlog 中,
SET NAMES会被记录为事件(取决于binlog_format和binlog_rows_query_log_events),但从库重放时只影响从库的 SQL 线程连接上下文,不影响已同步的数据
真正要改的是从库的全局 + 会话级字符集配置
修复目标:确保从库接收、解析、存储、返回数据时,各环节字符集链路一致。优先顺序是「服务端配置 > 连接初始化 > 表结构」。
操作步骤:
- 确认主从都使用
utf8mb4:检查my.cnf中是否包含character-set-server = utf8mb4和collation-server = utf8mb4_unicode_ci;重启 MySQL 生效(仅修改变量不持久) - 检查从库当前连接的字符集:执行
SHOW VARIABLES LIKE 'character_set%',重点看character_set_client、character_set_connection、character_set_results是否全为utf8mb4 - 如果从库 SQL 线程连接仍不是
utf8mb4,可在从库启动时加参数--default-character-set=utf8mb4,或在CHANGE MASTER TO语句中显式指定MASTER_BIND=''无帮助,真正有效的是让 IO 线程和 SQL 线程都继承 server 配置 - 已有表需手动转换:对每张表执行
ALTER TABLE tbl_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;—— 注意:这会锁表,且不能修复已损坏的双编码数据
如何验证修复是否生效
不能只看 SHOW VARIABLES,要模拟真实写入路径测试:
- 在主库执行:
INSERT INTO t1 (name) VALUES ('测试✅'); - 等待同步完成,在从库执行:
SELECT HEX(name), name FROM t1 WHERE name LIKE '%测试%';—— 正确应返回CEB2CAD4E29C95(utf8mb4 编码的十六进制)和可读文字;若返回C3A6C2B5C2B7,说明是 double-encoded - 检查从库 binlog 解析:
mysqlbinlog --base64-output=decode-rows -v mysql-bin.000001 | grep -A5 -B5 '测试',确认 event 中的字符串字节与主库原始输入一致 - 用不同客户端连从库(如 mysql cli、Navicat、Python pymysql),统一设置
charset=utf8mb4参数,避免客户端自身编码干扰判断
最易被忽略的一点:即使所有配置都对了,如果从库上曾手动执行过 SET NAMES latin1 或类似语句,且该连接还在复用,那么后续 SQL 依然走错路径。排查时一定要看 SHOW PROCESSLIST 并确认 Command 为 Query 的连接的实际变量值。











