天天看点

MySQL · 捉虫动态 · left-join多表导致crash

有一天小编胡乱写sql, left join了30张表, 结果导致了mysql server gone away…

我们来看看crash堆栈

可以看出, 在产生执行计划过程中crash了。

堆栈表明, <code>update_ref_and_keys</code>函数中<code>join_tab-&gt;join-&gt;join_list</code>为无效地址。 排查看到函数入口处这个变量还是ok的, 那么在gdb里watch一下。

这么整齐的地址一看就有问题。函数栈:

而<code>add_key_fields</code>修改<code>join_tab-&gt;join-&gt;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-&gt;alloc(sz)</code>分配空间, 再调用<code>add_key_fields</code>递归遍历where树, 遇到等值表达式, 会填充到<code>key_fields</code>数组中。而之前已经看到, add_key_field在写key_fields时却修改了<code>join_tab-&gt;join-&gt;join_list</code>。

可见在new的时候拿到了<code>join_tab-&gt;join-&gt;join_list</code>, 是(*key_fields++)的时候, 加过头了。从而可推断, key_fields没有分配到应该有的内存空间。那么出问题的就是sz用来分配空间的数字了。

这里涉及到两个变量<code>select_lex-&gt;cond_count</code>和<code>select_lex-&gt;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之前的小版本都可以复现, 请尽情调戏 .^.