解决MySQL Varchar 类型尾部空格的问题

目录
  • 背景
  • 原因
  • 详解
    • char 和 varchar 的区别
    • varchar 对于尾部空格的处理
    • 确定排序规则的 pad 属性

背景

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

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

原因

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

到此这篇关于MySQL Varchar 类型尾部空格的文章就介绍到这了,更多相关MySQL Varchar 类型尾部空格内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • MySQL中varchar和char类型的区别

    目录 前述 VARCHAR类型 VARCHAR适用情况 CHAR类型 测试 VARCHAR(5)与VARCHAR(200)的区别 总结 前述 VARCHAR和CHAR是两种最主要的字符串类型.不幸的是,很难精确地解释这些值是怎么存储在磁盘和内存中的,因为这跟存储引擎的具体实现有关.下面的描述假设使用的存储引擎是InnoDB和/或者MyISAM.如果使用的不是这两种存储引擎,请参考所使用的存储引擎的文档. 先看看VARCHAR和CHAR值通常在磁盘上怎么存储.请注意,存储引擎存储CHAR或者VAR

  • mysql中varchar类型的日期进行比较、排序等操作的实现

    在mysql使用过程中,日期一般都是以datetime.timestamp等格式进行存储的,但有时会因为特殊的需求或历史原因,日期的存储格式是varchar,那么我们该如何处理这个varchar格式的日期数据呢? 使用函数:STR_TO_DATE(str, format) STR_TO_DATE(str, format)函数是DATE_FORMAT()函数的反函数.它需要一个字符串str和一个格式字符串格式.STR_TO_DATE()返回一个DATETIME值,如果格式字符串包含日期和时间部分,

  • 详解Mysql查询条件中字符串尾部有空格也能匹配上的问题

    一.表结构 TABLE person id name 1 你 2 你(一个空格) 3 你(二个空格) 二.查询与结果 select * from person where `name` = ? 无论 ? = "你 + 几个空格",都会检索出全部三个结果. 三.原因 MySQL 校对规则属于PADSPACE,会忽略尾部空格 针对的是 varchar char text -- 等文本类的数据类型 此为 SQL 标准化行为.无需要设置也无法改变. 四.想要精确查询怎么办? 方法一:like

  • 解决MySQL Varchar 类型尾部空格的问题

    目录 背景 原因 详解 char 和 varchar 的区别 varchar 对于尾部空格的处理 确定排序规则的 pad 属性 背景 近期发现系统中某个输入框里如果输入 xxx+空格 的时候会出现异常情况,经过排查发现在调用后端接口时会有两步操作,一是从数据库中查询到的数组中将与 xxx+空格 一致的元素剔除,二是根据 xxx+空格 从数据库中查询对应的明细. 出现异常的原因是在剔除时未能剔除掉对应的元素,也就意味着 xxx+空格 对应的内容在数据库中不存在:但是在查询明细时还是查询到了,顿时感

  • mysql varchar类型求和实例操作

    有的小伙伴在学习数据库的时候,创建表结构的时候不小心把某字段设置成了varchar但是在统计求和的时候就傻眼了,接下来跟着小编学习一下,不用改该列数据类型也能求和的方法吧! 1.打开 数据库连接客户端Navicat Premium ,创建一个新的表结构,这里age这列 故意 设置为 varchar. 2.创建表成功之后,为刚刚的表创建一些测试的数据,这里如下图: 3.在数据量少的时候可以使用sum()函数直接求和,因为MySQL中它可以自动识别是字符串类型还是数字类型. 4.以上适用于整数,或者

  • Mysql中varchar类型一些需要注意的地方

    varchar的存储规则 4.0版本以下,varchar(20),指的是20字节,如果存放UTF8汉字时,只能存6个(每个汉字3字节). 5.0版本以上,varchar(20),指的是20字符,无论存放的是数字.字母还是UTF8汉字(每个汉字3字节),都可以存放20个,最大大小是65532字节. varchar 字段是将实际内容单独存储在聚簇索引之外,内容开头用1到2个字节表示实际长度. 官方是这么说的: Values in VARCHAR columns are variable-length

  • MySQL中CHAR和VARCHAR类型演变和详解

    一.演变: MySQL数据库的varchar类型在5.0.3以下的版本中的最大长度限制为255,其数据范围可以是0~255. 在MySQL5.0.3及以上的版本中,varchar数据类型的长度支持到了65535,也就是说可以存放65532个字节的数据,起始位和结束位占去了3个字节,也就是说,在5.0.3以下版本中需要使用固定的TEXT或BLOB格式存放的数据可以在高版本中使用可变长的varchar来存放,这样就能有效的减少数据库文件的大小. 如果在varchar中写入大于设定的长度,默认情况下会

  • Mysql数据库中把varchar类型转化为int类型的方法

    在上篇文章给大家讲了MySQL数据库中把int转化varchar引发的慢查询,本文给大家介绍Mysql数据库中把varchar类型转化为int类型的方法,一起看看吧! mysql为我们提供了两个类型转换函数:CAST和CONVERT,现成的东西我们怎能放过? CAST() 和CONVERT() 函数可用来获取一个类型的值,并产生另一个类型的值. 这个类型 可以是以下值其中的 一个: BINARY[(N)] CHAR[(N)] DATE DATETIME DECIMAL SIGNED [INTEG

  • MySQL数据库中varchar类型的数字比较大小的方法

    创建测试表 -- ---------------------------- -- Table structure for check_test -- ---------------------------- DROP TABLE IF EXISTS `check_test`; CREATE TABLE `check_test` ( `id` int(11) NOT NULL AUTO_INCREMENT, `current_price` varchar(10) NOT NULL, `price`

  • MySQL中把varchar类型转为date类型方法详解

    如下表: 先使用str_to_date函数,将其varchar类型转为日期类型,然后从小到大排序 语法:select str_to_date(class_time,'%Y%m%d %H:%i:%s') a from a order by a desc ; 下面接着看下oracle中varchar类型的日期格式转换date类型 oracle中varchar类型的日期格式转换date类型 SELECT to_char(to_date(m.ma_datetime,'yyyy-MM-dd hh24:mi

  • 解决postgreSql 将Varchar类型字段修改为Int类型报错的问题

    项目使用postgreSql数据库,先需要将库中的某个表中的某个字段类型由Varchar改成Int,直接右键设计表,修改类型为int,保存的时候报错,错误如下: 意思就是,这个crt_user字段不能自动转换成成类型bigint,需要使用USING表达式来转换. 这是在库中运行修改字段的类型的sql: ALTER TABLE auth_client_service ALTER COLUMN crt_user SET DATA TYPE int8 USING crt_user:: int8, AL

  • MYSQL的binary解决mysql数据大小写敏感问题的方法

    复制代码 代码如下: mysql> select binary 'ABCD'='abcd' COM1, 'ABCD'='abcd' COM2;+--------+-----------+| COM1 | COM2 |+--------+-----------+|      0     |      1      |+---------+-----------+1 row in set (0.00 sec) (仅仅有些而已!4.*以前)因为有的MySQL特别是4.*以前的对于中文检索会有不准确的问

随机推荐