MySQL系列:隐式类型转化可能带来的坑

时间:2023-03-09 17:09:52
MySQL系列:隐式类型转化可能带来的坑

在开发规范中,我们往往会要求研发避免在where条件中出现隐式类型转换,这么要求大概有以下两方面的原因:

  1. 隐式类型转换可能导致索引失效;
  2. 隐式类型转换可能产生非预期的结果。

注:这里说的是隐式类型转换。

我们可以看下官方关于类型转换的解释:

MySQL系列:隐式类型转化可能带来的坑

这里讨论以下情况:

In all other cases, the arguments are compared as floating-point (real) numbers.

回顾:一次研发提供过来的数据修正的sql:

 update tbjxxxxaccout set balancexxx=balancexxx+0.1 where bankxxx=6222000233022332111;

即,根据银行电子账户更新账户余额信息。执行后发现更新了多条数据(电子账户是唯一的),然后回滚操作;重新检查sql,发现bankxxx的条件忘记加引号,正确的sql应该是:

 update tbjxxxxaccout set balancexxx=balancexxx+0.1 where bankxxx='';

bankxxx的字段类型为varchar(20),在没有加引号的情况下发生了隐式数据类型转换,按官方的解释:这种情况的比较会转换为浮点型数据再去比较,即6222000233022332111会转化为string类型再转化为float类型来比较(注:至于为什么会先转换成string再转换成float,这其中内部机制我也没搞明白,有待考证)。

下面造几个数据做测试:

 CREATE TABLE `t_zw1` (

   `id` int(11) DEFAULT NULL,

   `account_id` char(19) DEFAULT NULL,

   `balance_amount` decimal(18,2) DEFAULT NULL

 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 insert into t_zw1 values(1, '', 18.9);

 insert into t_zw1 values(2, '', 108.1);

 insert into t_zw1 values(3, '', 180.3);

 insert into t_zw1 values(4, '', 2.3);

 insert into t_zw1 values(5, '', 33.3);

 root@zow 10:09:34>select * from t_zw1;

 +------+---------------------+----------------+

 | id   | account_id  | balance_amount |

 +------+------------------------------+----------------+

 |    1 | 6222000233022332111 |          18.90 |

 |    2 | 6222000233022332211 |         108.10 |

 |    3 | 6222000233022332311 |         180.30 |

 |    4 | 6222000233022334211 |           2.30 |

 |    5 | 6222000233022334311 |          33.30 |

 +------+---------------------+------------------------+

我们看下account为6222000233022332111 的情况:

 root@zow 10:17:02>select * from t_zw1 where account_id='';

 +------+---------------------+----------------+

 | id   | account_id          | balance_amount |

 +------+---------------------+----------------+

 |    1 | 6222000233022332111 |          18.90 |

 +------+---------------------+----------------+

 1 row in set (0.00 sec)

 root@zow 10:17:45>select * from t_zw1 where account_id=6222000233022332111;

 +------+---------------------+----------------+

 | id   | account_id          | balance_amount |

 +------+---------------------+----------------+

 |    1 | 6222000233022332111 |          18.90 |

 |    2 | 6222000233022332211 |         108.10 |

 |    3 | 6222000233022332311 |         180.30 |

 +------+---------------------+----------------+

 3 rows in set (0.00 sec)

 root@zow 10:18:59>select account_id, account_id+0.0, if(6222000233022332111=account_id, 1, 0) from t_zw1;

 +---------------------+----------------------+------------------------------------------+

 | account_id          | account_id+0.0       | if(6222000233022332111=account_id, 1, 0) |

 +---------------------+----------------------+------------------------------------------+

 | 6222000233022332111 | 6.222000233022332e18 |                                        1 |

 | 6222000233022332211 | 6.222000233022332e18 |                                        1 |

 | 6222000233022332311 | 6.222000233022332e18 |                                        1 |

 | 6222000233022334211 | 6.222000233022334e18 |                                        0 |

 | 6222000233022334311 | 6.222000233022334e18 |                                        0 |

 +---------------------+----------------------+------------------------------------------+

可以看出转化为float类型后,由于浮点类型精度的问题,红字的三条件记录转换后的值(约数)是一样的。

总结:隐式类型转换会带了很多不确定的结果,一定要杜绝where条件中隐式转换。

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