成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

MySQL 派生表查詢導(dǎo)致 Crash 的根源分析與解決方案

數(shù)據(jù)庫 MySQL
對于復(fù)雜的 SQL 會執(zhí)行復(fù)雜的 Item 變換和刪除不需要的 Item,但是正是由于這樣才更容易導(dǎo)致 Crash 的出現(xiàn)。分析類似這樣的 Crash 問題的時候,因為涉及代碼量大,代碼邏輯復(fù)雜往往很難找到相關(guān)修復(fù)代碼,因此需要對代碼運行流程比較熟悉,同時要有相關(guān)復(fù)雜問題解決的經(jīng)驗才能更好的應(yīng)對這類問題。?

一、問題發(fā)現(xiàn)

在之前的 MySQL 8.0.32 使用中,發(fā)現(xiàn)使用以下帶有派生表的 SQL 會導(dǎo)致 MySQL Crash,以下的sequence_table(2)替換為任何非常量表都行:

僅 MySQL 8.0.32 版本有影響。

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

Crash 的堆棧如下:

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)

二、問題調(diào)查過程

調(diào)查執(zhí)行 SQL 的 optimize 的過程,分析發(fā)現(xiàn)該 SQL 的 SQL 變換情況如下:

以下的 trim(ref_15.c_ogj) 執(zhí)行完 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函數(shù)里面由于名字一樣因此內(nèi)層字段被外層替代了。而后在Item::clean_up_after_removal執(zhí)行的時候,Item_func_ne即c_lrcm63eani因為出現(xiàn)了2次,因此執(zhí)行了2次decrement_ref_count(),然而在Query_block::delete_unused_merged_columns函數(shù)卻把0<>0 as c_lrcm63eani的Item置為空了,因為這個時候c_lrcm63eani的item->decrement_ref_count()以后ref_count()為0因此繼續(xù)執(zhí)行Item::clean_up_after_removal了。

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

查看函數(shù)調(diào)用過程發(fā)現(xiàn) Query_block 在 prepare 的時候執(zhí)行了 delete_unused_merged_columns,

-- 函數(shù)調(diào)用過程: 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執(zhí)行了2次,也執(zhí)行了2次decrement_ref_count()
                   pointer_cast<uchar *>(&ctx));
      }
  }
}

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

  assert(first_execution);
-- 這個函數(shù)把((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; -- 因為執(zhí)行完decrement_ref_count()以后返回的m_ref_count=0因此不會跳出這個循環(huán)

        // 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函數(shù)參數(shù)的c_lrcm63eani列刪除了
      }
    }
    delete_unused_merged_columns(&tl->nested_join->m_tables);
  }
}

三、解決方案

通過上面的分析,我們可以發(fā)現(xiàn)問題在于多執(zhí)行了一次Item::clean_up_after_removal,隨后在 MySQL 最新代碼嘗試執(zhí)行以上 SQL 發(fā)現(xiàn)該 BUG 已經(jīng)被修復(fù),找到相關(guān)修復(fù)代碼,可以發(fā)現(xiàn)以下修復(fù)代碼。

相關(guān)commit ID號為: 2171a1260e2cdbbd379646be8ff6413a92fd48f4

-- 相關(guān)修復(fù)代碼如下:
@@ -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;
 }

修改完查看一下這個函數(shù)的堆棧信息:

#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對象,執(zhí)行到Item::clean_up_after_removal的時候,因為reference_count() > 1因此會執(zhí)行新添加的ctx->stop_at(this),等到下一次再執(zhí)行到這個Item_func_ne的clean_up_after_removal()函數(shù)的時候,就會因為ctx->is_stopped(this)而直接返回,不再執(zhí)行一次decrement_ref_count(),從而避免了執(zhí)行后面的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執(zhí)行了2次,而只執(zhí)行了一次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; 因為執(zhí)行完decrement_ref_count()以后返回的m_ref_count=1因此不會繼續(xù)執(zhí)行后面的置空設(shè)置

        // 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);
  }
}

四、問題總結(jié)

通過以上分析我們可以發(fā)現(xiàn),對于復(fù)雜的 SQL 會執(zhí)行復(fù)雜的 Item 變換和刪除不需要的 Item,但是正是由于這樣才更容易導(dǎo)致 Crash 的出現(xiàn)。分析類似這樣的 Crash 問題的時候,因為涉及代碼量大,代碼邏輯復(fù)雜往往很難找到相關(guān)修復(fù)代碼,因此需要對代碼運行流程比較熟悉,同時要有相關(guān)復(fù)雜問題解決的經(jīng)驗才能更好的應(yīng)對這類問題。

責(zé)任編輯:武曉燕 來源: GreatSQL社區(qū)
相關(guān)推薦

2013-05-14 15:47:27

MySQL監(jiān)控錯誤日志

2010-04-28 11:48:13

Oracle MySQ

2016-02-24 11:25:43

DevOps運維

2025-02-04 12:05:10

2020-09-09 10:00:41

JavaScript前端瓶頸

2017-02-15 09:40:38

JavaScript分析解決

2024-06-12 12:59:16

2017-08-01 17:01:49

IOT梯聯(lián)網(wǎng)AWS

2012-01-04 11:05:24

2010-05-17 09:49:46

MySQL中文問題

2011-07-18 09:36:42

Mysql數(shù)據(jù)庫root@localh

2012-02-07 17:33:07

2011-08-11 18:38:05

Oracle回滾段

2010-08-06 13:40:06

DB2建立nickna

2010-07-30 13:21:21

2017-06-05 16:16:11

技術(shù)業(yè)務(wù)性

2010-05-28 19:39:28

MySQL 編碼轉(zhuǎn)換

2010-05-28 13:04:04

MySQL root

2010-06-07 09:22:21

MySQL+PHP亂碼

2010-01-06 09:37:06

華為交換機故障
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 欧美日韩精品 | 亚洲精品一区二区三区中文字幕 | 精品免费国产一区二区三区 | 91视在线国内在线播放酒店 | 老司机免费视频 | 国产主播第一页 | 成年人在线观看 | 日韩黄色av | 欧美极品少妇xxxxⅹ免费视频 | 国产精品毛片一区二区三区 | 国产丝袜一区二区三区免费视频 | 精品国产一区二区三区久久久蜜月 | 精品国产欧美一区二区三区成人 | 精品国产一区二区三区久久久蜜月 | 久久久久网站 | 国产精品国产a级 | 日本不卡一区 | 国产偷录视频叫床高潮对白 | 日韩精品久久久久久 | 天天摸天天看 | 可以在线看的黄色网址 | 久久最新精品视频 | 欧美成人精品一区二区男人看 | 精品国产一区二区三区日日嗨 | 亚洲成人自拍 | 久草免费视 | 午夜电影网| 久久久久久久一级 | 大象视频一区二区 | 久久久久久久久久久一区二区 | 午夜精品一区 | 国产精品久久av | 一区二区不卡视频 | 天天操操操操操 | 亚洲伊人精品酒店 | 国产一区欧美 | 中文精品视频 | 久久一级大片 | 9999国产精品欧美久久久久久 | 日本三级网址 | 91精品国产91久久久久游泳池 |