数据库与编码
# 19.数据库与编码
以 MySQL 为例。在 MySQL 中,utf8 编码并不是真正意义上的 utf8,而是 utf8mb3。utf8mb3 支持的字符集是 0~65535 之间的 BMP 的字符,最多支持 3 个字节,一些生僻字和 emoji 是不支持。
以 MySQL 为例。在 MySQL 中,utf8 编码并不是真正意义上的 utf8,而是 utf8mb3。utf8mb3 支持的字符集是 0~65535 之间的 BMP 的字符,最多支持 3 个字节,一些生僻字和 emoji 是不支持。
这会导致什么问题呢?如果你数据库使用的是 utf8mb3 编码,那么在 insert 一些 4 字节的字符的时候,会报错:
insert into t (name) value('😂')
SQL 错误 [1366] [HY000]: Incorrect string value: '\xF0\x9F\x98\x82' for column 'name' at row 1
insert into t (name) value('𡋾')
SQL 错误 [1366] [HY000]: Incorrect string value: '\xF0\xA1\x88\xBE' for column 'name' at row 1
2
3
4
5
其实,这并不是 MySQL 自己的问题,而是 Unicode 在一开始的时候就没确定好到底用几个字节存储,当时的方案并不完善;等到了后来,Unicode 标准才正式发布,确定了存储方案。
于是,在 2010 年,MySQL 发布了 MySQL 5.53,在创建数据库时可以用一个新的字符集 utf8mb4,支持 4 个字节的字符
官方文档醒目的提醒了大家说 utf8mb3 会在未来的版本中删除,并建议用户优先使用 utf8mb4。所以在创建数据库的时候,确保的使用是 utf8mb4,而不是 utf8mb3。
Applications that use UTF-8 data but require supplementary character support should use
utf8mb4
rather thanutf8mb3
(see Section 10.9.1, “The utf8mb4 Character Set (4-Byte UTF-8 Unicode Encoding)” (opens new window)).
可以通过查看数据库的建表语句来检查:
show create database 数据库名\G;
-- 例如
mysql> show create database dblog\G;
*************************** 1. row ***************************
Database: dblog
Create Database: CREATE DATABASE `dblog` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */
1 row in set (0.00 sec)
2
3
4
5
6
7
笔者在 MySQL 8.0.27 下尝试了下,默认都是用的 utf8mb4 编码了,而在很久之前,默认编码是 utf8mb3。
如果是早期的数据库表,有需求要兼容 4 个字节的字符的话,可以修改对应的字段的编码或数据库的编码。
# 编码的重要性
编码是一件非常重要的事情。现代很多系统都要求支持 Unicode。如果不支持,有什么后果呢?
举个例子,如果某个人名字带有生僻字,银行的系统不支持,那么这个客户就不能去该银行做业务,直接损失了一名客户。
例如:在 有没有谁的朋友名字中含有“䶮”字的? - 知乎 (opens new window) 里有人提到:
本人有两张储蓄卡,招商和工商,招商的户名是某䶮,工商银行的户名是某(YAN3),亲身经历证明:招商储蓄卡无法变更为公积金联名卡,主要原因是姓名不一致,因此肯定没有办法提取公积金
一个名字叫“䶮”的人的苦恼:开不了银行账户,用不了微信、支付宝 (opens new window)
近日,多位人士向记者表示,自己的名字中含有 “䶮”等汉字,在办理公积金、银行卡、第三方支付、手机卡等金融、通信业务时,名字无法被验证,导致业务无法办理。
甚至,因为没法申请银行账户,微信、支付宝也拒绝使用,有的银行建议名字带有“䶮”字的用户改回繁体字“龑”。
生僻字带来金融交易中的麻烦,这一情况并不鲜见。
# 演示
实践出真知,我们来演示下,本文是在 Windows 下的 MySQL 5 演示。
首先创建一个数据库,并指定字符集
mysql> create database testunicode default character set utf8 collate utf8_general_ci;
Query OK, 1 row affected (0.00 sec)
2
创建完后,可以检查下该数据库的编码
mysql> show create database testunicode;
+------------+--------------------------------------------------------+
| Database | Create Database |
+------------+--------------------------------------------------------+
| testunicode | CREATE DATABASE `testunicode` /*!40100 DEFAULT CHARACTER SET utf8 */ |
+-------------+--------------------------------------------------------+
1 row in set (0.00 sec)
2
3
4
5
6
7
使用该数据库并创建表,可以看到该表的默认编码是 UTF8
mysql> use testunicode;
Database changed
mysql> create table t (name varchar(50));
Query OK, 0 rows affected (0.25 sec)
mysql> show create table t
CREATE TABLE `t` (
`name` varchar(50) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
2
3
4
5
6
7
8
9
10
尝试 insert 一个 emoji,确实有报错:
insert into t (name) value('😂')
SQL 错误 [1366] [HY000]: Incorrect string value: '\xF0\x9F\x98\x82' for column 'name' at row 1
2
3
接下来我们尝试更换字符集,并创建一个新表:
mysql> ALTER DATABASE `testunicode`
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_bin;
mysql> create table tu (name varchar(50))
mysql> show create table t
CREATE TABLE `t` (
`name` varchar(50) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
2
3
4
5
6
7
8
9
10
尝试重新 insert,结果是能成功 insert(没有报错就是成功):
mysql> insert into tu (name) value('😂')
# 小结
虽然目前 MySQL 默认都是使用 utf8mb4 了,但一些早期的项目还是使用的 utf8mb3,希望读者们如果遇到了该编码问题能有思路,知道是怎么回事、怎么修复。
触类旁通,其他关系型数据库,例如 Oracle 等,也需要考虑编码问题
# 参考
英文版(需科学上网)In MySQL, never use “utf8”. Use “utf8mb4”. | by Adam Hooper | Medium (opens new window)
中文版:记住,永远不要在 MySQL 中使用“utf8”_数据库_Adam Hooper_InfoQ 精选文章 (opens new window)
一个名字叫“䶮”的人的苦恼:开不了银行账户,用不了微信、支付宝 (opens new window)
曝光 MySQL 的 一个坑,不要再使用 UTF-8 了? (opens new window)