五分钟教你彻底解决MySQL中文乱码
文章首发于:****
突发状况
周末,当我在峡谷马上拿下五杀时,突然手机弹出消息。
"线上系统的评论功能不能用了"。
还没等我反应过来,一个视频会议电话已经打进来了。作为社畜只能默默放下手机,打开电脑接入会议。
刚进入会议,产品疯狂吐槽。
"刚给用户开通灰度,用户怎么就用不了";
"重试好几次都不行";
"每次发表情都不行";
听到这,有经验的研发兄弟应该大概猜出来了,跟数据库的字符集有关系。
于是迅速登录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 对于专门定义了一张转化表进行了解释:
0000 0000 ---- 0000 007F | 0---127 | 0xxxxxxx |
0000 0080 ---- 0000 07FF | 128---2047 | 110xxxxx 10xxxxxx |
0000 0800 ---- 0000 FFFF | 2048---65535 | 1110xxxx 10xxxxxx 10xxxxxx |
0001 000 ---- 0010 FFFF | 65536---131071 | 11110xxx 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%的场景。
#晒一晒我的offer##软件开发薪资爆料##我的实习求职记录##23届找工作求助阵地#**【****】******************************
MySQL面试知识点分享