一、问题现象
执行 SQL 时出现如下错误:
sql
1267 - Illegal mix of collations (utf8mb4_0900_ai_ci,IMPLICIT) and (utf8mb4_unicode_ci,IMPLICIT) for operation '='
该错误通常出现在 JOIN ON、WHERE、GROUP BY、ORDER BY 或字符串函数比较中,典型场景如下:
sql
SELECT *
FROM table_a a
JOIN table_b b ON a.code = b.code;
如果 a.code 和 b.code 都是字符串字段,但两者排序规则不同,例如一个是 utf8mb4_0900_ai_ci,另一个是 utf8mb4_unicode_ci,MySQL 在执行 = 比较时就可能报错。
二、问题原因
MySQL 字符串字段由两个概念共同决定:
- 字符集:例如
utf8mb4 - 排序规则:例如
utf8mb4_general_ci、utf8mb4_unicode_ci、utf8mb4_0900_ai_ci
本次问题不是简单的 SQL 语法错误,而是参与比较的字符串字段或表达式排序规则不一致。
错误中的 IMPLICIT 表示两边通常都是字段本身的隐式排序规则。由于两边优先级相同,MySQL 无法自动选择一个排序规则,所以报错。
目标统一规范:
text
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci
说明:utf8mb4 是字符集,utf8mb4_general_ci 是排序规则。日常沟通中可以说"统一编码",但实际 SQL 修改时需要同时关注字符集和排序规则。
三、排查 SQL
1. 查看当前库默认字符集和排序规则
sql
SELECT
DEFAULT_CHARACTER_SET_NAME,
DEFAULT_COLLATION_NAME
FROM information_schema.SCHEMATA
WHERE SCHEMA_NAME = DATABASE();
2. 查看某张表的建表语句
sql
SHOW CREATE TABLE 表名;
3. 查看某张表字段排序规则
sql
SHOW FULL COLUMNS FROM 表名;
4. 查看当前库所有字符字段排序规则
sql
SELECT
TABLE_NAME,
COLUMN_NAME,
COLUMN_TYPE,
CHARACTER_SET_NAME,
COLLATION_NAME
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND COLLATION_NAME IS NOT NULL
ORDER BY TABLE_NAME, COLUMN_NAME;
5. 只查看非 utf8mb4_general_ci 的字段
sql
SELECT
TABLE_NAME,
COLUMN_NAME,
COLUMN_TYPE,
CHARACTER_SET_NAME,
COLLATION_NAME
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND COLLATION_NAME IS NOT NULL
AND COLLATION_NAME <> 'utf8mb4_general_ci'
ORDER BY TABLE_NAME, COLUMN_NAME;
四、不同层次的解决方案
方案一:SQL 临时层处理
适用场景:
- 线上临时排障
- 暂时不能修改表结构
- 只需要让某条 SQL 先执行通过
示例:
sql
SELECT *
FROM table_a a
JOIN table_b b
ON a.code COLLATE utf8mb4_general_ci = b.code COLLATE utf8mb4_general_ci;
也可以只对其中一侧做显式转换:
sql
SELECT *
FROM table_a a
JOIN table_b b
ON a.code = b.code COLLATE utf8mb4_general_ci;
优点:
- 改动小
- 见效快
- 不影响表结构
缺点:
- 不是根治方案
- SQL 可读性变差
- 可能影响索引使用
- 后续其他 SQL 仍可能继续报错
结论:只建议作为临时应急方案,不建议作为长期治理方案。
方案二:字段层处理
适用场景:
- 只有少数字段排序规则不一致
- 已经明确是哪两个字段在比较时报错
- 希望精确修复,避免影响整张表
修改单个 VARCHAR 字段示例:
sql
ALTER TABLE 表名
MODIFY 字段名 VARCHAR(255)
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
修改 TEXT 字段示例:
sql
ALTER TABLE 表名
MODIFY 字段名 TEXT
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
如果字段原本有 NOT NULL、默认值、注释等属性,必须完整保留,例如:
sql
ALTER TABLE 表名
MODIFY 字段名 VARCHAR(255)
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci
NOT NULL DEFAULT ''
COMMENT '字段说明';
优点:
- 影响范围最小
- 适合精确修复
- 风险相对可控
缺点:
- 需要逐个字段确认原始定义
- 如果冲突字段较多,执行成本较高
- 容易遗漏其他潜在冲突字段
结论:当问题集中在少数字段时,优先使用字段层修复。
方案三:表层处理
适用场景:
- 某张表内多个字符串字段排序规则不一致
- 该表经常参与关联查询
- 希望统一整张表的字符字段
将整张表已有字符字段转换为 utf8mb4_general_ci:
sql
ALTER TABLE 表名
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
该语句会转换表中已有的 CHAR、VARCHAR、TEXT 等字符类型字段。
如果只修改表的默认字符集和排序规则,不转换已有字段:
sql
ALTER TABLE 表名
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
注意:DEFAULT CHARACTER SET 只影响后续新增字段,不会自动修复已有字段的排序规则冲突。
优点:
- 能统一整张表已有字符字段
- 比逐字段处理更彻底
- 适合表内字段规则混乱的情况
缺点:
- 影响范围大于字段层
- 大表执行时间可能较长
- 执行期间可能产生锁表或性能影响
- 需要提前评估索引、字段长度、业务低峰执行窗口
结论:当冲突集中在某张表或某几张表时,表层转换是较优方案。
方案四:数据库层处理
适用场景:
- 希望统一整个数据库后续建表默认规则
- 当前库默认排序规则不是
utf8mb4_general_ci - 需要从源头避免新表、新字段继续产生不一致
修改当前数据库默认字符集和排序规则:
sql
ALTER DATABASE 数据库名
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
注意:该语句只修改数据库默认设置,主要影响后续新建表。已有表和已有字段不会自动全部转换。
如果要彻底统一已有对象,需要结合表层方案,逐表执行:
sql
ALTER TABLE 表名
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
优点:
- 从数据库默认配置层面统一规范
- 避免后续新增表继续产生排序规则不一致
- 适合作为长期治理动作
缺点:
- 不能单独解决已有字段冲突
- 仍需要配合字段层或表层处理历史数据表
结论:数据库层修改是长期规范治理动作,但不是单独的修复闭环。
方案五:连接层和会话层处理
适用场景:
- 程序连接 MySQL 时字符集不稳定
- SQL 中有字符串常量、临时表、函数表达式参与比较
- 希望应用连接和数据库规则保持一致
会话层是指当前 MySQL 连接级别的设置。该方案不修改数据库、表、字段结构,只影响当前连接或当前会话。
最直接的会话级设置:
sql
SET NAMES utf8mb4 COLLATE utf8mb4_general_ci;
该语句的目标是让当前连接统一使用:
text
character_set_client = utf8mb4
character_set_connection = utf8mb4
character_set_results = utf8mb4
collation_connection = utf8mb4_general_ci
执行后可以用如下 SQL 检查:
sql
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';
如果希望写得更明确,也可以拆成如下语句:
sql
SET character_set_client = utf8mb4;
SET character_set_connection = utf8mb4;
SET character_set_results = utf8mb4;
SET collation_connection = utf8mb4_general_ci;
会话层主要解决以下类型的问题:
sql
WHERE name = '张三'
sql
WHERE code = CONCAT('A', '001')
sql
CREATE TEMPORARY TABLE ...
这类问题通常与字符串常量、函数表达式、临时表、当前连接字符集或排序规则有关。
如果报错来自两个表字段直接比较,例如:
sql
a.code = b.code
并且 a.code 是 utf8mb4_0900_ai_ci,b.code 是 utf8mb4_unicode_ci,只修改会话层通常不能根治,因为两个字段自己的排序规则仍然不一致。
此时临时可以在 SQL 中显式指定排序规则:
sql
ON a.code COLLATE utf8mb4_general_ci = b.code COLLATE utf8mb4_general_ci
长期仍应回到字段层或表层统一:
sql
ALTER TABLE 表名
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
JDBC 连接层建议确保使用 utf8mb4,并避免应用连接默认排序规则与库表字段不一致。如果连接池支持连接初始化 SQL,可以在每个连接初始化时执行:
sql
SET NAMES utf8mb4 COLLATE utf8mb4_general_ci
常见连接池配置项名称可能是 connectionInitSql、connection-init-sqls、initSQL 等,具体以项目实际使用的连接池为准。
优点:
- 可以减少字符串常量、临时表达式带来的排序规则差异
- 有助于应用层和数据库层保持一致
- 不改库表结构,适合作为临时验证或连接规范补充
缺点:
- 不能修复已有表字段本身的排序规则冲突
- 需要结合数据源配置统一落地
- 只对当前连接或当前会话生效,连接断开后失效
- 如果是字段与字段之间的排序规则冲突,仍需要字段层或表层修复
结论:连接层和会话层属于配套治理,可以减少连接、字符串常量、临时表达式带来的排序规则差异,但不能替代字段、表、数据库层的结构治理。
五、推荐执行顺序
- 先备份数据库,尤其是涉及生产环境时必须先备份。
- 使用排查 SQL 找出所有非
utf8mb4_general_ci的字符字段。 - 如果只涉及少数字段,优先使用字段层修复。
- 如果某张表大量字段不一致,使用表层
CONVERT TO统一。 - 修改数据库默认字符集和排序规则,保证后续新表默认统一。
- 检查应用连接配置,确保连接层使用
utf8mb4。 - 重新执行原报错 SQL 验证问题是否解决。
六、执行注意事项
- 生产环境执行前必须备份。
- 大表执行
ALTER TABLE ... CONVERT TO前需要评估执行时间、锁表影响和业务低峰窗口。 - 字段层
MODIFY必须保留原字段类型、长度、是否允许空、默认值、注释等定义。 - 如果字段上存在索引,字符集或排序规则转换可能影响索引长度,需要提前确认。
- MySQL 8 默认排序规则常见为
utf8mb4_0900_ai_ci,如果项目统一要求utf8mb4_general_ci,建库、建表、建字段都应显式指定。 - 不建议长期依赖 SQL 中手写
COLLATE的方式规避问题。
七、最终目标
数据库、表、字段、连接层尽量统一到如下规则:
text
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci
这样可以避免因为 utf8mb4_0900_ai_ci、utf8mb4_unicode_ci、utf8mb4_general_ci 混用导致的字符串比较异常。