MySQL 派生表查询导致 Crash 的根源分析与解决方案
MySQL 派生表查询导致 Crash 的根源分析与解决方案
一、问题发现
在之前的 MySQL 8.0.32 使用中,发现使用以下带有派生表的 SQL 会导致 MySQL Crash,以下的 sequence_table(2)替换为任何非常量表都行:
仅 MySQL 8.0.32 版本有影响。
Crash 的堆栈如下:
二、问题调查过程
调查执行 SQL 的 optimize 的过程,分析发现该 SQL 的 SQL 变换情况如下:
以下的 trim(ref_15.c_ogj)
执行完 find_order_in_list
后,Item_func_trim
的args[0]->m_ref_item[0]
等于0<>0 as c_lrcm63eani
,而不是0<>0 as c_ogj
,这是因为c_lrcm63eani
和 c_ogj 的名字都一样,都是 0<>0,在find_order_in_list
函数里面由于名字一样因此内层字段被外层替代了。而后在Item::clean_up_after_removal
执行的时候,Item_func_ne 即 c_lrcm63eani 因为出现了 2 次,因此执行了 2 次decrement_ref_count()
,然而在Query_block::delete_unused_merged_columns
函数却把0<>0 as c_lrcm63eani
的 Item 置为空了,因为这个时候 c_lrcm63eani 的item->decrement_ref_count()
以后 ref_count()为 0 因此继续执行Item::clean_up_after_removal
了。
查看函数调用过程发现 Query_block 在 prepare 的时候执行了 delete_unused_merged_columns,
三、解决方案
通过上面的分析,我们可以发现问题在于多执行了一次Item::clean_up_after_removal
,随后在 MySQL 最新代码尝试执行以上 SQL 发现该 BUG 已经被修复,找到相关修复代码,可以发现以下修复代码。
相关 commit ID 号为: 2171a1260e2cdbbd379646be8ff6413a92fd48f4
修改完查看一下这个函数的堆栈信息:
对于0<>0 as c_lrcm63eani
这个Item_func_ne
对象,执行到Item::clean_up_after_removal
的时候,因为reference_count() > 1
因此会执行新添加的ctx->stop_at(this)
,等到下一次再执行到这个Item_func_ne
的clean_up_after_removal()
函数的时候,就会因为ctx->is_stopped(this)
而直接返回,不再执行一次decrement_ref_count()
,从而避免了执行后面的transl->item = nullptr
。
四、问题总结
通过以上分析我们可以发现,对于复杂的 SQL 会执行复杂的 Item 变换和删除不需要的 Item,但是正是由于这样才更容易导致 Crash 的出现。分析类似这样的 Crash 问题的时候,因为涉及代码量大,代码逻辑复杂往往很难找到相关修复代码,因此需要对代码运行流程比较熟悉,同时要有相关复杂问题解决的经验才能更好的应对这类问题。
版权声明: 本文为 InfoQ 作者【GreatSQL】的原创文章。
原文链接:【http://xie.infoq.cn/article/6cff7f051d283f10a315a14fd】。文章转载请联系作者。
评论