当前位置 主页 > 服务器问题 > win服务器问题汇总 >

    解析MySQL隐式转换问题

    栏目:win服务器问题汇总 时间:2019-12-19 21:38

    一、问题描述

    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)
    640?wx_fmt=jpeg

    什么鬼,明明查的是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(太复杂了,简单贴个注释吧)