应使用utf8mb4替代utf8,因其支持完整4字节utf-8编码(含emoji和生僻字);但必须同步精简字段长度、优先varchar、控制前缀索引(如191)、采用utf8mb4_unicode_ci排序规则,并在my.cnf中配置character-set-server、collation-server和skip-character-set-client-handshake,否则因单字符最多占4字节反而拖慢索引与内存使用。

直接结论:用 utf8mb4 替代 utf8,但必须配合字段长度精简、VARCHAR 优先、前缀索引控制和 utf8mb4_unicode_ci 排序规则 —— 否则不仅不提速,反而因单字符占 4 字节拖慢索引和内存使用。
为什么 utf8 在 MySQL 里不能存 emoji
MySQL 的 utf8 是个历史包袱,实际叫 utf8mb3,最多只支持 3 字节编码。像 ?(U+1F30D)、?? 这类四字节 Unicode 字符会直接被截断或报错 Incorrect string value。这不是客户端或应用层的问题,是服务端解析时就丢数据。
而 utf8mb4 才是真正的 UTF-8 实现。MySQL 8.0 已将其设为默认,但老实例或迁移系统仍大量残留 utf8 配置。
- 检查当前库表字符集:
SHOW CREATE DATABASE db_name;和SHOW CREATE TABLE tbl_name; - 若看到
DEFAULT CHARSET=utf8或COLLATE=utf8_general_ci,就属于风险配置 -
utf8mb4下每个字符最多占 4 字节,但英文、数字仍为 1 字节,中文多为 3 字节,仅 emoji/生僻字才用满 4 字节
utf8mb4 对索引和存储的实际开销
字符集变大,直接影响索引键长度和行存储体积。例如一个 VARCHAR(255) 字段:
- 用
utf8:最大索引长度 = 255 × 3 = 765 字节(InnoDB 单列索引上限) - 用
utf8mb4:最大索引长度 = 255 × 4 = 1020 字节 → 超出限制,建索引会失败或自动截断 - 行内存储时,
CHAR(10)在utf8mb4下固定占 40 字节,而VARCHAR(10)只按实际内容 + 1~2 字节长度头存储
所以不能只改字符集,还要同步做三件事:
- 把无意义的
CHAR全换成VARCHAR - 将过长的字段长度收紧,比如
VARCHAR(255)存用户名 → 改为VARCHAR(50) - 对长文本字段建索引时,显式指定前缀长度,如
INDEX idx_title (title(191))(因为 191 × 4 = 764 ≤ 765)
my.cnf 中最关键的三项配置
光改表结构没用,连接层乱码或隐式转换会让所有优化归零。必须在服务端强制统一:
-
character-set-server = utf8mb4:保证新库/新表默认字符集 -
collation-server = utf8mb4_unicode_ci:比utf8mb4_general_ci更准(尤其对德语 ß、土耳其语 I),性能差距可忽略 -
skip-character-set-client-handshake:关键!禁用客户端声明的charset,防止 JDBC URL 里带useUnicode=true&characterEncoding=utf8这种过时参数反向覆盖服务端设置
重启 MySQL 后验证:SELECT @@character_set_server, @@collation_server; 必须返回 utf8mb4 和 utf8mb4_unicode_ci。否则配置未生效。
迁移存量数据时最易踩的坑
ALTER TABLE CONVERT TO 不是万能的“一键升级”,它会重写整张表,且可能 silently 失败:
- 如果字段含
TEXT或BLOB,且定义了NOT NULL但值为空字符串,CONVERT会报错Invalid utf8mb4 character string - 外键约束、全文索引、生成列(generated column)会阻止 CONVERT,需先删后加
- 执行前必须
mysqldump --no-create-info备份原始数据,因为CONVERT不保证可逆 - 线上表建议用 pt-online-schema-change 分批改,避免锁表
真正影响速度的从来不是字符集本身,而是它如何与字段类型、索引设计、连接行为耦合。一个 VARCHAR(255) + utf8mb4 + 全字段索引的组合,比 utf8 下同结构慢 30% 以上 —— 这不是字符集的锅,是设计没跟上。











