Django 中使用 utf8mb4 支持 emoji 表情

事情起因是某天编辑博客时,发现保存时后台报错了,通过定位,发现 Django 报了如下错误:

嗯?编码相关?想了下,这次编辑跟以往的唯一区别就是插入了一个 emoji 表情,看来是我的数据库不支持 emoji 导致的报错了😛。既然知道了问题,那查找原因也就十分简单了。

1. 什么是 utf8

理论上,utf8 使用 1~6 个字符,

实际上,最新的 utf8 规范只使用 1~4 字节,最大能编码 21 位,正好能够表示所有的 17 个 Unicode 平面。

2. 什么是 utf8mb4

utf8mb4 是 utf8 的超集,理论上原来使用 utf8,然后将字符集修改为 utf8mb4,也不会对已有的utf8 编码读取产生任何问题。

3. MySQL 中的 utf8

MySQL 中的 utf8,只支持最长三个字节的 utf8 字符,也就是 Unicode 中的基本多文本平面。

仅使用三个字符的原因可能是,基本多文种平面之外的字符很少用到。

而在 MySQL 5.5.3 版本后,要在 Mysql 中保存 4 字节长度的 UTF-8 字符,就可以使用 utf8mb4 字符集了。例如可以用 utf8mb4 字符编码直接存储 emoj 表情,而不是存表情的替换字符。

4. Django 解决 \xF0\x9F\x90\xAF 错误

如果在 MySQL 的 utf8 字符集上写入表情字符,就会提示 Incorrect string value: ’\xF0\x9F\x90\xAF’ for column ... 错误。

解决办法就是,修改表中相关列或表的编码格式,然后在 Django 中配置访问数据库编码方式。由于 utf8mb4 是 utf8 的超集,兼容 utf8 的数据,不需要修改原来的数据,就可以正常的使用了。

4.1 修改 MySQL 编码

4.1.1 查看 MySQL 默认编码

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

4.1.2 查看数据库编码

show create database mydatabase;
+----------+--------------------------------------------------------------------+
| Database | Create Database                                                    |
+----------+--------------------------------------------------------------------+
|   mydb   | CREATE DATABASE `mydb` /*!40100 DEFAULT CHARACTER SET latin1 */ |
+----------+--------------------------------------------------------------------+

可以看到默认的数据库编码是 latin1。

4.1.3 查看数据表编码

show create table mytable;
| blog_article | CREATE TABLE `blog_article` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(200) NOT NULL,
  `head_img` varchar(100) NOT NULL,
  `read_hit` int(11) NOT NULL,
  `like_hit` int(11) NOT NULL,
  `content` longtext NOT NULL,
  `abstract` longtext,
  `create_time` datetime NOT NULL,
  `update_time` datetime NOT NULL,
  `allow_comment` tinyint(1) NOT NULL,
  `public` tinyint(1) NOT NULL,
  `author_id` int(11) NOT NULL,
  `cat_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `blog_content_4f331e2f` (`author_id`),
  KEY `blog_content_05e7bb57` (`cat_id`)
) ENGINE=MyISAM AUTO_INCREMENT=193 DEFAULT CHARSET=utf8 |

🤔... 可以看到我的数据表编码是 utf8,没法支持 emoji。

4.1.4 修改 MySQL 数据库(表)编码

# 修改某个数据库
ALTER DATABASE database_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 CHANGE column_name column_name VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL;

4.2 Django 升级到 utf8mb4 配置

DATABASES = {
    'default': {
        ...
        'OPTIONS': {
            'charset': 'utf8mb4'
        },
    },
}

4.3 修改 MySQL 配置[可选]

vim /etc/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  

5. 参考