主从复制乱码主因是字符集不一致:主库用utf8mb4写入,从库character_set_server为latin1,导致字节被错误解析为乱码(如“æ–‡”或???);须统一主从character_set_server为utf8mb4并持久化配置,仅set names无法根治。

主从复制出现乱码,不是表结构或数据本身坏了,而是主库写入时用的编码,和从库解析 binlog 时用的编码对不上——最常踩的坑是 character_set_server 和 character_set_client 在从库上不一致。
为什么从库查出来是 ??? 或 æ–‡ 而不是中文
MySQL 主从复制不校验字符集。只要 SQL 能执行,就照搬。如果主库 character_set_server = utf8mb4,但从库 character_set_server = latin1,那么主库写入的 utf8mb4 字节流,会被从库当成 latin1 解析,一个中文被拆成两三个字节分别当拉丁字符处理,结果就是 mojibake(如“æ–‡”)或一堆 ?。
典型现象包括:
- 主库
SELECT正常,从库查同一行显示???或乱码字节序列 -
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',说明底层存储层已拒绝四字节字符
必须统一 character_set_server,且要持久化
这个变量决定新数据库、新表的默认字符集,也影响 IO 线程连接主库时的上下文初始化。仅靠 SET NAMES utf8mb4 或改表结构,救不了根本。
操作要点:
- 主从两端都检查:
SHOW VARIABLES LIKE 'character_set_server';,必须完全一致,推荐设为utf8mb4 - 修改配置文件
my.cnf的[mysqld]段落,添加:character-set-server = utf8mb4 collation-server = utf8mb4_0900_ai_ci
- 重启 MySQL 生效;
SET GLOBAL character_set_server = 'utf8mb4'只对新连接有效,且不持久 - 注意:
character_set_server不会自动转换已有库/表,需单独执行ALTER DATABASE db_name CHARACTER SET = utf8mb4;和ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4;
SET NAMES utf8mb4 该在哪用、不该在哪用
SET NAMES utf8mb4 等价于同时设置 character_set_client、character_set_connection、character_set_results。它只作用于当前连接,不能替代服务端配置,但能快速验证问题是否出在连接层。
使用边界很明确:
- ✅ 应用连接池初始化时必须执行(如 JDBC URL 加
?useUnicode=true&characterEncoding=utf8mb4,PHP PDO 加PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4") - ✅ 用
mysql客户端手动连从库调试时,第一句就跑:SET NAMES utf8mb4; - ❌ 不要用在从库的 SQL 线程上下文中去“修复”历史乱码数据——它只影响后续语句的解析,不影响已同步进来的错误字节
- ❌ 不要客户端发的是 latin1 编码字节,却执行
SET NAMES utf8mb4,这会导致 double-encoded(双编码),乱得更彻底
容易被忽略的链路断点:binlog 格式与 init_connect
即使 server 和 connection 都设对了,如果 binlog 记录方式或连接初始化没兜住,照样出问题。
- binlog_format 推荐用
ROW:语句级(STATEMENT)模式下,SET NAMES会被记录并重放,但重放时只影响从库 SQL 线程的会话变量,不改变数据本体;而ROW模式直接同步行变更,绕过字符集解析歧义 - 在从库
my.cnf中加init_connect='SET NAMES utf8mb4',确保所有新连接(包括复制线程使用的连接)一建立就带对编码 - 加
skip-character-set-client-handshake可强制忽略客户端声明的字符集,防止应用层传错值覆盖服务端配置 - 验证是否生效:在从库执行
SHOW SLAVE STATUS\G,看Slave_IO_Running和Slave_SQL_Running是否为Yes,再查SHOW VARIABLES LIKE 'character_set%'确认三连项是否全为utf8mb4
真正难的不是改哪一行配置,而是确认「主库写入时用什么编码」和「从库重放时按什么编码解」这两件事,在每一个环节(server、connection、binlog、client)都对齐。漏掉任意一层,乱码就会在某个时间点突然冒出来。











