MySQL的隐式转换导致诡异现象的案例一则

同事问了个MySQL问题,现象上确实诡异,大致意思是select表的数据,where条件是"a=0",其中a字段是varchar类型,该字段存在NULL以及包含字符的记录,但是并无"0"的记录,然后执行SQL,返回的记录,恰恰就是所有包含字符的记录,明明没有0值记录,却可以返回,而且有规律,这是什么现象?

select * from test where a = 0;

为了比对说明,我们分别用MySQL、Oracle和SQL Server进行模拟。

(1)MySQL建表和插入数据的语句,

create table test (id int, a varchar(3000), b varchar(2000));
insert into test values(1, '测试a', '测试b'),(2, NULL, '测试');

(2)Oracle建表和插入数据的语句,

create table test (id NUMBER(1), a varchar2(3000), b varchar2(2000));
insert into test values(1, '测试a', '测试b');
insert into test values(2, NULL, '测试');

(3)SQL Server建表和插入数据的语句,

create table test (id numeric(1,0), a varchar(3000), b varchar(2000));
insert into test values(1, '测试a', '测试b');
insert into test values(2, NULL, '测试');

test表返回的记录,都应该是,

11ca699953fdc868a18e323b1a90ae0e.png

我们看下三种数据库中,都执行如下语句,得到的是什么,

select * from test where a = 0;

(1)MySQL执行返回如下带字符的记录,但实际逻辑上,肯定是错的,

45c21377a4e0559d0b7344f63db24005.png

执行时,还会抛出一个warning,

Truncated incorrect DOUBLE value: '测试a'

(2)Oracle执行直接报错,提示"无效数字",因为a是varchar2,0是数字,因此报错是针对字段a的,需要将a转成数字,但字符是无法转成数字的,所以提示"无效数字"是合情合理的,

ORA-01722: 无效数字

(3)SQL Server执行直接报错,但是提示信息更加清晰明了,说的就是字段a的值"测试a"不能转成int数值型,

SQL 错误 [245] [S0001]: 在将 varchar 值 '测试a' 转换成数据类型 int 时失败。

通过以上对比,可以知道Oracle和SQL Server对"字符型=数值型"的条件,会自动将字符型类型转成数值型,如果因为值的问题不能转成数值型,就会提示错误,而SQL Server给出的提示,比Oracle更具体。

相比之下,MySQL针对"字符型=数值型"的条件,不仅能执行,而且执行是错的,这就很拉垮了。毕竟对产品来说,避免错误可能比表面上能执行更加重要,但就这个问题上,Oracle和SQL Server可以说更胜一筹的。

MySQL为什么在这里会给出错误的结果?

从官方文档的这几段内容,我们可以得到一些线索,

https://dev.mysql.com/doc/refman/5.7/en/type-conversion.html

2c35856abdfbcf7e514669503ad51d7e.png

MySQL中将varchar转成int,是会自动截断字符串的,例如"1测试"会截成"1",通过如下判断,可以证明,

bisal@mysqldb 23:26:  [test]> select 1="1测试a";
+--------------+
| 1="1测试a"   |
+--------------+
|            1 |
+--------------+
1 row in set, 1 warning (0.00 sec)

上述例子中"测试a"会截成"",而""和0是相等的,才会返回所有字段不为空的记录,

bisal@mysqldb 23:27:  [test]> select 0="测试a";
+-------------+
| 0="测试a"   |
+-------------+
|           1 |
+-------------+
1 row in set, 1 warning (0.00 sec)

通过0和""进行比较,则可以进一步证明这个问题,

bisal@mysqldb 23:29:  [test]> select 0="";
+------+
| 0="" |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

因此,正是因为MySQL对字符串进行隐式转换时会截断再转,而不是像Oracle、SQL Server这些数据库针对这种问题直接报错,所以才出现了这个诡异的问题。

我不知道这种设计是出于什么考虑,但这种"容错性"不可取,毕竟返回了错误的结果集。

当然,这个问题也和数据类型的使用有关,SQL条件中"a=0"实际是"varchar=int",两边类型不一致,所以才导致了数据库的隐式转换,可能是数据库设计的问题(字段应该是int,但是定义成了varchar),还可能是开发人员的使用问题(SQL条件右值应该用字符类型,例如"0",但实际上用了int数值类型的0),如果按照数据库设计开发规范的要求,严格保证"="号两边的数据类型一致,这就不会引发数据库的隐式转换。

如果您认为这篇文章有些帮助,还请不吝点下文章末尾的"点赞"和"在看",或者直接转发pyq,

5b5138e21016454efb5e37cdd0e4c1c8.png

近期更新的文章:

MySQL中用到了索引还很慢的一个SQL场景

什么是"金砖国家"?

最近碰到的一些问题

MySQL客户端指令用法的探索

MySQL运行时的可观测性

近期的热文:

推荐一篇Oracle RAC Cache Fusion的经典论文

"红警"游戏开源代码带给我们的震撼

文章分类和索引:

公众号1200篇文章分类和索引

猜你喜欢

转载自blog.csdn.net/bisal/article/details/132613658