什么?MySQL 的等值查询竟然出错了??
1.问题背景
前段时间,一个业务线的小伙伴大 G 找过来,如下是我俩的对话。
大G
:云杰,听说你 MySQL 挺厉害的,我最近遇到一个奇怪问题,不知道你遇到过没,请教你下。
我
:请教不敢当,我也就是个 MySQL 入门级选手,说来看看。
大G
:WHERE 条件去等值查询字符串,结果却查出来几条尾部有空格的,明明不相等。
我
:不会吧?这么神奇,这个真没遇到过!
大G
:不信你试试!
我
:试试就试试!
抱着求知的心态,开启了本篇的探索之旅。
2.验证
2.1 数据准备
首先在测试库里建表,并准备相关的原数据。创建个 user_info 表,分别插入'adu'(无空格)、'adu '(一个空格)、'adu '(四个空格)三个用户。
2.2 问题验证
2.2.1 尾部空格验证
我们使用如上条件去查,还真复现了!无论查询中尾部带有几个空格,结果是一样的,都会命中'adu'、'adu '、'adu '三个用户,结果如下图所示(红框圈起来的表示我们认为不应该出现的异常结果):
太神奇了!
2.2.2 头部空格验证
那如果把空格放在前面呢?再来一把,结果如下:
这下又匹配不上了。空格放在后边可以,放在前边不可以,这太神奇了!!
2.2.3 唯一索引验证
那如果在 user_name 字段上建唯一索引,还能插入这三条记录吗?再来一把,结果如下:
也不行,被唯一索引约束住了。
2.2.4 长度验证
那这三条记录的 user_name 长度又分别是多少呢?
确实长度也不一样。
2.3 验证小结
从结果上来看,明明是三个长度不同的字符串,空格放在前边被认为是不同,放在后边又被认为是相同,而且唯一索引也冲突。我们有充足的理由怀疑 MySQL 忽略字符串尾部的空格,把'adu'、'adu '、'adu '都当成'adu'来处理。这确实超出了已有的认知,那背后的原因究竟又是什么呢?
3.分析原因
查询 MySQL 的官方文档,原来跟字符串的校对规则有关。
原来 MySQL 的校对规则基于PAD SPACE
,这就意味着 CHAR、VARCHAR、TEXT 等字符串的等值比较(“=”)会忽略掉尾部的空格,而且官网也说了,适用于所有 MySQL 版本,并且不会改变。这。。。
既然 MySQL 官网说的这么肯定,那么自信来自哪里呢?我们继续追查 SQL 规范,原来SQL规范还真对这块做了特别说明,如下所示:
既然规范都这样要求了,等值查询“=”不能精确查询,那么到底该如何精确地进行等值查询呢?
4.精确查询的方法
通过调研,我们可以通过以下两种方式进行精确等值查询。
4.1 LIKE
LIKE
是基于逐个字符进行比较的,这样就不会忽略尾部的空格,官网对这块也有特别的说明。
那么我们再使用 LIKE 进行等值查询,结果还真可以!
4.2 BINARY
BINARY
不是函数,是类型转换运算符,它用来强制它后面的字符串转为二进制字节,再逐个字节比较,也可以理解成精确匹配,官网对这块也有特别的说明。
那么我们再使用 BINARY 进行等值查询,结果也是可以的。
5.总结
MySQL 的 CHAR、VARCHAR、TEXT 等字符串字段在等值比较("=")时,基于
PAD SPACE
校对规则,会忽略掉尾部的空格;在存储时,不会自动截断尾部的空格,会按原值存储;
如果想要精确查询就不能用等值查询("="),而应改用
LIKE
或BINARY
;认知有界,而求知无界。
关于作者
杜云杰,高级架构师,转转架构部负责人,转转技术委员会执行主席,腾讯云 MVP。负责服务治理、MQ、云平台、APM、IM、分布式调用链路追踪、监控系统、配置中心、分布式任务调度平台、分布式 ID 生成器、分布式锁等基础组件。微信号:waterystone
,欢迎建设性交流。
道阻且长,拥抱变化;而困而知,且勉且行。
转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。
关注公众号「转转技术」(综合性)、「大转转 FE」(专注于 FE)、「转转 QA」(专注于 QA),更多干货实践,欢迎交流分享~
评论