MySQL里的utf8和很多人理解的utf8不是一个东西。它最多存三个字节,标准编码里的四字节字符全都进不来。emoji正是四字节,插进去就报Incorrect string value,报错里跟着一串十六进制,那就是没能识别的字节。换utf8mb4才能存下,mb4指的就是最多四个字节。但只改连接不够。字符集在MySQL里分层:服务器默认、库、表、列,各管一段,另外还有连接层。任何一层不是utf8mb4,四字节字符就在那一层被截断或者拒绝。常见的情况是改了库忘了列,新表生效老表照旧。层级之间还有继承关系,父级改了子级不一定跟着变,这是排查最容易漏的点。
服务器默认这一层,查character_set_server和collation_server两个变量。新建库会继承它们,老环境里常常还是latin1或者旧的utf8。改这一层要写进配置文件再重启,SET改的只在当前会话有效。迁移环境的时候,这一步常常被跳过。
库和表这两层,SHOW CREATE DATABASE和SHOW CREATE TABLE能直接看到字符集写在哪一行。库是utf8mb4、表还是utf8,这种混搭很常见。列级还能单独设,建表语句里每一列都能带自己的字符集。表改了列没改,照样存不进去。查列级字符集,看information_schema里的columns表最直接。
程序连接参数要带charset=utf8mb4,PDO写进DSN,或者连上之后执行SET NAMES utf8mb4。连接层最容易被忽略,这一层不对,前面三层全是utf8mb4也白搭。表现往往是存进去变成问号,而不是直接报错。有的框架在配置里单独写了charset,只改DSN不生效,要翻框架自己的配置。
动手之前先备份,再改。ALTER TABLE后面跟CONVERT TO CHARACTER SET utf8mb4会把列一起转,这是最省事的一条路。它会重建表,大表执行时间长,期间可能锁写。几百万行的表要挑低峰,或者先在小表上验证一遍。只改默认字符集不转列,等于白改。ALTER TABLE后面跟DEFAULT CHARSET只影响以后新增的列。这个区别是很多人改完发现没生效的原因。动手之前先用一张小表试,语句跑通了再上大表。
utf8mb4下一个字符占四字节,varchar(255)的索引就是1020字节。InnoDB在DYNAMIC行格式下单列索引上限3072字节,够用。老版本COMPACT格式上限767字节,varchar(191)就是那个年代留下的写法。索引建不上,报的是key too long。索引长度要跟着一起算。改字符集之前先看一遍索引定义,别等改完才发现建不上。
插一条带emoji的记录,读出来跟原值逐字节对比。再查一次连接字符集,确认程序真的用了utf8mb4。两边都对上,才算改完。只验证能不能插入是不够的。测试数据要用真的emoji,别拿替代字符糊弄过去。
申请创业报道,分享创业好点子。点击此处,共同探讨创业新机遇!
