MySQL 报 ER_1366 Incorrect string value:从驱动到字段排查连接字符集链
参考:MySQL 8.4 手册 12.4 Connection Character Sets and Collations
报错原文
Warning: #1300 Invalid utf8 character string: 'F09F8D'
Warning: #1366 Incorrect string value: '\xF0\x9F\x8D\xB0' for column 'Text' at row 1
java.sql.SQLException: Incorrect string value: '\xF0\x9F\x90\xB0\xE5\xA4...' for column 'username' at row 1 ; error code [1366]
General error: 3988 Conversion from collation utf8mb3_general_ci into utf8mb4_general_ci impossible for parameter
\xF0\x9F\x8D\xB0 就是 🍰 的 UTF-8 字节。
根因:utf8 是 utf8mb3 的别名
MySQL 里的 utf8 等于 utf8mb3,每字符最多 3 字节,编码上限是 BMP(0xFFFF)。emoji(🍰 🐰 🎶)是 4 字节字符,一部分生僻汉字和数学符号同样是 4 字节。MySQL 5.5.3 起才有 utf8mb4(most bytes 4)。
写入失败的路径大致是这几条:
- 4 字节字符写进 utf8 列 —— 列本身要改成 utf8mb4
- 列已经是 utf8mb4 仍报错 —— 连接字符集和列字符集不匹配,客户端发来的字节服务器解释不了
- 迁移时未转换的二进制数据(latin1 → utf8 没动字节)
- 应用发原始字节,驱动的默认字符集和数据不一致
LOAD DATA INFILE用了错误的CHARACTER SET子句
客户端 / 连接层 / 服务端对象三处都要 utf8mb4
- 客户端:保证输出的字符串是 utf8mb4
- 连接层:连接字符集是 utf8mb4
- 服务端对象:实例参数、数据库、表、字段
手册里的连接机制:服务器把客户端发来的语句从 character_set_client 转换到 character_set_connection;带 introducer 的字面量(_utf8mb4、_latin2)由 introducer 决定字符集。collation_connection 只对字面量字符串比较有意义,字符串与列值比较时列自己的 collation 优先级更高。character_set_results 决定返回给客户端的字符集,覆盖结果数据、列名元数据、错误消息;设成 NULL 或 binary 则不做转换。
要看的变量
SHOW VARIABLES WHERE Variable_name LIKE 'character\_set\_%' OR Variable_name LIKE 'collation%';
重点看这几个,都应该落在 utf8mb4:
character_set_clientcharacter_set_connectioncharacter_set_databasecharacter_set_resultscharacter_set_servercollation_connection
不用关心的:character_set_filesystem、character_set_system(存元数据用,一直是 utf8)、character_sets_dir。
连接建立后确认服务端是否采纳:
SELECT @@character_set_client;
SET NAMES 与防止客户端覆盖
SET NAMES utf8mb4;
SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci';
SET NAMES 一次设定 client / connection / results 三个,第二种写法同时设连接 collation。
几个边界报错:
SET character_set_client='ucs2'→ERROR 1231 (42000): Variable 'character_set_client' can't be set to the value of 'ucs2'SET NAMES 'bogus'→ERROR 1115 (42000): Unknown character set: 'bogus'SET NAMES 'utf8mb4' COLLATE 'bogus'→ERROR 1273 (HY000): Unknown collation: 'bogus'mysql --default-character-set=bogus→Character set '...' is not a compiled character set;客户端认识但服务器不认识时,服务器回退默认字符集
用 my.cnf 固定下来:
[mysqld]
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
[client]
default-character-set = utf8mb4
[mysql]
default-character-set = utf8mb4
character-set-client-handshake = FALSE 让服务端忽略客户端握手时声明的字符集,避免被覆盖。也可以加 init_connect = 'SET NAMES utf8mb4',对所有非超级用户连接生效。
按实例 → 库 → 表 → 字段改
优先级从高到低。
实例:character_set_server = utf8mb4。RDS 在控制台改参数,重启生效。
ALTER DATABASE db_name CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE table_name
MODIFY column_name VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL;
ALTER TABLE `reactions`
CHANGE `emoji` `emoji` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL;
查对象当前的字符集:
SELECT character_set_name FROM information_schema.COLUMNS
WHERE table_name='user' AND column_name='displayname';
SELECT TABLE_NAME, TABLE_COLLATION FROM information_schema.TABLES;
SHOW CREATE DATABASE db1;
SHOW CREATE TABLE user;
SHOW FULL COLUMNS FROM user;
驱动侧
PHP mysqli:
$conn->set_charset("utf8mb4");
$conn->query("SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'");
DSN 里对应的键是 char_set,值用 'utf8mb4'。
Python:
mysql.connector.connect(..., charset='utf8mb4', collation='utf8mb4_unicode_ci')
SQLAlchemy:
mysql+pymysql://root@localhost/mydb?charset=utf8mb4
Go:
charset=utf8mb4&collation=utf8mb4_unicode_ci&parseTime=True&loc=Local
JDBC:用 Connector/J 5.1.13+。连接串不推荐配 characterEncoding,容易和驱动默认值冲突,可以用 useUnicode=true。
latin1 里存 UTF-8 的双重编码
latin1 列里如果已经存了 UTF-8 字节,直接 CONVERT TO utf8mb4 会把这些字节当成 latin1 再编码一次(double encoding)。得先把列改成 BINARY/VARBINARY 再 CONVERT,或者做字节级转换。
LOAD DATA INFILE 显式指定字符集:
LOAD DATA INFILE '/tmp/data.csv' INTO TABLE users
CHARACTER SET utf8mb4
FIELDS TERMINATED BY ',' ENCLOSED BY '"'
LINES TERMINATED BY '\n';
改完之后
utf8 → utf8mb4 对现有数据兼容,BMP 字符的存储方式相同,新增数据可以写 emoji。代价是存储空间预估要变:VARCHAR 按 4 字节/字符而不是 3 字节/字符算。
排查顺序:先确认列/表/库是不是 utf8mb4,再看 SHOW VARIABLES 里的 client / connection / results,最后看驱动连接串。ER_1366 基本都是某一环还停在 utf8,也就是 3 字节。