在mysql中,我们都知道如果一个索引字段使用了函数或者计算那么查询的时候索引会失效,可是我相信在联表的时候我们只会关注两个表关联字段是否都创建了索引,却没有关注过这两个字段的类型是否一致,如果不一致的话索引是会失效的,因为数据库会对两个不同类型的字段进行隐式的类型转换。
上面的坑倒比较容易发现,但是在mysql存储过程中如果你的变量声明的时候是bigint,但是与他比较的字段是varchar的时候就不容易发现了。我今天在执行一个存储过程的时候我发现结果不对,明明在表中这个字段值是唯一的但是执行的时候就会查出多条报错子查询返回多行数据。
简单说,我有一个表q1,它的表结构如下:
数据大致长这样,c4是连续的19位的bigint(其实就是雪花值,但是存进去的时候没有注意到c4是varchar的),从1111111111111111111
开始递增,有200多行
我执行以下sql,能查出122行数据:
select c4 from q1 where c4 = 1111111111111111111 -- 122
我执行以下sql,能准确查出行1数据:
select c4 from q1 where c4 = '1111111111111111111' -- 1
1111111111111111111
很明显是一个19位的bigint
并没有超出bigint的范围,在存储过程中我声明的时候就是bigint,只不过我在这简单举例了。很显然后者才是我们期望的数据,我并没有搜到mysql官方说varchar类型与bigint比较的时候会先转换为那种类型进行判断,只知道会先进行隐式类型转换再进行比较(如果有朋友知道原理的话欢迎赐教)。我把c4字段类型从varchar改为bigint后发现上面两个sql查询结果都只有一行。
经过我的尝试,在c4仍为varchar的情况下,我觉得varchar与bigint进行比较的时候mysql应该是先转换为双精度浮点数double
再进行比较的,由于bigint的位数超过了16位所以会导致丢失一部分精度,所以会匹配上多行数据。
而在mysql中double的精度的精度为16位有效数据,我将其中两行改为16为长度的数字1111111111111111
和1111111111111112
,然后用下面的sql直接查16位的1111111111111111
结果只有一行:
select c4 from q1 where c4 = 1111111111111111;-- 16位,只有一行准确结果
这说明在double
的范围是准确的。
因此如果要比较varchar和bigint类型的话,如果允许的话就改为统一的类型进行比较。也可以将bigint转换为varchar进行比较,或者两者都使用更高精度的Decimal类型进行比较。
我猜测隐式转换为double也是和下面的查询有关:
select c4 + '0' 隐式类型转换,c4,cast(c4 as Decimal(19,0)) `Decimal`
from q1
一看第一列是双精度浮点数(double)类型,结合上面的16为精度测试所以这么猜测。