Categories: Yurii谈开发

MySQL升级8.0的新故障,utf8mb4_0900_ai_ci是啥?

本文由Yurii原创,转载请注明来源: Life Sailor

本文链接 MySQL升级8.0的新故障,utf8mb4_0900_ai_ci是啥?


前段时间,遇到朋友的求助,说以前运行的好好的系统现在出问题了,而且看不懂报错到底是什么意思。

我仔细看看报错信息,应该是MySQL数据库报出来的,大意是说:collation不兼容,一个是 utf8mb4_0900_ai_ci,另一个是utf8mb4_general_ci

utf8mb4_general_ci这玩意儿我见过,是针对utf8mb4编码的collation,但是utf8mb4_0900_ai_ci是啥,我也没见过。

于是我问他,这玩意儿从哪里出来的?

他说:“我也不知道,我完全没见过啊。再说,我数据库编码已经是utf8mb4了,怎么还会有这么多名堂?”

看他着急又不知所措的样子,我便花了点时间来研究,还真学到点新知识。而且我也发现,有许多程序员天真的以为“用了UTF8就等于做了国际化了,不用再担心编码问题”。看来,这个话题还真值得多讲讲。

首先从utf8mb4_0900_ai_ci这个诡异的名字说起。

Unicode编码的诞生,是为了解决之前各国的计算机文字编码自成一体的问题。不同国家采用不同的编码,自己用还算正常,但是跨文化交流必然会出问题,更无法解决“在同一篇文档里又要显示中文又要显示韩文还要显示日文”之类的问题。

有了Unicode,地球上所有的文字都有独一无二的编码(Code Point,也就是为它分配的码值,或者说“逻辑代号”),前述问题就解决了。

但是Unicode(有个相关的名字是UCS,Universal Coded Character Set,二者基本等价)只确定了码值,或者说,只分配了逻辑代号。至于这些逻辑代号在实际使用中如何存储,如何传输,那是另一个问题。而UTF-8,就是解决存储和传输等问题的“实际方案”。

实际上,UTF的全名是Unicode Transformation Format,也就是“Unicode变换格式”。这里的“变换”,基本可以类比为:要告诉别人明天早上九点来开会,到底是发邮件呢,还是打电话呢,还是写纸条呢,还是直接去敲门打招呼呢?。

所以,Unicode的变换格式不只UTF-8一种,还有UTF-16、UTF-32等等。UTF-8使用比较普遍,因为它是变长编码,如果只传输ASCII字符,则每个字符只需要一个字节。因此,如果数据中包含大量的ASCII字符,那么UTF-8可以节省很多存储空间。

老一点的程序员大概都知道UTF-8,在MySQL中写作utf8,没有横线。如果要用MySQL存储多种语言的字符,那么把字符集(character set)设定为utf8是合适的选择。注意,MySQL中必须指定utf8,而不是Unicode。因为Unicode只是逻辑规范,utf8才是具体存储和传输的格式。

那么,utf8mb4_0900_ai_ci什么意思呢?

我们分部分来看这个名字,先从开头看起。

utf8mb4,这个名字许多人大概熟悉。如今🖨️🛒♥️☎️🌹之类的emoji表情已经大量使用,但MySQL之前的的字符集(character set)是utf8(更准确的名字是utf8mb3,一个字符最多使用3个字节来存储),只能存储编码值从0x000到0xFFFF之间的字符。

然而,emoji表情字符的码值超过了0xFFFF,按照UTF-8规范,存储时需要用4个字节。正因为如此,MySQL才提供了utf8mb4的字符集。如果把数据库表的字符集设定为utf8mb4,就可以正常存储包含表情字符的文本了。

中间的0900,它对应的是Unicode 9.0的规范。要知道,Unicode规范是在不断更新的,每次更新既包括扩充,也包括修正。比如6.0版新加入了222个中日韩统一表义字符(CJK Unified Ideographs),7.0版加入了俄国货币卢布的符号等等。

如果支持新的Unicode规范,就可以直接享受好处,像对待普通字符那样对待这些新字符,当然是好事。

以前的MySQL虽然也会跟随Unicode的更新,但速度太慢了。MySQL 5.7的第一个发行版MySQL 5.7.1是2013年4月23日面世的,它包含的最新的Unicode规范是Unicode 5.2,发布于2009年10月。即便是2020年1月13日发布的MySQL 5.7.29,仍然是这样。

然而Unicode规范早已升级了很多版,即便是9.0版本,也发布于2016年6月,过去了好多年了。到目前为止,最新的版本已经到了12.1,发布于2019年5月。所以从5.2更新到9.0,看起来是一大进步,其实也只是补课而已。

Unicode在不断更新,来源:维基百科

最后两部分_ai_ciai表示accent insensitivity,也就是“不区分音调”,而ci表示case insensitivity,也就是“不区分大小写”。

所以,utf8mb4_0900_ai_ci到底是个什么东西呢?其实,它是个collation。

说起“字符集”,许多人想当然认为,给每个字符分配了一个编码,并且能存储、能传输,这就够了。其实这当然不够,我们不但需要给每个字符分配编码,让它们能存储、能传输,还需要定义一套关系来组织它们,找到它们之间的联系。这套关系的定义,就是collation。

collation定义了哪个字符和哪个字符是“等价”的。所以如果指定“不区分大小写”,那么a和A,e和E就是等价的,这样查找时就会方便很多。但这还不够,世界上的文字很多,所以才会有“不区分音调”的要求,这时候e、ē、é、ě、è就是等价的,那么假设我们要进行拼音查找,只要按e去找就可以全部列出来,很方便。甚至,它们也和ê、ë也是等价的,这样就更方便了。

collation也定义了字符的排序规则,如果按照“字符顺序(而不是简单的‘字母顺序’)”来排序,哪个字符应当排在哪个字符前面。所以,尽管“啊”、“副”、“德”三个字的拼音开头分别为A、F、D,但直接选定collation为utf8mb4,它们并不会按照“啊”、“德”、“副”的顺序排序,而是会排成“副”、“啊”、“德”。如果你希望把中文字符按照拼音(英文字母)来排序,指定使用gb18030_chinese_ci作为collation就可以了。

当然,要补充的是,collation依赖于字符集(character set),所以把gb18030_chinese_ci作为collation,就要求字符集是gb18030,而不能是utf8mb4

这也很好理解,字符集定义了可以使用的字符,对应的collation定义了字符之间的关系。如果collation不依赖于字符集,那么很可能出现“有些字符没有关系定义,不知如何判断等价和顺序”的问题。

到这里,那位朋友的疑惑就解开了。MySQL 8.0之后,默认collation不再像之前版本一样是utf8mb4_general_ci,而是统一更新成了utf8mb4_0900_ai_ci

不幸的是,这位朋友的系统是一路升级上来的,所以之前建的各种数据表,它们的collation仍然是utf8mb4_general_ci(这个名字够自负,这个例子也提醒我们,不要低估技术的发展,不要把话说得太满),而新建的表是utf8mb4_0900_ai_ci。如果,恰好遇到包含字符串相等或者大小比较的联表查询语句,而关联的表又使用了不同的collation,MySQL就无法决策到底应当使用哪个,就会报错。

既然如此,解决办法也很简单,用alter table table_name collate utf8mb4_0900_ai_ci显式统一所有表的collation,问题就解决了。

我们可以多想想,把character set和collation分开,到底有什么好处?其实好处很多。如果把字符看作个人,character set就相当于验明正身,给每个字符发张身份证,而collation相当于告诉大家,排队的时候谁在前谁在后。collation有多套,就相当于可以灵活按身高、体重、年龄、出身地等等因素来排序,却完全不会受到身份证号的干扰。

实际上,MySQL 8.0中的collation也确实有多套。从utf8mb4_0900_ai_ci这个名字就可以看出来,起码还有utf8mb4_0900_as_ci,也就是“区分音调”的collation,此时e、ē、é、ě、è就不再是等价的。另外还有utf8mb4_0900_as_cs,此时e和E也不等价了。只要在查询时指定不同的collation,就可以享受这些好处,十分方便。

这个问题本来不麻烦,为什么会难住人呢?原因不复杂,你去看关于MySQL和Unicode的中文资料,绝大部分都是告诉你,utf8或者utf8mb4就可以解决问题了。因此,不少程序员完全意识不到还有collation这种东西。

所以,这些程序员理解的“字符集”就只有一堆孤零零的字符,根本没想到还需要定义字符之间的等价和排序关系。而这恰恰是最可惜的,因为他们完全错过了“举一反三”的启发,许多类似问题也就缺乏解决思路。要知道,哪怕你做的不是国际化的业务,也可以从collation中受益的。

我们都知道,电商系统的订单处理是一个流程,其中涉及许多状态,比如“已下单,未支付”、“已支付”、“已确认”、“已拣货”、“已发货”等等。

有程序员看到这个需求,想当然就按照先后顺序,用1、2、3、4、5来表示对应状态,确实简单不会出错,也方便先后对比,比如要查找所有“已确认”之前的订单,就查查“已确认”的状态码是4,那么找状态码<4的订单就可以。

然后,有一天,忽然要在两个状态之间加入某个中间状态,比如“已确认”之后需要新的风险评估,通过了才可以去拣货,怎么办?总不可能在3和4之间加一个3.5吧?因为这个数据字段本来就是整数型啊。

所以“有经验”一点的程序员会改改,一开始就不按照1、2、3、4、5这样来分配状态码,而是按100、200、300、400、500,留足空隙,这样就避免了3.5的尴尬,直接给“风控系统已通过”分配350就可以了。

但这仍然不够。如果业务忽然要求既有顺序要变,比如之前“已确认”在前,“风控系统已通过”在后,现在要求“风控系统已通过”在前,“已确认”在后,该怎么办?350总不可能大于400呀。

如果你了解了collation就会发现,这是同样的问题。数据的标识和数据的有序性应当隔离开来。标识是一套规范,有序性是另一套规范,两者可以随意组合。你看,Unicode字符的排序可以按照字符的编码值来,也可以按照其它规范来——加载不同collation就是了嘛。

所以,“已下单,未支付”的代码就可以是OUPD,“已支付“的代码就可以是PDED,“已确认”的代码就可以是CFMD…… 它们只用来做唯一标识,没有任何其它意义。然后在外面定义一套顺序规则,比如OUPD < PDED < CFMD,然后提供一个查询接口,做任何比较的时候都查询这个接口就好——实际上许多语言可以自定义compare函数来做排序,道理就在这里。万一将来要改业务流程,比如加入新状态,或者更改状态的先后顺序,也只需要做一点点更改,规则查询接口保持不变,其它地方更是保持原封不动。

最后我想补充的是,即便你有非常多的软件开发经验,但如果要做“国际化”的业务,仍然会面对许多想不到的问题——e、ē、é、ě、è、ê、ë的等价问题就是一例。这类问题,不亲自经历是很难想象的。

Yurii

View Comments

  • 感觉用状态码做例子不是很好。在我看来,状态码设计是一个破坏MECE的问题,导致的不协调。

  • 讲的很生动有趣,解决了我的疑惑,顺便还提到了状态码的排序,感谢作者٩(ˊᗜˋ*)و

Recent Posts

德国生活点滴:歧视比你想象的要复杂(续)

在上一篇文章里,我列举了一些种族歧视现象的亲身经历,引发了许多读者的讨论。但是让我略感遗憾的是,许多人大概没有注意文章的标题,没有觉察到关键是“比想象的要复杂”,所以直接给出了一个简单的论断。 我的本意绝不是强化已有的简单粗疏的刻板印象,而是希望让大家知道,种族歧视这回事,有许多的侧面和细节。了解这些侧面和细节,有助于我们形成更立体的认知。 于是就有了下面这些内容,希望能引发大家的思考。 一 种族歧视是一种最简单粗暴的歧视。 许多人都知道,“歧视”的英文是discriminate,准确的意思是“区别对待”。既然要区别对待,就自然首先必须有办法区分。目力所及,似乎没有人愿意“区别对待”与自己完全同样的人,而总是要先找出一点区别来,再实行区别对待。 所以,种族、口音、家庭出身、经济能力等等各种因素,都可以成为“区别”的指标,由此催生出区别对待。在这些因素当中,种族大概是最容易识别的特征——判断口音需要等对方开口,家庭出身、经济能力等等因素就更是要全面接触才可能了解。唯有种族,具体来说,绝大多数时候是相貌和肤色,是可以远远一眼就望见的。 也恰恰是因为这个原因,种族歧视特别容易引起反感。 这些年来,我得到的一条重要的生活经验是,如果你希望指出对方的问题,但又不纯粹是为了激怒对方,那么最好不要归因为一些木已成舟,对方无法改变的因素,否则对方多半会恼羞成怒。 举个例子,你觉得某人的口语表达还可以更好一点,完全可以直接给出具体的建议。但是如果从“经济不发达地区来的人就是口语差”,或者“个子矮的人就是没自信心来表达”,那几乎一定会制造矛盾。因为“口语表达”是可以改进的,加以锻炼将来肯定更好,而“不发达地区来的人”和“个子矮的人”就像烙印一样,是无法摆脱的。这种话说出来,对方哪怕有意愿改进,也会觉得无奈甚至恼怒。 种族歧视也是这样,“种族”同样是一种烙印,是无法摆脱的。所以当对某些人的判断与种族挂钩的时候,他或她必然感到无奈甚至愤怒。况且老话说“人上一百,形形色色;人上一万,千奇百怪”。哪怕是同一个种族的人,也可能在肤色、相貌之外完全找不到相同点。先入为主地用种族去对其他人下判断,无论是从情感反应上,还是从逻辑上,都是站不住脚的。 (more…)

2 weeks ago

德国生活点滴:歧视比你想象的要复杂

去年初的时候,小朋友冰球俱乐部来了个新教练Robo。Robo来自加拿大,总是一副很健谈很乐观的样子,而且很喜欢放音乐,把整个训练场搞得热情四射。最关键的是,小朋友们好像都很喜欢他,不但许多动作耐心示范,对每个人的指导也相当到位。而且,他的英语很好,人又很喜欢开玩笑,所以我们交谈很多,他总是跟我说:“你家的小朋友超级酷的,不要给他太大压力,只要他自己运动起来足够自在,能够持续练下去,就是最好的。” 没想到的是,到去年9月份,Robo忽然神秘失踪了,没有任何征兆,也没有任何说明,就此人间蒸发了一般。问其他的教练,也是语焉不详。小朋友训练完,偶尔会失落地跟我说“好久没看到Robo了,不知道他哪里去了。” 3月份的时候,一个偶然的机会,我又见到了Robo,虽然当时时间很紧张,只是打了个照面,但我要他留下了联系方式。 当天晚上我问他:哥们,你怎么忽然就不见了,大家都很想你啊。 过会儿我收到他的回复:我也很想念小孩子们,你儿子很酷……我现在没在那个俱乐部了,因为其他几个教练总是或明或暗地针对我,仅仅因为我的肤色,这是我受不了的。 (more…)

2 weeks ago

在德国, 全远程+共享空间办公,是什么体验?

注:原文发布于2023年1月16日 到1月份为止,我已经体验了几个月的全远程+共享空间办公了。有不少朋友听说之后很有兴趣,问我到底是什么感觉,所以我简单介绍下个人的体验。 背景 2019年末、2020年初开始在全球流行的Covid-19对远程办公来说,绝对是黑天鹅一般的存在。因为疫情导致的社交隔离措施,极大影响了各大公司的正常运转。 所幸,IT类公司受到的影响比较小,只要求员工“面对屏幕编程”,不必亲临现场。所以,许多IT公司也谨小慎微地开展了远程办公的试验。 从我所知道的结果来看,不少美国公司并不特别喜欢远程办公,比如Google,一旦社交隔离措施有所放松,就忙不迭要求员工回到办公室,盖因为公司认为远程办公严重影响合作效率。 与此相反,不少德国公司反倒是逐渐适应了远程办公的节奏,纷纷降低对员工“到办公室上班”的要求,许多公司甚至可以支持百分百的远程办公。 这里要提到的是,德国公司说的“远程办公”往往是货真价实的“远程”,而不是一些人理解的“家和办公室在同一个城市,只是不用去办公室”而已。 因为德国IT行业缺人严重,而且许多德国公司并没有那么“互联网”,而是依托实业开展业务,所以据我所知,目前不少公司非但没有裁员,反而都在大力招人。 (more…)

1 month ago

成年人找工作,不值得那么多愁善感

注:本文发布于2023年2月6日 最近硅谷几大公司都在裁员,看了些报道,被裁的员工真是不好过。损失经济来源不说,有些人还面临身份问题,这可真是屋漏偏逢连夜雨。 我也留意到,不少被裁的人会不停追问自己:为什么我会遇到这样的事情?为什么这样的不幸会降临到我头上?…… 实话说,我挺能理解这种态度。这挫折如此巨大,似乎又来得全无预兆,不由得让人对命运、对人生、对世界产生深重的怀疑。尤其是对已经走入社会,取得一定成就(如果非要抠字眼,那就用“进展”吧)的人来说,更是如此。 但是我更想说,如果被裁员了,当务之急是赶紧找到下一份工作,哪怕只是机械地行动。要知道,成年人找工作,容不下那么多愁善感。 我之所以这么说,是有切身经历为基础的。之前我讲过找德国工作的经历。最开始是信心十足的,因为虽然毕业多年,手艺没丢,基础还在,随时打开leetcode,中等难度题目基本都不在话下,不但能解对,解法也基本接近最优。既然网上都说“刷题就能找到工作”,估计自己应该没大问题。 没想到真的找起工作来,仍然充满了意想不到的挫折。如果不相信,我且举几个例子吧。 (more…)

1 month ago

我读《园丁与木匠》

虽然早就听说《园丁与木匠》是关于育儿的好书,但一直没开始读。最近终于翻开这本书,才发现属于“拿起就很难放下”的类型,加班加点读完,收获不少。 关于这本书的价值,已经有许多书评讨论过了,所以我想略过微言大义、长篇大论的叙述,谈谈我印象最深,也是最打动我的三点细节。 第一,儿童的学习方式 小孩子觉得拧螺丝很好玩,想自己动手拧一颗螺丝。于是,他打开了工具箱,对着琳琅满目的工具,他不知所措。一会儿摸摸钳子,一会儿试试扳手……这时候,旁边的父母应当怎么办? 在大多数情况下,父母大概会直接告诉孩子,“亲爱的,你应该用螺丝刀,来,我告诉你”。耐心一点的父母,大概会潜心观察一段孩子的举动,再设法“引导”他到正确的工具上来。在父母眼里,孩子当然不可能一开始就找对正确答案,所以做各种尝试也是情有可原。但是另一方面,也不应该“在错误的路径上摸索太久,浪费时间”,应当“迅速识别出正确的答案”。 无论父母有多少耐心,在他们眼里,孩子找到拧螺丝的工具的过程,都是个“不断接近正确答案”的过程。这个过程越短,孩子就越“聪明”,或者说“学习效率”就越高。 (more…)

1 month ago

再见,或许就是再也不见

陈皓(Haoel,网名“左耳朵耗子”)上周六因为突发心梗去世了,享年47岁。 我跟他虽然聊过好些次,但只是微信好友,从未见过面。回看微信记录,当年稀松平常的一声“再见”,已经成了“再也不见”。 许多人在缅怀他,许多文章提到他的时候,会用到“骨灰级程序员”、“技术大牛”这样的称呼。但如果仅仅用这两个词描述他,断然难以解释,为什么他的突然去世,会引发互联网上怀念的狂潮。 所以,我更愿意按照自己的经验,把他描绘为“有坦诚追求,兼具趣味、操守、胸怀的技术人”。恰恰是因为这样的人在这个年代太稀少,而这些品质又让众多人赏识和受益,大家才会如此地怀念他。 这个年代,做技术(仅指狭义的IT)的人很多,愿意分享的人也不在少数,其中不少还可以算世俗意义上的“成功者”。 但是,若仔细去看他们分享的内容,总感觉不够真诚。总感觉作者希望往高深了靠,目的也没有那么纯粹。你若提一些小白问题,迎来的往往是“你怎么连这都不知道?”的反问,或者“要谈这个问题,你先去看几本书再说吧”。话是这么说没错,但无数的初学者也往往因此打了退堂鼓。 但是陈皓的分享不同。我已经不止一次地看到有人提起,他分享——更准确说,是“创作”——的内容质量很高,而且总能做到“深入浅出”。哪怕是小白读者,看完也确实能有收获,如果还有兴趣,更可以跟着文末的链接,顺藤摸瓜探究更广阔的世界。 这让我想起我佩服的一位记者说的:记者写文章的最高境界,就是不表达自己的观点,因为记者的观点应当来自于他的素材。只要把这些素材摆出来,读者读完报道,观点就自然形成了。要做到这一点,需要对素材有足够的信心和把握,外加真诚和坦荡。 能做到这一点的记者,着实不多。陈皓虽然不是记者,他写的技术文章却能让读者得到类似的结论——要知道,技术讨论往往是非常容易擦枪走火的——可见他运用素材和逻辑的功力,以及更重要的,他的真诚和坦荡。 (more…)

1 month ago