Incorrect string value: '\xF0\x9F\x98\x80' for column 是国内最常被搜的 MySQL 报错之一,标准答案“改成 utf8mb4”并不总能解决问题。这篇在本机把几种情形分开复现:列字符集不够、连接字符集错配、老表索引长度限制。
服务器默认: character_set_server=utf8mb4,collation_server=utf8mb4_0900_ai_ci 建表用 charset=utf8,实际: ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 insert 一个 emoji (U+1F600) -> DataError: (1366, "Incorrect string value: '\\xF0\\x9F\\x98\\x80' for column 'v' at row 1") insert 'a😀b' -> DataError: (1366, "Incorrect string value: '\\xF0\\x9F\\x98\\x80b' for column 'v' at row 1") insert 汉字 -> ok 表里的行: ((1, '汉字'),)
MySQL 的 utf8 是 utf8mb3 的别名,每字符最多 3 字节。报错里 \xF0\x9F\x98\x80 是出问题的字符的 UTF-8 字节,从坏字符开始一直到值末尾。
gbk 每字符最多 2 字节 默认排序规则 gbk_chinese_ci latin1 每字符最多 1 字节 默认排序规则 latin1_swedish_ci utf8mb3 每字符最多 3 字节 默认排序规则 utf8mb3_general_ci utf8mb4 每字符最多 4 字节 默认排序规则 utf8mb4_0900_ai_ci 英文 a UTF-8 1 字节 utf8mb3: ok utf8mb4: ok 汉字 汉 UTF-8 3 字节 utf8mb3: ok utf8mb4: ok 欧元符号 € UTF-8 3 字节 utf8mb3: ok utf8mb4: ok 生僻字 𠮷 (U+20BB7) UTF-8 4 字节 utf8mb3: 1366 utf8mb4: ok emoji 😀 UTF-8 4 字节 utf8mb3: 1366 utf8mb4: ok 带肤色的 emoji 👍🏽 UTF-8 8 字节 utf8mb3: 1366 utf8mb4: ok
凡是 UTF-8 编码 4 字节的字符(emoji、部分生僻汉字)在 utf8mb3 列里都会 1366;汉字和 € 是 3 字节,没有问题。
列字符集 连接 SET NAMES abc 汉 😀 latin1 utf8mb4 原样 报 1366 报 1366 latin1 latin1 原样 变样:'汉' 变样:'😀' latin1 gbk 原样 报 1366 报 1366 utf8mb3 utf8mb4 原样 原样 报 1366 utf8mb3 latin1 原样 变样:'汉' 变样:'😀' utf8mb3 gbk 原样 报 1406 变样:'馃榾' utf8mb4 utf8mb4 原样 原样 原样 utf8mb4 latin1 原样 变样:'汉' 变样:'😀' utf8mb4 gbk 原样 报 1406 变样:'馃榾' gbk utf8mb4 原样 原样 报 1366 gbk latin1 原样 报 1366 报 1366 gbk gbk 原样 报 1366 变样:'馃榾'
错配时只有一部分组合会报 1366,另一些组合写入成功但内容已经变成乱码(utf8mb4 列经 latin1 连接写入汉字,读回 汉)。同一个报错背后可能是列,也可能是连接。
表 big:100000 行,row_format=DYNAMIC,v varchar(255) 带普通索引 k(v) alter table big convert to character set utf8mb4 -> ok,453 ms 之后: ) ENGINE=InnoDB AUTO_INCREMENT=100001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;emoji 现在能写进去: ok 索引占的最大字节数: varchar(255) × 3 = 765,× 4 = 1020 建 COMPACT 老表 (utf8mb3): ok;行格式 Compact alter table cpt convert to character set utf8mb4 -> OperationalError: (1071, 'Specified key was too long; max key length is 767 bytes') 先 alter table cpt row_format=dynamic -> ok;再 convert -> ok
CONVERT TO CHARACTER SET utf8mb4 会重建整张表。DYNAMIC 行格式下直接成功,COMPACT 行格式的老表索引上限是 767 字节,255×4 = 1020 超了,先改行格式。
pymysql 不传 charset: ('utf8mb4', 'utf8mb4', 'utf8mb4')
pymysql charset=utf8mb4: ('utf8mb4', 'utf8mb4', 'utf8mb4')
pymysql charset=latin1: ('latin1', 'latin1', 'latin1')
utf8mb4 连接上执行 set names latin1 之后: ('latin1', 'latin1', 'latin1')character_set_client、character_set_connection、character_set_results 三个变量要一致。一条 SET NAMES 能改掉整个会话,连接池的初始化语句尤其要检查。