阿里、蚂蚁、晟腾、中科加禾精彩分享 AI 基础设施洞见,现购票可享受 9 折优惠 |AICon 了解详情
写点什么

MySQL Varchar 类型尾部空格详解

aotian

  • 2022-04-06
  • 本文字数:4678 字

    阅读完需:约 15 分钟

MySQL Varchar 类型尾部空格详解

背景


近期发现系统中某个输入框里如果输入 xxx+空格 的时候会出现异常情况,经过排查发现在调用后端接口时会有两步操作,一是从数据库中查询到的数组中将与 xxx+空格 一致的元素剔除,二是根据 xxx+空格 从数据库中查询对应的明细。


出现异常的原因是在剔除时未能剔除掉对应的元素,也就意味着 xxx+空格 对应的内容在数据库中不存在;但是在查询明细时还是查询到了,顿时感觉很费解,也就衍生出了这篇文章后续的内容。


原因


  1. 开发人员在处理前端传过来的字符串时没有执行 trim(),所以导致与数组中元素匹配的时候没有匹配到,也就没能剔除对应的元素,"a".equals("a ") 的结果肯定是 false 嘛。

  2. MySQL 在查询时会忽略掉字符串最后的空格,所以导致 xxx+空格 作为查询条件时和 xxx 为同一效果。


详解


对于第一条原因只能说是开发时疏漏,没什么可说的,我们着重了解下第二条,为什么 MySQL 会忽略掉查询条件最后的空格。本文基于 MySQL 8.0.28,文章中有些内容是 MySQL 8.0 新增的,但主体也适用于 5.x 版本。


在探究之前我们需要准备下使用的数据库,毕竟实践出来的结果才是真实的,首先我们准备一个测试使用的数据库和表,结构如下,字符集和排序规则先选择比较常用的 utf8mb4 和 utf8mb4_unicode_ci,之后在表里插入两条数据:


mysql> desc test;+--------------+-------------+------+-----+---------+-------+| Field        | Type        | Null | Key | Default | Extra |+--------------+-------------+------+-----+---------+-------+| id           | int         | NO   | PRI | NULL    |       || name_char    | char(20)    | YES  |     | NULL    |       || name_varchar | varchar(20) | YES  |     | NULL    |       |+--------------+-------------+------+-----+---------+-------+3 rows in set (0.01 sec)
复制代码


INSERT INTO `test` VALUES (1, 'char1', 'varchar1');INSERT INTO `test` VALUES (2, 'char2     ', 'varchar2     ');
复制代码

char 和 varchar 的区别


首先看一下官方对于 char 类型和 varchar 类型的介绍,以下内容摘自【11.3.2 The CHAR and VARCHAR Types】


The length of a CHAR column is fixed to the length that you declare when you create the table. The length can be any value from 0 to 255. When CHAR values are stored, they are right-padded with spaces to the specified length. When CHAR values are retrieved, trailing spaces are removed unless the PAD_CHAR_TO_FULL_LENGTH SQL mode is enabled.

Values in VARCHAR columns are variable-length strings. The length can be specified as a value from 0 to 65,535. The effective maximum length of a VARCHAR is subject to the maximum row size (65,535 bytes, which is shared among all columns) and the character set used.


通过以上我们可以得知以下几部分内容:


  • char 类型长度为 0-255,varchar 类型长度为 0-65535,char 和 varchar 类型的长度其实还会受到内容长度的影响,这里我们不深究。

  • char 类型为定长字段,存储时会向右填充空格至声明的长度;varchar 类型为变长字段,存储时声明的只是可存储的最长内容,实际长度与内容有关。

  • 在 sql mode 中未开启 PAD_CHAR_TO_FULL_LENGTH 时,char 类型在查询时会在忽略尾部空格(关于 sql mode 的资料请移步【5.1.11 Server SQL Modes】,这里我们不深究)


下面的查询结果中第一行是都没有空格的结果,第二行是都带有 5 个空格的结果,可以看到 char 类型无论带不带空格都只会返回基本的字符。


mysql> select concat("(",name_char,")") name_char, concat("(",name_varchar,")") name_varchar from test;+-----------+-----------------+| name_char | name_varchar    |+-----------+-----------------+| (char1)   | (varchar1)      || (char2)   | (varchar2     ) |+-----------+-----------------+2 rows in set (0.01 sec)
复制代码


第一行好理解,你存进去的时候没带空格,数据库自己填充上了空格,总不能查出来的结果还变了吧;第二行则是入库的时候字符串最后的字符和数据库填充的字符是同一种,查询的时候数据库怎么分得清是你自己填的还是它填的呢,直接一刀切。而 varchar 类型因为不会被填充,所以查询结果中完成的保留下了尾部空格。

varchar 对于尾部空格的处理


上节了解过 char 类型查询时会忽略尾部空格,但是在实际使用中发现 varchar 也有类似的规则,在查看文档时发现有以下一段内容,摘自【11.3.2 The CHAR and VARCHAR Types】


Values in CHAR, VARCHAR, and TEXT columns are sorted and compared according to the character set collation assigned to the column.

MySQL collations have a pad attribute of PAD SPACE, other than Unicode collations based on UCA 9.0.0 and higher, which have a pad attribute of NO PAD.


根据这一段描述,我们可以得知 char、varchar 和 text 内容的排序和比较过程受排序规则影响,在 UCA 9.0.0 之前 pad 属性默认为 PAD SPACE,而之后的默认属性为 NO PAD。


在官方文档中可以找到以下说明,摘自【Trailing Space Handling in Comparisons】


For nonbinary strings (CHAR, VARCHAR, and TEXT values), the string collation pad attribute determines treatment in comparisons of trailing spaces at the end of strings:

  • For PAD SPACE collations, trailing spaces are insignificant in comparisons; strings are compared without regard to trailing spaces.

  • NO PAD collations treat trailing spaces as significant in comparisons, like any other character.


这一段主要描述 char、varchar 和 text 类型在比较时,如果排序规则的 pad 属性为 PAD SPACE 则会忽略尾部空格,NO PAD 属性则不会,而这正解释了最初的问题。我们通过修改列的排序规则验证以下,首先看一下当前使用 PAD SPACE 时的查询结果。


mysql> show full columns from test;+--------------+-------------+--------------------+------+-----+---------+-------+---------------------------------+---------+| Field        | Type        | Collation          | Null | Key | Default | Extra | Privileges                      | Comment |+--------------+-------------+--------------------+------+-----+---------+-------+---------------------------------+---------+| id           | int         | NULL               | NO   | PRI | NULL    |       | select,insert,update,references |         || name_char    | char(20)    | utf8mb4_unicode_ci | YES  |     | NULL    |       | select,insert,update,references |         || name_varchar | varchar(20) | utf8mb4_unicode_ci | YES  |     | NULL    |       | select,insert,update,references |         |+--------------+-------------+--------------------+------+-----+---------+-------+---------------------------------+---------+3 rows in set (0.01 sec)
mysql> select * from test where name_varchar = 'varchar2';+----+-----------+---------------+| id | name_char | name_varchar |+----+-----------+---------------+| 2 | char2 | varchar2 |+----+-----------+---------------+1 row in set (0.01 sec)
复制代码


可以看到在 PAD SPACE 属性下可以通过 varchar2 查询到 varchar2 ,说明比较时忽略的尾部的空格,我们将 name_varchar 的排序规则切换为 UCA 9.0.0 以后版本再来看一下结果。


mysql> ALTER TABLE test CHANGE name_varchar name_varchar VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;Query OK, 0 rows affected (0.05 sec)Records: 0  Duplicates: 0  Warnings: 0
mysql> show full columns from test;+--------------+-------------+--------------------+------+-----+---------+-------+---------------------------------+---------+| Field | Type | Collation | Null | Key | Default | Extra | Privileges | Comment |+--------------+-------------+--------------------+------+-----+---------+-------+---------------------------------+---------+| id | int | NULL | NO | PRI | NULL | | select,insert,update,references | || name_char | char(20) | utf8mb4_unicode_ci | YES | | NULL | | select,insert,update,references | || name_varchar | varchar(20) | utf8mb4_0900_ai_ci | YES | | NULL | | select,insert,update,references | |+--------------+-------------+--------------------+------+-----+---------+-------+---------------------------------+---------+3 rows in set (0.01 sec)
mysql> select * from test where name_varchar = 'varchar2';Empty set (0.00 sec)
复制代码


与预期一样,切换排序规则后,尾部空格参与比较,已经不能通过varchar2 查询到 varchar2 了。

确定排序规则的 pad 属性


那接下来的问题是如何判断当前的排序规则是基于 UCA 9.0.0 之前还是之后的版本呢?其实在 mysql 8.x 版本中,排序规则保存在 information_schema 库的 COLLATIONS 表中,可以通过以下语句查询对应的 pad 属性值,例如我们一开始选择的 utf8mb4_unicode_ci。


mysql> select collation_name, pad_attribute from information_schema.collations where collation_name = 'utf8mb4_unicode_ci';+--------------------+---------------+| collation_name     | pad_attribute |+--------------------+---------------+| utf8mb4_unicode_ci | PAD SPACE     |+--------------------+---------------+1 row in set (0.00 sec)
复制代码


除了查询数据库以外,还可以通过排序规则的名称进行区别,在官方文档中有以下一段描述,摘自【Unicode Collation Algorithm (UCA) Versions】


MySQL implements the xxx_unicode_ci collations according to the Unicode Collation Algorithm (UCA) described at http://www.unicode.org/reports/tr10/. The collation uses the version-4.0.0 UCA weight keys: http://www.unicode.org/Public/UCA/4.0.0/allkeys-4.0.0.txt. The xxx_unicode_ci collations have only partial support for the Unicode Collation Algorithm.


Unicode collations based on UCA versions higher than 4.0.0 include the version in the collation name. Examples:

  • utf8mb4_unicode_520_ci is based on UCA 5.2.0 weight keys (http://www.unicode.org/Public/UCA/5.2.0/allkeys.txt),

  • utf8mb4_0900_ai_ci is based on UCA 9.0.0 weight keys (http://www.unicode.org/Public/UCA/9.0.0/allkeys.txt).


可以看出,名称类似 xxx_unicode_ci 的排序规则是基于 UCA 4.0.0 的,而 xxx_520_ci 是基于 UCA 5.2.0,xxx_0900_ci 是基于 UCA 9.0.0 的。通过查询数据库验证,排序规则中包含 0900 字样的 pad 属性均为 NO PAD,符合以上描述。


需要注意的是 binary 排序规则的 pad 属性为 NO PAD,这里其实不是个例外,因为 char、varchar 和 text 类型都归类为 nonbinary


关于作者:


aotian

Java 后端工程师,目前从事互联网教育相关系统的开发。

2022-04-06 14:071823

评论

发布
暂无评论
发现更多内容

基于 Spring Boot 的 RESTful API 设计与实现

霍格沃兹测试开发学社

大话JMeter2|正确get参数传递和HTTP如何正确使用

霍格沃兹测试开发学社

大话测试数据(一)

霍格沃兹测试开发学社

如何用Sonic云真机打王者

霍格沃兹测试开发学社

软件测试 | 测试开发 | 测试人生 | 双非学历,从外包到某大厂只用了1年时间,在2线城市年薪近30万,我柠檬了......

测吧(北京)科技有限公司

面试 测试 软件测试和开发

软件测试 | 测试开发 | 测试人生 | 从跨专业手工测试转岗外包,再到 Python 测试开发,跳槽涨薪 85%

测吧(北京)科技有限公司

Python 测试开发

如何利用 xUnit 框架对测试用例进行维护?

霍格沃兹测试开发学社

实战 | UI 自动化测试框架设计与 PageObject 改造

霍格沃兹测试开发学社

C#/VB.NET: 为Excel表格添加超链接

Geek_249eec

C# Excel VB.NET 超链接

软件测试 | 测试开发 | 测试人生 | 从外行到外包,从手工测试到知名互联大厂测开 这个90后小姐姐是怎么腾飞的?

测吧(北京)科技有限公司

同样是断言,为何 Hamcrest 如此优秀?

霍格沃兹测试开发学社

只需搞定Docker,环境问题再也不是测开路上的『坑』

霍格沃兹测试开发学社

天翼云打造国云安全品牌 铸牢企业云上安全防线

Geek_2d6073

IDaaS系统方舟一账通ArkID内置OIDC认证插件配置流程

龙归科技

Idaas OIDC ArkID

融云 2022 社交泛娱乐出海嘉年华,邀你一起「超浪」!

融云 RongCloud

IT 程序猿 社交娱乐

基于Requests与mitmproxy打造迷你接口测试框架

霍格沃兹测试开发学社

Python基础(二) | Python的基本数据类型

timerring

Python 9月月更

阿里云一站式专家测试服务,护航APP线上质量,发版无忧

移动研发平台EMAS

阿里云 移动测试 限时活动

学习Docker就应该掌握的dockerfile语法与指令

霍格沃兹测试开发学社

易观千帆《银行APP用户体验分析白皮书》重磅首发,助力银行打造获客新增长点

易观分析

金融 银行

谈安全测试的重要性

京东科技开发者

漏洞 软件系统 安全测试 网络安全渗透测试

软件测试 | 测试开发 | MySQL锁机制总结

测吧(北京)科技有限公司

知识图谱在智能运维中的应用

穿过生命散发芬芳

知识图谱 9月月更

大话JMeter4|不同的并发数可以自动化做压测吗?

霍格沃兹测试开发学社

如何做好性能压测(一):压测环境的设计和搭建

霍格沃兹测试开发学社

实战 | 基于JMeter 完成典型电商场景(首页浏览)的性能压测

霍格沃兹测试开发学社

史上最全 Appium 自动化测试从基础到框架实战精华学习笔记(一)

霍格沃兹测试开发学社

软件测试 | 测试开发 | 项目倒排,跟工期不足say byebye~

测吧(北京)科技有限公司

测试

后端Web开发框架(Java)

霍格沃兹测试开发学社

实战 | JMeter 典型电商场景(下单/支付)的性能压测

霍格沃兹测试开发学社

最佳实践|用腾讯云AI图像搜索打造属于自己的拍立淘

牵着蜗牛去散步

腾讯 图像搜索 腾讯云AI 小程序商城 AI技术实践

MySQL Varchar 类型尾部空格详解_语言 & 开发_InfoQ精选文章