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】。文章转载请联系作者。









评论