MySQL令人咋舌的隐式转换


Posted in MySQL onApril 05, 2021

一、问题描述

root@mysqldb 22:12:  [xucl]> show create table t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `id` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

root@mysqldb 22:19:  [xucl]> select * from t1;
+--------------------+
| id                 |
+--------------------+
| 204027026112927605 |
| 204027026112927603 |
| 2040270261129276   |
| 2040270261129275   |
| 100                |
| 101                |
+--------------------+
6 rows in set (0.00 sec)

奇怪的现象:

root@mysqldb 22:19:  [xucl]> select * from t1 where id=204027026112927603;
+--------------------+
| id                 |
+--------------------+
| 204027026112927605 |
| 204027026112927603 |
+--------------------+
2 rows in set (0.00 sec)

明明查的是204027026112927603,为什么204027026112927605也出来了

 

二、源码解释

其中JOIN::exec()是执行的入口,Arg_comparator::compare_real()是进行等值判断的函数,其定义如下

int Arg_comparator::compare_real()
{
  /*
    Fix yet another manifestation of Bug#2338. 'Volatile' will instruct
    gcc to flush double values out of 80-bit Intel FPU registers before
    performing the comparison.
  */
  volatile double val1, val2;
  val1= (*a)->val_real();
  if (!(*a)->null_value)
  {
    val2= (*b)->val_real();
    if (!(*b)->null_value)
    {
      if (set_null)
        owner->null_value= 0;
      if (val1 < val2)  return -1;
      if (val1 == val2) return 0;
      return 1;
    }
  }
  if (set_null)
    owner->null_value= 1;
  return -1;
}

比较步骤如下图所示,逐行读取t1表的id列放入val1,而常量204027026112927603存在于cache中,类型为double类型(2.0402702611292762E+17),所以到这里传值给val2后val2=2.0402702611292762E+17。

当扫描到第一行时,204027026112927605转成doule的值为2.0402702611292762e17,等式成立,判定为符合条件的行,继续往下扫描,同理204027026112927603也同样符合

如何检测string类型的数字转成doule类型是否溢出呢?这里经过测试,当数字超过16位以后,转成double类型就已经不准确了,例如20402702611292711会表示成20402702611292712(如图中val1)

MySQL string转成double的定义函数如下:

{
  char buf[DTOA_BUFF_SIZE];
  double res;
  DBUG_ASSERT(end != NULL && ((str != NULL && *end != NULL) ||
                              (str == NULL && *end == NULL)) &&
              error != NULL);

  res= my_strtod_int(str, end, error, buf, sizeof(buf));
  return (*error == 0) ? res : (res < 0 ? -DBL_MAX : DBL_MAX);
}

真正转换函数my_strtod_int位置在dtoa.c(太复杂了,简单贴个注释吧)

/*
  strtod for IEEE--arithmetic machines.
 
  This strtod returns a nearest machine number to the input decimal
  string (or sets errno to EOVERFLOW). Ties are broken by the IEEE round-even
  rule.
 
  Inspired loosely by William D. Clinger's paper "How to Read Floating
  Point Numbers Accurately" [Proc. ACM SIGPLAN '90, pp. 92-101].
 
  Modifications:
 
   1. We only require IEEE (not IEEE double-extended).
   2. We get by with floating-point arithmetic in a case that
     Clinger missed -- when we're computing d * 10^n
     for a small integer d and the integer n is not too
     much larger than 22 (the maximum integer k for which
     we can represent 10^k exactly), we may be able to
     compute (d*10^k) * 10^(e-k) with just one roundoff.
   3. Rather than a bit-at-a-time adjustment of the binary
     result in the hard case, we use floating-point
     arithmetic to determine the adjustment to within
     one bit; only in really hard cases do we need to
     compute a second residual.
   4. Because of 3., we don't need a large table of powers of 10
     for ten-to-e (just some small tables, e.g. of 10^k
     for 0 <= k <= 22).
*/

既然是这样,我们测试下没有溢出的案例

root@mysqldb 23:30:  [xucl]> select * from t1 where id=2040270261129276;
+------------------+
| id               |
+------------------+
| 2040270261129276 |
+------------------+
1 row in set (0.00 sec)

root@mysqldb 23:30:  [xucl]> select * from t1 where id=101;
+------+
| id   |
+------+
| 101  |
+------+
1 row in set (0.00 sec)

结果符合预期,而在本例中,正确的写法应当是

root@mysqldb 22:19:  [xucl]> select * from t1 where id='204027026112927603';
+--------------------+
| id                 |
+--------------------+
| 204027026112927603 |
+--------------------+
1 row in set (0.01 sec)

三、结论

  1. 避免发生隐式类型转换,隐式转换的类型主要有字段类型不一致、in参数包含多个类型、字符集类型或校对规则不一致等

  2. 隐式类型转换可能导致无法使用索引、查询结果不准确等,因此在使用时必须仔细甄别

  3. 数字类型的建议在字段定义时就定义为int或者bigint,表关联时关联字段必须保持类型、字符集、校对规则都一致

  4. 最后贴一下官网对于隐式类型转换的说明吧

1、If one or both arguments are NULL, the result of the comparison is NULL, except for the NULL-safe
<=> equality comparison operator. For NULL <=> NULL, the result is true. No conversion is needed.
2、If both arguments in a comparison operation are strings, they are compared as strings.
3、If both arguments are integers, they are compared as integers.
4、Hexadecimal values are treated as binary strings if not compared to a number.
5、If one of the arguments is a TIMESTAMP or DATETIME column and the other argument is a
constant, the constant is converted to a timestamp before the comparison is performed. This is
done to be more ODBC-friendly. This is not done for the arguments to IN(). To be safe, always
use complete datetime, date, or time strings when doing comparisons. For example, to achieve best
results when using BETWEEN with date or time values, use CAST() to explicitly convert the values to
the desired data type.
A single-row subquery from a table or tables is not considered a constant. For example, if a subquery
returns an integer to be compared to a DATETIME value, the comparison is done as two integers.
The integer is not converted to a temporal value. To compare the operands as DATETIME values,
use CAST() to explicitly convert the subquery value to DATETIME.
6、If one of the arguments is a decimal value, comparison depends on the other argument. The
arguments are compared as decimal values if the other argument is a decimal or integer value, or as
floating-point values if the other argument is a floating-point value.
7、In all other cases, the arguments are compared as floating-point (real) numbers.

MySQL 相关文章推荐
MySQL慢查询的坑
Apr 28 MySQL
MySQL系列之一 MariaDB-server安装
Jul 02 MySQL
Prometheus 监控MySQL使用grafana展示
Aug 30 MySQL
MySQL 数据类型详情
Nov 11 MySQL
关于mysql中时间日期类型和字符串类型的选择
Nov 27 MySQL
MySQL 开窗函数
Feb 15 MySQL
深入讲解数据库中Decimal类型的使用以及实现方法
Feb 15 MySQL
一文了解MySQL二级索引的查询过程
Feb 24 MySQL
一文搞清楚MySQL count(*)、count(1)、count(col)区别
Mar 03 MySQL
解决MySQL Varchar 类型尾部空格的问题
Apr 06 MySQL
MYSQL常用函数介绍
May 05 MySQL
mysql 体系结构和存储引擎介绍
May 06 MySQL
MySQL基础(一)
Apr 05 #MySQL
MySQL基础(二)
MySQL学习总结-基础架构概述
MySQL锁机制
mysql知识点整理
Apr 05 #MySQL
浅析InnoDB索引结构
Apr 05 #MySQL
MySQL入门命令之函数-单行函数-流程控制函数
Apr 05 #MySQL
You might like
2020最新CPU的性能排名
2020/04/02 数码科技
php数据结构与算法(PHP描述) 快速排序 quick sort
2012/06/21 PHP
thinkphp,onethink和thinkox中验证码不显示的解决方法分析
2016/06/06 PHP
获取Javscript执行函数名称的方法
2006/12/22 Javascript
javascript 词法作用域和闭包分析说明
2010/08/12 Javascript
javascript getElementsByTagName
2011/01/31 Javascript
autoIMG 基于jquery的图片自适应插件代码
2011/03/12 Javascript
在js文件中如何获取basePath处理js路径问题
2013/07/10 Javascript
JavaScript数据结构与算法之栈详解
2015/03/12 Javascript
JS实现的不规则TAB选项卡效果代码
2015/09/18 Javascript
JS获取字符串实际长度(包含汉字)的简单方法
2016/08/11 Javascript
js入门之Function函数的使用方法【新手必看】
2016/11/22 Javascript
Bootstrap缩略图与警告框学习使用
2017/02/08 Javascript
获取layer.open弹出层的返回值方法
2018/08/20 Javascript
JavaScript中join()、splice()、slice()和split()函数用法示例
2018/08/24 Javascript
微信小程序实现tab左右切换效果
2020/11/15 Javascript
Vue常见面试题整理【值得收藏】
2018/09/20 Javascript
微信小程序实现多选功能
2018/11/04 Javascript
Vue CLI3基础学习之pages构建多页应用
2019/06/02 Javascript
vue-路由精讲 二级路由和三级路由的作用
2020/08/06 Javascript
Python的re模块正则表达式操作
2016/05/25 Python
Python自动化测试ConfigParser模块读写配置文件
2016/08/15 Python
python实现微信跳一跳辅助工具步骤详解
2018/01/04 Python
python reverse反转部分数组的实例
2018/12/13 Python
Python中使用pypdf2合并、分割、加密pdf文件的代码详解
2019/05/21 Python
pytorch-RNN进行回归曲线预测方式
2020/01/14 Python
10分钟理解CSS3 Grid布局
2018/12/20 HTML / CSS
植物选择:Botanic Choice
2017/02/15 全球购物
生物有机护肤品:Aurelia Probiotic Skincare
2018/01/31 全球购物
设计总监岗位职责
2013/12/07 职场文书
网吧收银员岗位职责
2013/12/14 职场文书
高中政治教学反思
2014/01/18 职场文书
银行竞聘演讲稿
2014/05/16 职场文书
工作作风整顿个人剖析材料
2014/10/11 职场文书
入党转正申请报告
2015/05/15 职场文书
Mysql服务添加 iptables防火墙策略的方案
2021/04/29 MySQL