MySQL 派生表查询导致 Crash 的根源分析与办理方案

打印 上一主题 下一主题

主题 1799|帖子 1799|积分 5397

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
MySQL 派生表查询导致 Crash 的根源分析与办理方案

一、问题发现

在之前的 MySQL 8.0.32 使用中,发现使用以下带有派生表的 SQL 会导致 MySQL Crash,以下的sequence_table(2)更换为任何非常量表都行:
仅 MySQL 8.0.32 版本有影响。
  1. EXPLAIN FORMAT=TREE
  2. select  
  3.     trim(ref_15.c_ogj),
  4.     0<>0 as c_lrcm63eani
  5.   from
  6.     (select
  7.     0<>0 as c_ogj
  8.   from
  9.    sequence_table(2) t1
  10.   where 0<>0
  11.   order by c_ogj asc) as ref_15;
复制代码
Crash 的堆栈如下:
  1. Thread 55 "mysqld" received signal SIGSEGV, Segmentation fault.
  2. Item_view_ref::used_tables (this=0x7fff2418f410)
  3.     at sql/item.h:6670
  4. 6670            table_map inner_map = ref_item()->used_tables(); ==> ref_item()为空指针,因此crash了
  5. (gdb) bt
  6. #0  Item_view_ref::used_tables (this=0x7fff2418f410)
  7.     at sql/item.h:6670
  8. #1  0x0000555558e978d1 in Item::const_item (this=0x7fff2418f410)
  9.     at sql/item.h:2342
  10. #2  0x0000555558ecc765 in Item_ref::print (this=0x7fff2418f410, thd=0x7fff24001050,
  11.     str=0x7fffc83ee7e0, query_type=(QT_TO_SYSTEM_CHARSET | QT_SHOW_SELECT_NUMBER))
  12.     at sql/item.cc:9993
  13. #3  0x000055555903b839 in Item_func_trim::print (this=0x7fff24120d20, thd=0x7fff24001050,
  14.     str=0x7fffc83ee7e0, query_type=(QT_TO_SYSTEM_CHARSET | QT_SHOW_SELECT_NUMBER))
  15.     at sql/item_strfunc.cc:3244
  16. #4  0x0000555558ea7fc5 in Item::print_item_w_name (this=0x7fff24120d20, thd=0x7fff24001050,
  17.     str=0x7fffc83ee7e0, query_type=(QT_TO_SYSTEM_CHARSET | QT_SHOW_SELECT_NUMBER))
  18.     at sql/item.cc:727
  19. #5  0x00005555593f18c0 in Query_block::print_item_list (this=0x7fff24120768, thd=0x7fff24001050,
  20.     str=0x7fffc83ee7e0, query_type=(QT_TO_SYSTEM_CHARSET | QT_SHOW_SELECT_NUMBER))
  21.     at sql/sql_lex.cc:4041
  22. #6  0x00005555593efb50 in Query_block::print_query_block (this=0x7fff24120768,
  23.     thd=0x7fff24001050, str=0x7fffc83ee7e0,
  24.     query_type=(QT_TO_SYSTEM_CHARSET | QT_SHOW_SELECT_NUMBER))
  25.     at sql/sql_lex.cc:3614
  26. #7  0x00005555593efa3d in Query_block::print (this=0x7fff24120768, thd=0x7fff24001050,
  27.     str=0x7fffc83ee7e0, query_type=(QT_TO_SYSTEM_CHARSET | QT_SHOW_SELECT_NUMBER))
  28.     at sql/sql_lex.cc:3598
  29. #8  0x00005555593ee556 in Query_expression::print (this=0x7fff24120670, thd=0x7fff24001050,
  30.     str=0x7fffc83ee7e0, query_type=(QT_TO_SYSTEM_CHARSET | QT_SHOW_SELECT_NUMBER))
  31.     at sql/sql_lex.cc:3232
  32. #9  0x0000555559a89c2c in print_query_for_explain (query_thd=0x7fff24001050,
  33.     unit=0x7fff24120670, str=0x7fffc83ee7e0)
  34.     at sql/opt_explain.cc:2288
  35. #10 0x0000555559a10b11 in PrintQueryPlan[abi:cxx11](THD*, THD const*, Query_expression*) (
  36.     ethd=0x7fff24001050, query_thd=0x7fff24001050, unit=0x7fff24120670)
  37.     at sql/join_optimizer/explain_access_path.cc:1894
  38. #11 0x0000555559a8985a in ExplainIterator (ethd=0x7fff24001050, query_thd=0x7fff24001050,
  39.     unit=0x7fff24120670) at sql/opt_explain.cc:2205
  40. #12 0x0000555559a89e91 in explain_query (explain_thd=0x7fff24001050, query_thd=0x7fff24001050,
  41.     unit=0x7fff24120670) at sql/opt_explain.cc:2359
  42. #13 0x000055555955cd46 in Sql_cmd_dml::execute_inner (this=0x7fff24165630, thd=0x7fff24001050)
复制代码
二、问题调查过程

调查执行 SQL 的 optimize 的过程,分析发现该 SQL 的 SQL 变换情况如下:
以下的 trim(ref_15.c_ogj) 执行完 find_order_in_list 后,Item_func_trim的args[0]->m_ref_item[0] 等于00 as c_lrcm63eani,而不是00 as c_ogj,这是因为c_lrcm63eani和c_ogj的名字都一样,都是00,在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函数却把00 as c_lrcm63eani的Item置为空了,因为这个时候c_lrcm63eani的item->decrement_ref_count()以后ref_count()为0因此继承执行Item::clean_up_after_removal了。
  1. EXPLAIN FORMAT=TREE
  2. select  
  3.     trim(ref_15.c_ogj),
  4.     0<>0 as c_lrcm63eani
  5.   from
  6.     (select
  7.     0<>0 as c_ogj
  8.   from
  9.    sequence_table(2) t1
  10.   where 0<>0
  11.   order by c_ogj asc) as ref_15;
复制代码
查看函数调用过程发现 Query_block 在 prepare 的时候执行了 delete_unused_merged_columns,
  1. -- 函数调用过程: Query_block::prepare -> Query_block::apply_local_transforms -> Query_block::delete_unused_merged_columns
  2. bool find_order_in_list() {
  3.   if (select_item != not_found_item) {
  4.       if ((*order->item)->real_item() != (*select_item)->real_item()) {
  5.         Item::Cleanup_after_removal_context ctx(
  6.             thd->lex->current_query_block());
  7.         (*order->item)
  8.             ->walk(&Item::clean_up_after_removal, walk_options,  ==>Item_func_ne执行了2次,也执行了2次decrement_ref_count()
  9.                    pointer_cast<uchar *>(&ctx));
  10.       }
  11.   }
  12. }
  13. bool Query_block::apply_local_transforms(THD *thd, bool prune) {
  14.   DBUG_TRACE;
  15.   assert(first_execution);
  16.   -- 这个函数把((Item_func *)&fields[0][0])->args[0]->m_ref_item[0]给删了
  17.   if (derived_table_count) delete_unused_merged_columns(&m_table_nest);
  18. }
  19. void Query_block::delete_unused_merged_columns(
  20.     mem_root_deque<Table_ref *> *tables) {
  21.   DBUG_TRACE;
  22.   for (Table_ref *tl : *tables) {
  23.     if (tl->nested_join == nullptr) continue;
  24.     if (tl->is_merged()) {
  25.       for (Field_translator *transl = tl->field_translation;
  26.            transl < tl->field_translation_end; transl++) {
  27.         Item *const item = transl->item;
  28.         // Decrement the ref count as its no more used in
  29.         // select list.
  30.         if (item->decrement_ref_count()) continue; -- 因为执行完decrement_ref_count()以后返回的m_ref_count=0因此不会跳出这个循环
  31.         // Cleanup the item since its not referenced from
  32.         // anywhere.
  33.         assert(item->fixed);
  34.         Item::Cleanup_after_removal_context ctx(this);
  35.         item->walk(&Item::clean_up_after_removal, walk_options,
  36.                    pointer_cast<uchar *>(&ctx));
  37.         transl->item = nullptr; -- 这个地方把Item_view_ref引用的Item_func_ne对象置为空了,即把trim函数参数的c_lrcm63eani列删除了
  38.       }
  39.     }
  40.     delete_unused_merged_columns(&tl->nested_join->m_tables);
  41.   }
  42. }
复制代码
三、办理方案

通过上面的分析,我们可以发现问题在于多执行了一次Item::clean_up_after_removal,随后在 MySQL 最新代码尝试执行以上 SQL 发现该 BUG 已经被修复,找到相干修复代码,可以发现以下修复代码。
相干commit ID号为: 2171a1260e2cdbbd379646be8ff6413a92fd48f4
  1. -- 相关修复代码如下:
  2. @@ -7575,7 +7865,6 @@ bool Item::clean_up_after_removal(uchar *arg) {
  3.    if (reference_count() > 1) {
  4.      (void)decrement_ref_count();
  5. +    ctx->stop_at(this);
  6.    }
  7.    return false;
  8. }
复制代码
修改完查看一下这个函数的堆栈信息:
  1. #0  Item::clean_up_after_removal (this=0x2,
  2.     arg=0x41 <error: Cannot access memory at address 0x41>)
  3.     at sql/item.cc:9236
  4. #1  0x0000555558fea5a8 in Item::walk (this=0x7fff2c338db8, processor=&virtual table offset 864,
  5.     walk=7, arg=0x7fffc83ee4b0 "") at sql/item.h:2543
  6. #2  0x00005555596cc6f2 in find_order_in_list (thd=0x7fff2c001070, ref_item_array=...,
  7.     tables=0x7fff2c330b90, order=0x7fff2c32eae8, fields=0x7fff2c32fb20, is_group_field=false,
  8.     is_window_order=false) at sql/sql_resolver.cc:4625
  9. #3  0x00005555596cd0ae in setup_order (thd=0x7fff2c001070, ref_item_array=...,
  10.     tables=0x7fff2c330b90, fields=0x7fff2c32fb20, order=0x7fff2c32eae8)
  11.     at sql/sql_resolver.cc:4811
  12. #4  0x00005555596bf528 in Query_block::prepare (this=0x7fff2c32fae0, thd=0x7fff2c001070,
  13.     insert_field_list=0x0) at sql/sql_resolver.cc:400
  14. #5  0x00005555597d035d in Query_expression::prepare (this=0x7fff2c32f9e8, thd=0x7fff2c001070,
  15.     sel_result=0x7fff2c33b2a8, insert_field_list=0x0, added_options=0, removed_options=0)
  16.     at sql/sql_union.cc:758
  17. #6  0x0000555559590772 in Table_ref::resolve_derived (this=0x7fff2c339790, thd=0x7fff2c001070,
  18.     apply_semijoin=true) at sql/sql_derived.cc:451
  19. #7  0x00005555596c2a80 in Query_block::resolve_placeholder_tables (this=0x7fff2c333f08,
  20.     thd=0x7fff2c001070, apply_semijoin=true)
  21.     at sql/sql_resolver.cc:1408
  22. #8  0x00005555596bea62 in Query_block::prepare (this=0x7fff2c333f08, thd=0x7fff2c001070,
  23.     insert_field_list=0x0) at sql/sql_resolver.cc:265
复制代码
对于00 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。
  1. bool find_order_in_list() {
  2.   if (select_item != not_found_item) {
  3.       if ((*order->item)->real_item() != (*select_item)->real_item()) {
  4.         Item::Cleanup_after_removal_context ctx(
  5.             thd->lex->current_query_block());
  6.         (*order->item)
  7.             ->walk(&Item::clean_up_after_removal, walk_options,  -- Item_func_ne执行了2次,而只执行了一次decrement_ref_count()
  8.                    pointer_cast<uchar *>(&ctx));
  9.       }
  10.   }
  11. }
  12. void Query_block::delete_unused_merged_columns(
  13.     mem_root_deque<Table_ref *> *tables) {
  14.   DBUG_TRACE;
  15.   for (Table_ref *tl : *tables) {
  16.     if (tl->nested_join == nullptr) continue;
  17.     if (tl->is_merged()) {
  18.       for (Field_translator *transl = tl->field_translation;
  19.            transl < tl->field_translation_end; transl++) {
  20.         Item *const item = transl->item;
  21.         // Decrement the ref count as its no more used in
  22.         // select list.
  23.         if (item->decrement_ref_count()) continue; 因为执行完decrement_ref_count()以后返回的m_ref_count=1因此不会继续执行后面的置空设置
  24.         // Cleanup the item since its not referenced from
  25.         // anywhere.
  26.         assert(item->fixed);
  27.         Item::Cleanup_after_removal_context ctx(this);
  28.         item->walk(&Item::clean_up_after_removal, walk_options,
  29.                    pointer_cast<uchar *>(&ctx));
  30.         transl->item = nullptr; ==>这个地方不会运行到
  31.       }
  32.     }
  33.     delete_unused_merged_columns(&tl->nested_join->m_tables);
  34.   }
  35. }
复制代码
四、问题总结

通过以上分析我们可以发现,对于复杂的 SQL 会执行复杂的 Item 变换和删除不必要的 Item,但是正是由于如许才更容易导致 Crash 的出现。分析雷同如许的 Crash 问题的时候,因为涉及代码量大,代码逻辑复杂往往很难找到相干修复代码,因此必要对代码运行流程比较熟悉,同时要有相干复杂问题办理的经验才气更好的应对这类问题。

Enjoy GreatSQL
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

风雨同行

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表