mysql中隐式转换导致的精度问题

场景

统计员工报工工时报表中,某个项目的某位员工多了一行不属于他的报工数据()

  • where条件可以简化为task.owner = member.id,且task.ownervarchar(36)member.idbigint
  • 该员工id为1607129629428023303,多出来的一行的报工的员工的id为1607129629428023309

原因【假】

经过拆解sql的方式,确定了,原sql的拼接条件,会查询出那一条多的报工记录,但如果将where条件改为task.owner = concat(member.id, ''),则不会出现对应的记录,故初步定位为mysql隐式转换导致的问题,并执行了以下sql,结果也与sql中的现象一致

微信截图_20241105111101

这时,在网上查阅资料,说法为mysql数据库,隐式字段转换是等号左边的转化为等号右边的类型,也就是上述例子中的task.owner转化为bigint,例子中还说,在转换过程中,如果存在超过bigint范围(有符号SIGNED:从 -2^632^63 - 1;无符号UNSIGNED:从 02^64 - 1),或者非法字符串的转化时可能会出现异常,具体异常情况见下图

微信截图_20241105112031

但可以看到,将两个值都转化为bigint后,二者时不相等的,mysql数据库,隐式字段转换是等号左边的转化为等号右边的类型这种说法不成立,故去官网找隐式转换的具体规则

原因【真】

mysql版本为8.0.30,官网上对应的隐式转换规则文档地址为14.3 Type Conversion in Expression Evaluation,规则截图:

12134

可以看到,当一个字符串与数字进行比较时,隐式转换是将其转换为浮点数进行比较,又因为隐式转换只会转化为常用的数据类型,比如FLOAT、DOUBLE、INT等,故在这个例子中,将其转化为了DOUBLE,观察以下查询结果

1730777545672

可以发现隐式转换与将其转化为DOUBLE的比较结果一致,因此问题原因在于此

为什么 18014398509481983 被舍入为 18014398509481984

这是因为 DOUBLE 类型的精度限制。DOUBLE 类型遵循 IEEE 754 双精度浮动小数点标准,具有 15~16 位有效数字的精度。当数字超出这个范围时,它会进行舍入。 18014398509481983 刚好处于 DOUBLE 精度的边缘,导致 DOUBLE 类型在表示该数字时进行舍入

  • 1801439850948198318014398509481984 之间的差异很小,但超出了 DOUBLE 类型的有效精度范围。
  • 由于 DOUBLE 类型在存储大数时使用的是 浮动小数点数表示法,即使输入的是 18014398509481983,它也可能被表示为接近的值。在这种情况下,DOUBLE 类型舍入 1801439850948198318014398509481984