有一天小编胡乱写sql, left join了30张表, 结果导致了mysql server gone away…
我们来看看crash堆栈
可以看出, 在产生执行计划过程中crash了。
堆栈表明, <code>update_ref_and_keys</code>函数中<code>join_tab->join->join_list</code>为无效地址。 排查看到函数入口处这个变量还是ok的, 那么在gdb里watch一下。
这么整齐的地址一看就有问题。函数栈:
而<code>add_key_fields</code>修改<code>join_tab->join->join_list</code>实际是不合理的, 因此这里说明一下路径上几个关键的函数。
还要从子查询优化说起,当遇到semi-join子查询情况下, <code>join::optimize()</code>会调用<code>join::flatten_subqueries</code>改写sql, 如下形式:
会被修改为:
函数<code>join::flatten_subqueries</code>, 做了以下几件事:
创建semi join(it1, …, itn)的节点并添加到外层查询语句的from语法树下
将<code>subq_where and oe=ie</code>加入到外层查询语句的where树下
再移除原先的子查询语句
<code>join::flatten_subqueries</code>中, 对于每一个子查询, 调用函数<code>join::convert_subquery_to_semijoin</code>, 那么子查询上维护的query信息也要同步加到外部查询上。所以可见, 子查询中的信息, 会转交给外部查询。
之后, <code>join::optimize()</code>调用<code>update_ref_and_keys</code>, 这个函数用来处理出最终查询要使用的索引。crash的问题也出现在这个函数中, 因此还要看<code>update_ref_and_keys</code>内部做了什么。
在函数<code>update_ref_and_keys</code>中, 一个重要的数组, key_fields, 用来存放所有可能用到的索引字段。先通过<code>key_fields=(key_field*) thd->alloc(sz)</code>分配空间, 再调用<code>add_key_fields</code>递归遍历where树, 遇到等值表达式, 会填充到<code>key_fields</code>数组中。而之前已经看到, add_key_field在写key_fields时却修改了<code>join_tab->join->join_list</code>。
可见在new的时候拿到了<code>join_tab->join->join_list</code>, 是(*key_fields++)的时候, 加过头了。从而可推断, key_fields没有分配到应该有的内存空间。那么出问题的就是sz用来分配空间的数字了。
这里涉及到两个变量<code>select_lex->cond_count</code>和<code>select_lex->between_count</code>, 而cond_count就是number of conditions; 构造的语句中的等值表达式足有31条, 而这里在分配时是2, 活该内存越界。
而这个变量在子查询优化过程中, 子查询应该将其移交给外部查询语句。
函数<code>join::convert_subquery_to_semijoin</code>中, 改写完sql后, 忘记把子查询的cond_count和between_cond信息更新到外部查询了, 这时只要手动添加即可。
<a href="https://github.com/mysql/mysql-server/commit/71e74f2a0118f460abc4f7a3da215c61785d35f0" target="_blank">官方修复(5.6.25)参见</a>
<a href="http://dev.mysql.com/worklog/task/?id=5275" target="_blank">相关worklog参见</a>
可以通过以下方式复现
然后执行
mysql5.6在5.6.25之前的小版本都可以复现, 请尽情调戏 .^.