MySQL派生表查询引发崩溃的根源剖析及应对之策

MySQL派生表查询崩溃根源剖析与解决策略

一、问题的发现

在使用MySQL 8.0.32时,发现执行包含派生表的以下SQL会导致MySQL崩溃,其中sequence_table(2)替换为任意非常量表都会出现此情况:

仅MySQL 8.0.32版本受影响。

EXPLAIN FORMAT=TREE 
select  
    trim(ref_15.c_ogj),
    0<>0 as c_lrcm63eani
  from 
    (select 
    0<>0 as c_ogj
  from 
   sequence_table(2) t1
  where 0<>0
  order by c_ogj asc) as ref_15;

崩溃时的堆栈信息如下:

Thread 55 "mysqld" received signal SIGSEGV, Segmentation fault.
Item_view_ref::used_tables (this=0x7fff2418f410)
    at sql/item.h:6670
6670            table_map inner_map = ref_item()->used_tables(); ==> ref_item()为空指针,因此crash了
(gdb) bt
#0  Item_view_ref::used_tables (this=0x7fff2418f410)
    at sql/item.h:6670
#1  0x0000555558e978d1 in Item::const_item (this=0x7fff2418f410)
    at sql/item.h:2342
#2  0x0000555558ecc765 in Item_ref::print (this=0x7fff2418f410, thd=0x7fff24001050, 
    str=0x7fffc83ee7e0, query_type=(QT_TO_SYSTEM_CHARSET | QT_SHOW_SELECT_NUMBER))
    at sql/item.cc:9993
#3  0x000055555903b839 in Item_func_trim::print (this=0x7fff24120d20, thd=0x7fff24001050, 
    str=0x7fffc83ee7e0, query_type=(QT_TO_SYSTEM_CHARSET | QT_SHOW_SELECT_NUMBER))
    at sql/item_strfunc.cc:3244
#4  0x0000555558ea7fc5 in Item::print_item_w_name (this=0x7fff24120d20, thd=0x7fff24001050, 
    str=0x7fffc83ee7e0, query_type=(QT_TO_SYSTEM_CHARSET | QT_SHOW_SELECT_NUMBER))
    at sql/item.cc:727
#5  0x00005555593f18c0 in Query_block::print_item_list (this=0x7fff24120768, thd=0x7fff24001050, 
    str=0x7fffc83ee7e0, query_type=(QT_TO_SYSTEM_CHARSET | QT_SHOW_SELECT_NUMBER))
    at sql/sql_lex.cc:4041
#6  0x00005555593efb50 in Query_block::print_query_block (this=0x7fff24120768, 
    thd=0x7fff24001050, str=0x7fffc83ee7e0, 
    query_type=(QT_TO_SYSTEM_CHARSET | QT_SHOW_SELECT_NUMBER))
    at sql/sql_lex.cc:3614
#7  0x00005555593efa3d in Query_block::print (this=0x7fff24120768, thd=0x7fff24001050, 
    str=0x7fffc83ee7e0, query_type=(QT_TO_SYSTEM_CHARSET | QT_SHOW_SELECT_NUMBER))
    at sql/sql_lex.cc:3598
#8  0x00005555593ee556 in Query_expression::print (this=0x7fff24120670, thd=0x7fff24001050, 
    str=0x7fffc83ee7e0, query_type=(QT_TO_SYSTEM_CHARSET | QT_SHOW_SELECT_NUMBER))
    at sql/sql_lex.cc:3232
#9  0x0000555559a89c2c in print_query_for_explain (query_thd=0x7fff24001050, 
    unit=0x7fff24120670, str=0x7fffc83ee7e0)
    at sql/opt_explain.cc:2288
#10 0x0000555559a10b11 in PrintQueryPlan[abi:cxx11](THD*, THD const*, Query_expression*) (
    ethd=0x7fff24001050, query_thd=0x7fff24001050, unit=0x7fff24120670)
    at sql/join_optimizer/explain_access_path.cc:1894
#11 0x0000555559a8985a in ExplainIterator (ethd=0x7fff24001050, query_thd=0x7fff24001050, 
    unit=0x7fff24120670) at sql/opt_explain.cc:2205
#12 0x0000555559a89e91 in explain_query (explain_thd=0x7fff24001050, query_thd=0x7fff24001050, 
    unit=0x7fff24120670) at sql/opt_explain.cc:2359
#13 0x000055555955cd46 in Sql_cmd_dml::execute_inner (this=0x7fff24165630, thd=0x7fff24001050)

二、问题调查过程

对执行该SQL的优化过程进行调查,发现该SQL的SQL变换情况如下:

trim(ref_15.c_ogj)执行完find_order_in_list后,Item_func_trimargs[0]->m_ref_item[0]变为0<>0 as c_lrcm63eani,而非0<>0 as c_ogj,这是因为c_lrcm63eanic_ogj名字相同,在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被置为空,因为此时item->decrement_ref_count()ref_count()为0,继续执行Item::clean_up_after_removal

EXPLAIN FORMAT=TREE 
select  
    trim(ref_15.c_ogj),
    0<>0 as c_lrcm63eani
  from 
    (select 
    0<>0 as c_ogj
  from 
   sequence_table(2) t1
  where 0<>0
  order by c_ogj asc) as ref_15;

查看函数调用过程发现,Query_block在prepare时执行了delete_unused_merged_columns,相关函数调用过程如下:

-- 函数调用过程: Query_block::prepare -> Query_block::apply_local_transforms -> Query_block::delete_unused_merged_columns

bool find_order_in_list() {
  if (select_item != not_found_item) {
      if ((*order->item)->real_item() != (*select_item)->real_item()) {
        Item::Cleanup_after_removal_context ctx(
            thd->lex->current_query_block());

        (*order->item)
            ->walk(&Item::clean_up_after_removal, walk_options,  ==>Item_func_ne执行了2次,也执行了2次decrement_ref_count()
                   pointer_cast<uchar *>(&ctx));
      }
  }
}

bool Query_block::apply_local_transforms(THD *thd, bool prune) {
  DBUG_TRACE;

  assert(first_execution);
  -- 这个函数把((Item_func *)&fields[0][0])->args[0]->m_ref_item[0]给删了
  if (derived_table_count) delete_unused_merged_columns(&m_table_nest);
}

void Query_block::delete_unused_merged_columns(
    mem_root_deque<Table_ref *> *tables) {
  DBUG_TRACE;

  for (Table_ref *tl : *tables) {
    if (tl->nested_join == nullptr) continue;
    if (tl->is_merged()) {
      for (Field_translator *transl = tl->field_translation;
           transl < tl->field_translation_end; transl++) {
        Item *const item = transl->item;
        // Decrement the ref count as its no more used in
        // select list.
        if (item->decrement_ref_count()) continue; -- 因为执行完decrement_ref_count()以后返回的m_ref_count=0因此不会跳出这个循环

        // Cleanup the item since its not referenced from
        // anywhere.
        assert(item->fixed);
        Item::Cleanup_after_removal_context ctx(this);
        item->walk(&Item::clean_up_after_removal, walk_options,
                   pointer_cast<uchar *>(&ctx));
        transl->item = nullptr; -- 这个地方把Item_view_ref引用的Item_func_ne对象置为空了,即把trim函数参数的c_lrcm63eani列删除了
      }
    }
    delete_unused_merged_columns(&tl->nested_join->m_tables);
  }
}

三、解决方案

经分析,问题源于多执行了一次Item::clean_up_after_removal。在MySQL最新代码中,该BUG已被修复,相关修复代码如下:

相关commit ID号为: 2171a1260e2cdbbd379646be8ff6413a92fd48f4

-- 相关修复代码如下:
@@ -7575,7 +7865,6 @@ bool Item::clean_up_after_removal(uchar *arg) {

   if (reference_count() > 1) {
     (void)decrement_ref_count();
    +    ctx->stop_at(this);
   }
   return false;
 }

修改后查看函数堆栈信息:

#0  Item::clean_up_after_removal (this=0x2, 
    arg=0x41 <error: Cannot access memory at address 0x41>)
    at sql/item.cc:9236
#1  0x0000555558fea5a8 in Item::walk (this=0x7fff2c338db8, processor=&virtual table offset 864, 
    walk=7, arg=0x7fffc83ee4b0 "") at sql/item.h:2543
#2  0x00005555596cc6f2 in find_order_in_list (thd=0x7fff2c001070, ref_item_array=..., 
    tables=0x7fff2c330b90, order=0x7fff2c32eae8, fields=0x7fff2c32fb20, is_group_field=false, 
    is_window_order=false) at sql/sql_resolver.cc:4625
#3  0x00005555596cd0ae in setup_order (thd=0x7fff2c001070, ref_item_array=..., 
    tables=0x7fff2c330b90, fields=0x7fff2c32fb20, order=0x7fff2c32eae8)
    at sql/sql_resolver.cc:4811
#4  0x00005555596bf528 in Query_block::prepare (this=0x7fff2c32fae0, thd=0x7fff2c001070, 
    insert_field_list=0x0) at sql/sql_resolver.cc:400
#5  0x00005555597d035d in Query_expression::prepare (this=0x7fff2c32f9e8, thd=0x7fff2c001070, 
    sel_result=0x7fff2c33b2a8, insert_field_list=0x0, added_options=0, removed_options=0)
    at sql/sql_union.cc:758
#6  0x0000555559590772 in Table_ref::resolve_derived (this=0x7fff2c339790, thd=0x7fff2c001070, 
    apply_semijoin=true) at sql/sql_derived.cc:451
#7  0x00005555596c2a80 in Query_block::resolve_placeholder_tables (this=0x7fff2c333f08, 
    thd=0x7fff2c001070, apply_semijoin=true)
    at sql/sql_resolver.cc:1408
#8  0x00005555596bea62 in Query_block::prepare (this=0x7fff2c333f08, thd=0x7fff2c001070, 
    insert_field_list=0x0) at sql/sql_resolver.cc:265

对于0<>0 as c_lrcm63eani这个Item_func_ne对象,执行Item::clean_up_after_removal时,因reference_count() > 1会执行ctx->stop_at(this),下次执行该函数时,因ctx->is_stopped(this)直接返回,不再执行decrement_ref_count(),避免了transl->item = nullptr的执行。

bool find_order_in_list() {
  if (select_item != not_found_item) {
      if ((*order->item)->real_item() != (*select_item)->real_item()) {
        Item::Cleanup_after_removal_context ctx(
            thd->lex->current_query_block());

        (*order->item)
            ->walk(&Item::clean_up_after_removal, walk_options,  -- Item_func_ne执行了2次,而只执行了一次decrement_ref_count()
                   pointer_cast<uchar *>(&ctx));
      }
  }
}

void Query_block::delete_unused_merged_columns(
    mem_root_deque<Table_ref *> *tables) {
  DBUG_TRACE;

  for (Table_ref *tl : *tables) {
    if (tl->nested_join == nullptr) continue;
    if (tl->is_merged()) {
      for (Field_translator *transl = tl->field_translation;
           transl < tl->field_translation_end; transl++) {
        Item *const item = transl->item;
        // Decrement the ref count as its no more used in
        // select list.
        if (item->decrement_ref_count()) continue; 因为执行完decrement_ref_count()以后返回的m_ref_count=1因此不会继续执行后面的置空设置

        // Cleanup the item since its not referenced from
        // anywhere.
        assert(item->fixed);
        Item::Cleanup_after_removal_context ctx(this);
        item->walk(&Item::clean_up_after_removal, walk_options,
                   pointer_cast<uchar *>(&ctx));
        transl->item = nullptr; ==>这个地方不会运行到
      }
    }
    delete_unused_merged_columns(&tl->nested_join->m_tables);
  }
}

四、问题总结

通过分析可知,复杂SQL的Item变换和不需要的Item删除过程易引发Crash。分析此类Crash问题需熟悉代码运行流程,具备复杂问题解决经验,才能有效应对。

文章整理自互联网,只做测试使用。发布者:Lomu,转转请注明出处:https://www.it1024doc.com/12601.html

(0)
LomuLomu
上一篇 2025 年 6 月 20 日
下一篇 2025 年 6 月 20 日

相关推荐

  • 亲测有效clion激活码获取,图文版clion破解教程上手快

    声明:以下补丁与激活码均源自网络公开分享,仅限个人学习研究,禁止商业用途。若条件允许,请支持正版 JetBrains 全家桶! CLion 是 JetBrains 出品的跨平台 C/C++ IDE,支持 Windows、macOS 与 Linux。下文将手把手教你利用破解补丁完成永久激活,解锁全部高级特性。 无论你当前系统与版本号如何,均已为你整理完毕。 激…

    2025 年 11 月 6 日
    8100
  • 最新IDEA破解资源包及永久IDEA激活码文件

    免责声明:下文提及的破解补丁与激活码均来源于互联网,仅供个人学习研究,禁止商业用途。若条件允许,请支持正版! IntelliJ IDEA 是 JetBrains 出品的一款跨平台 IDE,支持 Windows、macOS 与 Linux。本教程将手把手教你利用破解补丁实现永久授权,解锁全部高级特性。无论你的系统或版本如何,下方步骤都已为你梳理完毕。 激活成功…

    IDEA破解教程 2025 年 12 月 3 日
    4900
  • 详细图解最新idea激活码免费申领和破解教程

    声明:以下破解补丁与激活码均源自网络公开分享,仅供个人学习研究,禁止商用。若条件允许,请支持正版!官方正版全家桶低至 32 元/年:https://panghu.hicxy.com/shop/?id=18 废话少说,先上图:IDEA 2025.2.1 已顺利激活至 2099 年,爽到飞起! 下面用图文方式手把手带你完成最新版 IntelliJ IDEA 的激…

    IDEA破解教程 2025 年 10 月 17 日
    12300
  • 加速分布式系统响应:远程调用性能优化之径

    文章标题: 加速分布式系统响应:远程调用性能优化之道 文章内容: 目录 一、远程调用直接案例剖析 二、并行调用 (一)核心观念 (二)并行调用的达成方式 基础思路 代码示例 关键要点阐释 线程池配置建议 三、数据异构 (一)场景回顾 (二)数据异构的长处与难题 (三)数据一致性优化 双写策略 消息队列异步更新 定期同步 (四)缓存管理策略优化 缓存预热 缓存…

    2025 年 7 月 21 日
    15000
  • CLion破解稳定性如何?亲测一年不掉激活!

    声明:以下激活补丁与序列号均源自网络,仅供学习交流,禁止商业用途;若条件允许,请支持正版! 先放一张成果图:CLion 2025.2.1 已顺利激活至 2099 年,稳! 下面用图文方式带你一步步搞定最新版 CLion。 前期清理 如果之前尝试过其他补丁失败,建议先卸载旧版本或清理残留配置,避免冲突。 放心删,项目代码不会受影响! Windows 用户 删除…

    2025 年 9 月 13 日
    13300

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信