简单的 MySQL Emoji 支持方案(BMP、SMP 和 utf8mb4)

前言

最近由于抽空比较多的在进行博客 API 后端和手机客户端的开发,没太多精力和时间更新文章。但是想到点什么和发现些什么还是非常想写出来的。

因为在手机端测试发文,难免就想打几个可爱的 emoji 表情,但是发现只要存在 emoji 就无法成功保存。虽然这在情理之中,但是还是想一探究竟这背后深层的原理。(因为处理 emoji 问题其实很常见 😅)

于是深入查了下资料,便有了本文。:)

BMP

BMP 的全称是 Basic Multilingual Plane (基本多语言平面),其中的 P 和 BM 表示的意思应该分开。P(Plane)是 Unicode 里边的一种概念,是多种编排方案的划分区段。
BMP 又被称作 「0 号平面」,范围是:U+0000 至 U+FFFF 。包含有最常见的字符:各种国家的文字、全角半角符号以及一部分特殊字符符号等。
一个 Unicode 字符的表示形式例如:’\u0000’,其中 “反斜杠 + u” 表示无符号的十六进制,而 ‘0000’ 就是这种类型的数字,它在十进制的取值范围是 0 - 65536,对应的 16 进制就是 0000 至 ffff 。所以 BMP 的范围就是:’\u0000’ ~ ‘\uffff’ 。

SMP

Supplementary Multilingual Plane(多文种补充平面)被称作「1 号平面」,跟 BMP 不在同一个编排区段。SMP 的起始对应 0 号平面的结尾(十进制)加上 65536 作为结尾,转换为 16 进制便是 SMP 的范围,即:U+10000 至 U+1FFFF 。

它包含了:某些领域中使用的特殊符号、埃及象形文字和楔形文字,还有例如 Shavian 和 Deseret 字母等我也没了解过的乱七八糟的东西。其中符号就包括了 emoji ,即 emoji 属于 SMP 范围,不是常见的 BMP 字符。

有关 Unicode 的更多知识请参考 Wiki 页面等专业文献。

utf8mb4

这里是 MySQL 官方文档中关于 utf8mb4 的说明,其主要指出四点:

  1. utf8 仅能包含 BMP 字符,无法存储 BMP 以上平面的字符。
  2. utf8mb4 采用四个字节存储(区别于 utf8 的三个字节)一个 Unicode 字符,能够保存 BMP 以上平面的字符。
  3. utf8mb4 对 BMP 字符的储存和 utf8 有相同的特性。
  4. utf8mb4 是 utf8 的超集,完全兼容 utf8 的旧数据过渡。

所以我们明白了:MySQL 的 utf8 等字符集类型无法储存 BMP 以上平面的字符,所以无法保存 emoji 表情。而将utf8 换成 utf8mb4 几乎没有害处,并且支持更高平面字符储存,自然也就解决了 emoji 表情问题。

配置 MySQL

给 my.cnf(或者被加载的其它配置文件)加上如下配置:

[client]
default-character-set = utf8mb4

[mysql]
default-character-set = utf8mb4

[mysqld]
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci

注意:修改以后要重启 MySQL,如果特殊指定过库或表的字符集那么可能不会生效。

测试:

mysql> insert into smp_table(content) values('😇');
mysql> insert into smp_table(content) values('❥');
mysql> insert into smp_table(content) values('𝌆');
mysql> select * from smp_table;
+---------+
| content |
+---------+
| 😇        |
| ❥       |
| 𝌆        |
+---------+

可以看到,除了支持 emoji 表情之外,还可以储存一些奇奇怪怪的符号。😃

不需要任何的针对特殊 Unicode 区段字符的编码储存然后解码返回,即可兼容 emoji,算是最原生绿色的方式了。☺

注意:如果你的 MySQL 不支持 utf8mb4,请确认在 5.5 版本或以上,否则请升级 MySQL 。

客户端字符集

如果数据库配置好以后,发现应用仍然无法储存 emoji 或者读取出来是 ‘?’ 之类的奇怪字符,那么可能是客户端字符集不是 utf8mb4 导致的。
你可以输入以下 SQL 查看生效后的字符集(客户端甚至指的是你的应用程序,所以请使用应用程序执行下列 SQL 查看结果):

mysql> SHOW VARIABLES WHERE Variable_name LIKE 'character\_set\_%' OR Variable_name LIKE 'collation%';
+--------------------------+--------------------+
| Variable_name            | Value              |
+--------------------------+--------------------+
| character_set_client     | utf8mb4            |
| character_set_connection | utf8mb4            |
| character_set_database   | utf8mb4            |
| character_set_filesystem | binary             |
| character_set_results    | utf8mb4            |
| character_set_server     | utf8mb4            |
| character_set_system     | utf8               |
| collation_connection     | utf8mb4_unicode_ci |
| collation_database       | utf8mb4_unicode_ci |
| collation_server         | utf8mb4_unicode_ci |
+--------------------------+--------------------+

如果 character_set_results 变量值不是 utf8mb4 那么就有客户端字符集错误的可能,并且也不会得到正确的 SMP 字符。

解决办法就是指定客户端连接字符集,方式是设置连接字符串时的变量。一般来讲,是这样的:

<connetion_url>?charset=utf8mb4

上面 charset 变量的命名取决于你所使用的语言的 MySQL 驱动,可能命名不同但是基本都提供有。

但有些是不需要手动指定的,例如 Java 的 MySQL Connector 会自动读取 character_set_server 执行相应的 SET NAMES <charset>

最后

请容许我多发一些 emoji:

😀😁😂🤣😃😄😅😘😍😎😋😊😉😆😗😙😚☺️🙂🤗🤔😐😑😶🙄😏😣😴😫😪😯🤐😮😥😌🤓😛😜😝🤤😒😓😔😕🙃🤑😲😢😤😟😞😖🙁☹️😭😦😧😨😩😬😰🤠🤧😱🤡😈😳🤥👿😵😷👹😡🤒👺😠🤕💀😇🤢☠️😸😾👻😹🙈👽😻🙉👾😼🙊🤖😽👦💩🙀👧😺😿👨👩‍🎓👩‍💼👩👩‍🏫👩‍🔬👴👩‍⚖️👩‍💻👵👩‍🌾👩‍🎤👶👩‍🍳👩‍🎨👼👩‍🔧👩‍✈️👩‍⚕️👩‍🏭👩‍🚀👱🤰👩‍🚒🎅👲👮🤶🙍🕵️👸🙎💂🤴🙅👷👰🙆👳🤵💁🚶👤🙋🏃👥🙇💃👫🤦🕺👬👯👯👭💆🕴️💏💇🗣️👨‍❤️‍💋‍👨👨‍👩‍👧‍👦👨‍👨‍👧‍👧👩‍❤️‍💋‍👩👨‍👩‍👦‍👦👩‍👩‍👦💑👨‍👩‍👧‍👧👩‍👩‍👧👨‍❤️‍👨👨‍👨‍👦👩‍👩‍👧‍👦👩‍❤️‍👩👨‍👨‍👧👩‍👩‍👦‍👦👪👨‍👨‍👧‍👦👩‍👩‍👧‍👧👨‍👩‍👧👨‍👨‍👦‍👦👨‍👦👩‍👧☝️👨‍👦‍👦👩‍👧‍👦👆👨‍👧👩‍👧‍👧🖕👨‍👧‍👦💪👇👨‍👧‍👧🤳✌️👩‍👦👈🤞👩‍👦‍👦👉🖖👎👏🤘✊✍️🤙👊👐🖐️🤛🙌✋🤜🙏👌🤚🤝👍👋💅👅💕👂👄💖👃💋💗👣💘💙👀❤️💚👁️💓💛👁️‍🗨️💔💜💤💬🖤💢🗨️💝💣🗯️💞💥💭💟💦🕳️❣️💨👓💌💫🕶️👚👟👔👛👠👕👜👡👖👝👢👗🛍️👑👘🎒👒👙👞🎩🎓⛑️📿💄💍💎