2019年,德国

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

我仔细看看报错信息,应该是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和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、ē、é、ě、è、ê、ë的等价问题就是一例。这类问题,不亲自经历是很难想象的。

回想起来,十多年前我开始接触这方面业务,还真的积累了一些经验,是坐在办公室里写代码想不到,也非常有意思的问题。如果大家有兴趣,下回我们接着聊。

【Mysql-字符集】软件开发没看上去那么简单,从utf8mb4_0900_ai_ci说起相关推荐

  1. 直播软件开发,看智播如何为各行业提供直播场景解决方案?

    2019独角兽企业重金招聘Python工程师标准>>> 一.智播的简介 智播,专注于直播应用场景解决方案,为各行业视频直播软件开发提供从底层到应用层的一体化直播应用,针对不同行业和场 ...

  2. 作为软件开发人员,3种简单的方法来推销自己

    目录 翻译内容 1. Start a blog 开始写博客 2. Build a network 建立一个网络 3. Build a personal brand (the best way to m ...

  3. 关于HTC Touch Diamond 2的ROM和WM软件开发高手看过来…

    在这一个多月的时间里,学到不少东西,去xda-developers.com论坛潜水半个月,发现那里牛人很多,似乎个个都会把机器玩转,个性非凡,都能开发出几个自己的ROM供大家刷机,也有很多很炫的Win ...

  4. 【软件开发】Java语言的简单介绍

    Java语言的简单介绍 一.Java语言的介绍 二.Java的版本 三.JDK的介绍 四.Java API文档 五.Java语言的特点 1. 面向对象 2. 解释性 3. 多线程 4. 可移植性 / ...

  5. 你了解吗?一对一直播软件开发它们才能实现音视频传播

    语音聊天软件是当代人民日常生活中最常使用的软件之一,无论是一对一即时聊天的QQ.微信.还是陌陌等支持陌生人语音社交连线的软件,又或者是直播.聊天室等多人聊天软件,你总归会使过其中一种或几种. 以一对一 ...

  6. 华为软件开发云 变革企业软件研发模式

    如今,一个APP也许几天就需要更新,一个应用也许一.两周就需要上线,一套新的业务系统可能从理念提出到系统上线也仅仅只会给开发团队不足一个月的时间,这就是移动和互联网+时代对软件研发交付的真实需求.面对 ...

  7. 每个程序员必看:如何在40岁后继续做软件开发?

    导读: 这是一个 42 岁的开发者所写经验分享文章.并且列出一些他 18 年多身为软件开发者的经验谈.许多部分看完后都会希望自己当时就能够了解,所以很推荐不论是新手或是老手都要好好阅读这一篇文章. 故 ...

  8. halcon从入门到精通pdf_机器视觉软件开发新人入门必看 --机器视觉软件开发学习路径...

    机器视觉是机械.运动.控制.光学.软件.算法于一体的交叉学科,对于学工科的人来说,机械.运动.控制都有一定的了解,对于软件.算法.光学不是很了解.一台设备,有一个到二个机械设计师或者结构工程师,那么这 ...

  9. 软件开发的那些坑,你跳了没?这篇文章价值千万(一)

    接触过上万名创业者,开发上线过超过70款App,没有比我更适合回答这个问题的了.. 本文对想做好一款App项目的人来说这是一篇价值百万的回答!因为这是我们花了几百万试错成本试出来的经验! 在我们从事a ...

最新文章

  1. docker 报错 non-overlapping IPv4 address pool among the defaults to assign to the network 解决方法
  2. 搭建SSH框架之一(资料准备)
  3. 通过日期获取一周的那一天
  4. 音视频技术开发周刊:FFmpeg内置的一个无中生有的音视频输入数据 | 214
  5. java和tornado_TornadoJ
  6. linux给目录加裸设备磁盘,Linux添加裸设备空间(Redhat)
  7. 数据结构和算法之排序一:归并排序
  8. Ubuntu 18.04 与 20.04 LTS 性能测试
  9. C++ 网络多线程编程
  10. Bootstrap Table插件 页面跳转后再回来保存搜索的值
  11. mysql 中文 phpmyadmin_mysql中文乱码问题,phpmyadmin操作解决方法
  12. SpringSecurity视频教程
  13. 三大抽样分布:卡方分布,t分布和F分布的简单理解
  14. SwiftUI HealthKit 基础教程
  15. BZOJ 1069 最大土地面积(旋转卡壳求最大四边形)
  16. 高数笔记(四):导数概念,函数的求导法则,高阶导数,隐函数求导、参数方程求导
  17. Swift学习笔记 (四十二) 不透明类型
  18. 深度Linux桌面图标,图标推荐 Deepin plus 所有图标集
  19. 南安普顿大学计算机排名2019,南安普顿大学2019THE世界大学排名最新排名第118
  20. POX控制器的分析(二)

热门文章

  1. Isolated and Exhausted:Attacking Operating Systems via Site Isolation in the Browser论文阅读笔记
  2. 深度优先搜索——采药的最短路径
  3. web前端开发企业命名规范
  4. 使用Wwise在3D RPG游戏中的声音设计思路
  5. Linux常用软件安装
  6. YTU.2244: 背包问题(栈和队列)
  7. Java8 流式递归树形菜单
  8. 使用计算机教学的不足,信息技术在教学中的优势和不足之处
  9. DNF虚拟机防检测过TP NP 无机器码 不蓝屏 极限优化快速
  10. 推荐一个开源流媒体工具:ZLMediaKit