分享

五分钟教你彻底理解MySQL中文乱码

 雅藏轩 2024-04-11 发布于河北

突发状况

周末,当我在峡谷马上拿下五杀时,突然手机弹出消息。

"线上系统的评论功能不能用了"。

还没等我反应过来,一个视频会议电话已经打进来了。作为社畜只能默默放下手机,打开电脑接入会议。

刚进入会议,产品疯狂吐槽。

"刚给用户开通灰度,用户怎么就用不了";

"重试好几次都不行";

"每次发表情都不行";

听到这,有经验的研发兄弟应该大概猜出来了,跟数据库的字符集有关系。

于是迅速登录mysql,查询了评论表的字符集,发现是utf8。于是马上提单修改字符集为utf8mb4。问题马上解决了。

alter table `table_name` modify clo_name varchar(20) character set utf8mb4;

utf8 其实是 utf8mb3 的别名,只使用 1~3 个字节表示字符。
utf8mb4 使用 1~4 个字节表示字符,能够存储更多的 emoji 表情及任何新增的 Unicode 字符。

utf8mb4 兼容 utf8 ,且比 utf8 能表示更多的字符,是 utf8 字符集的超集。所以现在一些新的业务建议将数据库的字符集设置为 utf8mb4 ,特别是有表情存储需求时。
正是因为utf8mb4兼容utf8,所以可以直接改。如果是其他字符集就不能直接改了。

复盘

维护的这个评论系统非常的老,前端支持的编辑器也很老,原本只支持纯文本格式,所以之前一直相安无事。但是上周老大提了个需求,让前端支持富文本编辑器。但是前端哥们没有跟我沟通,我也忘记查评论表字段的字符集了,导致了一个低级问题。

图片

本来事情到这就结束了,但是聪明的我仔细想了下,明明存到计算机里都是0和1,为啥会报错呢?

要解释这个问题,我们还得从字符集编码这个话题开始聊起。

我们知道对于计算机而言,所有的数据都是以0和1的形式存储在磁盘中的,包括在网络上进行传输的信息都需要变为0和1再进行传输。

也就是需要一个机制 把人的语言变为计算机的语言,我们称之为编码。

同样的需要一个机制 把计算机的语言变为人的语言,我们称之为解码。

上面两个场景结合起来,统称为字符编码。

那我们平常总是遇到的乱码是什么情况呢?

那就是同一份数据的编码方和解码方使用的规则不一样导致的。

比如下面这样的编解码就容易友尽。

图片

一、ASCII码

既然有了通信的需求,那么就需要指定一套规则。只要大家都遵循这套规则,就万事大吉了,不会鸡同鸭讲了。完美实现同一个世界同一个梦想。

最开始,美国制定了一套字符编码,叫ASCII码,一共规定了128个字符。只需要7位(bit)就可以一一映射。但是一般采用一个字节(8 bit)进行存储编码,最高位统一为0。

比如我们必会的ping对应的编码就是:\u0070\u0069\u006e\u0067 (16进制表示,2进制太长了)

具体的映射规则可以到ASCII码对照表查看。

对于母语是英语的国家ASCII码是完全够用了。但是随着计算机的普及,越来越多的国家加入互联网。那么对于这些国家ASCII码肯定是不够用的。

比如中国,汉字就多达10万左右。ASCII码只使用8个字节进行表示,最多可以表示256个字符,还不够塞牙缝,肯定是不够用的。于是对于汉字就提出了GB2312 编码方式,即使用两个字节表示一个汉字,那么就可以表示 256 * 256 = 65536 个字符。

汉字提出了GB2312编码,那韩文呢,日文呢,泰文呢。世界上那么多国家那么多语言,如果每一个人都提出自己的编码方式,那计算机就得装载各种编码程序,这将极其复杂,也增加了出错的几率。

想想你好不容易下载的日本电影被编解码程序解析为了新闻联播时。

图片

二、大一统-Unicode

正所谓天下大势分久必合,合久必分,咳咳咳。

正式因为世界上存在太多的编码方式,一段0101010的二进制数字可以被解释为不同的语义。

因此对沟通上带来了极大的成本。

像上学的时候,看小说还是使用的txt,就总是容易出现乱码。

一般故事发展到这个时候,就会出现一本秘籍统一江湖,这就是Unicode编码。

图片

unicode的思路很简单,你们不是国家多,语言多吗?行,我给世界上的每个字符都分配一个编号。

具体的字符映射表可以到 unicode映射表进行查询。

目前的序号的范围从0×000000到0x10FFFF,一共表示了110多万个字符。

不过这样的编码方式也带来了问题,对于单个英文字符,也需要三个字节进行编码,造成了极大的浪费。要知道互联网每天产生的数据可以绕地球好几圈,如果这么浪费的进行存储,只能说一句:土豪带带我。

故事发展到这个时候,就会出现一本秘籍统一江湖。

图片

三、UTF-8

由于互联网的高度普及,提出一种可行的高效的的编码方式迫在眉睫。

于是基于Unicode的编码方式UTF-8横空出世。

UTF-8并不是一种新的编码协议,他是Unicode编码的一种实现,类似的还有UTF-16,UTF-32。这是由于Unicode只是提出了一套映射规范,但是关于怎么存储,怎么提高效率都没有提及。所以才出现了UTF-8

我们知道按照原始的 Unicode 规定的存储,对于低位字符会操作极大的空间浪费。UTF-8提出了变长编码的思路。诶,你不是低位字符浪费吗,那我就用少一点的字节进行表示。

UTF-8 使用1~4个字符表示一个符号,根据符号的Unicode码而变化字节长度。

具体规则如下:

1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此UTF-8 编码是兼容ASCII的。

2)对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的二进制位,全部为这个符号的 Unicode 码。

\

那么问题来,计算机怎么知道什么是单字节,什么是n字节。对他不就是010101吗?

别慌,UTF-8 对于专门定义了一张转化表进行了解释:

Unicode符号范围(16进制表示)Unicode符号范围(10进制表示)utf-8编码(二进制表示)
0000 0000 ---- 0000 007F0---1270xxxxxxx
0000 0080 ---- 0000 07FF128---2047110xxxxx 10xxxxxx
0000 0800 ---- 0000 FFFF2048---655351110xxxx 10xxxxxx 10xxxxxx
0001 000 ---- 0010 FFFF65536---13107111110xxx 10xxxxxx 10xxxxxx 10xxxxxx

刚看到这张表,大家肯定一头雾水,别慌。我们举例实战推演帮助大家理解。

比如现在需要编码汉字"中",首先需要找到"中"Unicode编码。"中"Unicode编码是:4E2D(16进制)--> 20013(10进制)。可以发现落在了上表的

2048---65535 区间。也就是"中"对应的utf-8 编码为

1110xxxx 10xxxxxx 10xxxxxx。

第一步完成了,找到了模板,剩下就是填充xxx了。

把4E2D 转为 二进制 得到 100111000101101。

按照从后往前填充,不足补0的规制,把得到的二进制填充到utf-8模板中可得:11100100 10111000 10101101,这就是"中"经过utf-8编码后的结果。

正向编码理解了,那把0和1字符串变为字符就简单了。

首先按顺序读取,如果第一位是0,那证明是单字符,直接取一个字节去掉头部得到一个数字,去Unicode表中找对应的字符。

如果第一位是1,那就继续往后读,指定读到0为止。这个时候读到几个1就证明需要几个字节进行编码。例如"中"的二进制可以知道需要三个字节,所以这里取三个字节,分别去掉第一个字节的三个1,后面两个字节的10,得到一个数字,直接去Unicode表里找到对应字符。

四、大功已成

相信学到这里,你已经完全掌握了编码的真谛,下次再遇到日本电影变成新闻联播时也可以从容应对了。

图片

五、扩展

通过下面的命令可以查询当前mysql支持的字符集。

SHOW CHARACTER SET;

这是我安装的mysql(8.0.29)支持的字符集。

图片

虽然mysql支持了那么多字符集,但是经过上面的分析,可以知道utf8mb4基本可以覆盖99%的场景。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多