根本原因是触发器执行时字符集上下文与表 / 列定义不一致;需确保创建触发器时连接字符集为 utf8mb4,显式声明编码、避免裸字符串、正确处理跨编码转换,并优先在应用层完成编码清洗。

触发器里插入中文变问号或乱码
根本原因是触发器执行时的字符集上下文和表 / 列定义不一致,不是数据本身坏了。MySQL 默认用 latin1 启动连接时,哪怕表是 utf8mb4,触发器内部的字符串字面量也会按 latin1 解释,一拼接就丢字节。
- 检查触发器创建时的连接字符集:
SHOW VARIABLES LIKE 'character_set_client';,必须是utf8mb4 - 建触发器前显式声明编码:
SET NAMES utf8mb4;再CREATE TRIGGER - 避免在触发器里写裸字符串:
INSERT INTO log VALUES ('用户登录');→ 改成CONVERT('用户登录' USING utf8mb4) - 如果触发器调用函数,确保该函数的
DETERMINISTIC声明不影响字符集推导(它会影响 MySQL 对返回值编码的猜测)
BEFORE INSERT 触发器中对字段做编码转换失败
常见于从旧系统迁移时,源字段存的是 gbk 编码的二进制串,但目标表设为 utf8mb4,直接赋值会报错或截断。
- 不能用
CAST(new.col AS CHAR)—— 这依赖当前连接编码,不可控 - 正确做法是先转成二进制再解码:
CONVERT(CAST(new.col AS BINARY) USING gbk),再转目标编码:CONVERT(…… USING utf8mb4) - 注意
gbk不是 MySQL 内置别名,必须写全称;gb2312和gbk不兼容,混用会出 ? - 如果字段实际是
VARBINARY类型,跳过CAST …… AS BINARY这步,直接CONVERT(new.col USING gbk)
触发器里调用 CONVERT() 或 CAST() 导致性能骤降
每次 INSERT 都做全量编码转换,尤其在批量导入或高并发写入时,CPU 会明显上扬。这不是语法错,是设计误用。
- 优先在应用层或 ETL 阶段完成编码清洗,触发器只做轻量校验或补全
- 如果必须在触发器内转码,只转必要字段,避免对
TEXT或长VARCHAR字段无差别处理 -
CONVERT(…… USING xxx)比CAST(…… AS CHAR CHARACTER SET xxx)稍快,但差异有限;真正拖慢的是嵌套多层转换,比如CONVERT(CONVERT(……) USING utf8mb4) - 测试时用
EXPLAIN FORMAT=TRADITIONAL看不到触发器开销,得靠SHOW PROFILE或 slow log 抓实际执行耗时
MySQL 5.7 升级到 8.0 后触发器乱码更严重
8.0 默认 character_set_server=utf8mb4,但老触发器是 5.7 下用 latin1 连接创建的,元数据里没存编码意图,重载后行为会变。
- 用
SHOW CREATE TRIGGER xxx查看 DDL,如果里面没带CHARACTER SET utf8mb4注释,基本就是隐患点 - 不要直接
DROP + CREATE,先用SELECT @@character_set_client确认当前会话编码,再重建 - 8.0 的
validate_password插件启用时,可能拦截含非 ASCII 字符的触发器创建语句,关掉或临时切到sql_mode=''再操作 - 最稳妥的方式:把触发器逻辑抽成存储过程,过程头显式声明
CHARACTER SET utf8mb4,再让触发器调用它
字符集问题从来不在“设对了就行”,而在“每一层上下文是否都对齐”。触发器像一段嵌套在事务里的黑盒代码,它的编码环境比普通 SQL 更隐蔽、更难调试。