升级数据库小版本也不省心--发现mysql5.7.18的严重bug(续)

   源于朋友发邮件提问,前几天意外发现mysql.5.7.18以及目前最新的版本5.7.20 都存在的一个bug,   bug现象如下:

mysql>  SELECT distinct  con_id   FROM user_task  where id in (56,57); 

+--------+

| con_id |

+--------+

|    844 |

+--------+

mysql>  SELECT distinct  con_id   FROM user_task  where id in (56,57,58);

+--------+

| con_id |

+--------+

|    844 |

|    785 |

+--------+

mysql>  SELECT distinct  con_id   FROM user_task  where id in (56,57,58,66,70,71);


Empty set (3 min 0.16 sec)

   查询范围变大, 结果反而没有了。 原因是最后面sql的执行计划跟前面sql的执行计划不一样。  详情请阅读前面两篇文章:

当在mysql5.7上发现这个bug,小心脏不好受了

小心脏平复了--发现mysql5.7的s1(最严重)级bug后第二天

在各位朋友的帮助下,确认这个bug的存在,以及需要在下一个版本5.7.21(未发布)上解决。同时,当前的解决办法是设置优化器参数,设置如下

set  optimizer_switch='use_index_extensions=off';

        到此,这个问题也算圆满解决。但作者发愁是否线上所有的mysql 实例都得修改这个参数?哪些版本有影响(不影响的版本先不动)? 这个bug产生的原因?

         抱着试试看的心态捋代码,还算比较幸运,没花太久的时间找到了下面这个函数(对代码不感兴趣的直接跳到最后看结果,免得厌烦,嘿嘿):

bool Field::is_part_of_actual_key(THD *thd, uint cur_index)

{

  return thd->optimizer_switch_flag(OPTIMIZER_SWITCH_USE_INDEX_EXTENSIONS) ?

    part_of_key.is_set(cur_index) :

    part_of_key_not_extended.is_set(cur_index);

}

  该函数的作用是用来判断当前字段是否存在当前索引中。解析函数体,里面用到了优化器参数OPTIMIZER_SWITCH_USE_INDEX_EXTENSIONS, 也就是我们为了解决遇到的这个bug而需要设置的优化器的一个参数use_index_extensions(还不了解的请阅读前面提到的两篇文章)。  如果该参数为true ,则调用函数part_of_key.is_set(cur_index) 来判断, 如果为false,则调用函数part_of_key_not_extended.is_set(cur_index)来判断。


这个两个函数的作用类似,但有点区别:

part_of_key.is_set(cur_index),该函数不排除主键,用该函数来判断主键字段,永远返回true. 因为主键一定包含在索引中(索引一定会存放主键字段)。 

part_of_key_not_extended.is_set(cur_index),该函数会排除主键字段。如果当前索引是一个二级索引,用来判断主键字段,会返回为false. 


上面信息仅仅作为补充,最关键信息还在后面。

根据这个函数,找到了下面这个代码, 这段代码存在于get_best_group_min_max(PARAM *param, SEL_TREE *tree, const Cost_estimate *cost_est) 函数中

    /*

      If the current storage manager is such that it appends the primary key to

      each index, then the above condition is insufficient to check if the

      index is covering. In such cases it may happen that some fields are

      covered by the PK index, but not by the current index. Since we can't

      use the concatenation of both indexes for index lookup, such an index

      does not qualify as covering in our case. If this is the case, below

      we check that all query fields are indeed covered by 'cur_index'.

    */

    if (pk < MAX_KEY && cur_index != pk &&

        (table->file->ha_table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX))

    {

      /* For each table field */

      for (uint i= 0; i < table->s->fields; i++)

      {

        Field *cur_field= table->field[i];

        /*

          If the field is used in the current query ensure that it's

          part of 'cur_index'

        */

        if (bitmap_is_set(table->read_set, cur_field->field_index) &&

            !cur_field->is_part_of_actual_key(thd, cur_index))

        {

          cause= "not_covering";

          goto next_index;                  // Field was not part of key

        }

      }

    }

    trace_idx.add("covering", true);


该段代码的作用是(主要看for循环体里面的内容),

如果当前字段在必须需要查询的字段中(bitmap_is_set(table->read_set, cur_field->field_index) 为true) ,但该字段不在当前选择的索引中,也就是cur_field->is_part_of_actual_key(thd, cur_index) 为false, 取反后为true, 

然后进入条件体,   cause= "not_covering";   goto next_index;   也就是跳转到“选择另外一个索引“。 

      反之,如果所有的字段都不满足跳转到另外一个索引(goto next_index)的条件,则认为是一个索引覆盖查询,  trace_idx.add("covering", true); 继续走后面的流程。 

     

        而这个bug出现的时候,执行路径就是一个索引覆盖查询,走的是唯一性索引(具体见前面的文章),而后出现了错误的结果。当修改优化器参数之后,cur_field->is_part_of_actual_key(thd, cur_index) 函数返回结果将产生变化,将会选择另外一个路径(goto next_index),从而结果正确。

          这就是设置 optimizer_switch='use_index_extensions=off'; 能够规避这个bug的真正原因。


         (藐视)找到原因,我们来确认一下其他版本是否有这个问题, 因为在bug网站上mysql 研发确认要到5.7.21 才能解决,所以我们来确认一下5.7.17 会不会有这个问题。 然后比较5.7.17 与5.7.18 这段代码


5.7.17 这段代码:


5.7.18这段代码

图片


这两段代码不同的地方:

cur_field->part_of_key_not_clustered.is_set(cur_index)    ----5.7.17调用的函数


cur_field->is_part_of_actual_key(thd, cur_index)    ----5.7.18调用的函数


 从代码上来看,5.7.17 在判断是否是索引覆盖的时候,排除了主键。而5.7.18 版本则根据参数use_index_extensions 是否为false来做排除,如果是true,则不作排除。

     

      因此,貌似这个bug 是从5.7.18开始引入了,5.7.17应该没有这个问题。 

这个信息也让人好尴尬: 好的一方面是5.7.17没有这个问题,不需要修改参数了。但从另一方面来看,就要吐槽了,发布小版本也带来一个大bug, 坑坑坑...... 让使用者不省心!!!

(因为没有现成的5.7.17测试环境,偷懒没有真正实际验证,有空验证一下,如果万一错了,请各位海涵。作者尽快实际验证)。


猜你喜欢

转载自blog.51cto.com/15057824/2648654