MySQL 派生表查询导致 Crash 的根源分析与解决方案
MySQL 派生表查询导致 Crash 的根源分析与解决方案
一、问题发现
在之前的 MySQL 8.0.32 使用中,发现使用以下带有派生表的 SQL 会导致 MySQL Crash,以下的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;
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)
二、问题调查过程
调查执行 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
了。
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 最新代码尝试执行以上 SQL 发现该 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)
,等到下一次再执行到这个Item_func_ne
的clean_up_after_removal()
函数的时候,就会因为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 问题的时候,因为涉及代码量大,代码逻辑复杂往往很难找到相关修复代码,因此需要对代码运行流程比较熟悉,同时要有相关复杂问题解决的经验才能更好的应对这类问题。
Enjoy GreatSQL :)
关于 GreatSQL
GreatSQL是适用于金融级应用的国内自主开源数据库,具备高性能、高可靠、高易用性、高安全等多个核心特性,可以作为MySQL或Percona Server的可选替换,用于线上生产环境,且完全免费并兼容MySQL或Percona Server。
相关链接: GreatSQL社区 Gitee GitHub Bilibili
GreatSQL社区:
社区有奖建议反馈: https://greatsql.cn/thread-54-1-1.html
社区博客有奖征稿详情: https://greatsql.cn/thread-100-1-1.html
(对文章有疑问或者有独到见解都可以去社区官网提出或分享哦~)
技术交流群:
微信&QQ群:
QQ群:533341697
微信群:添加GreatSQL社区助手(微信号:wanlidbc
)好友,待社区助手拉您进群。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
从18个月到几周:Amazon Transform 用Agentic AI重塑开发者的现代化迁移体验
企业系统“上云”已不是新命题,但对开发者而言,真正的挑战从来不是“是否迁移”,而是“如何在不出错、不返工的前提下,把老系统转得快、转得稳”。亚马逊云科技日前宣布Amazon Transform正式可用,这项基于Agentic AI的现代化迁移服务,正悄悄改变这一切。 Amazon Transform的最大价值,在于它不仅是一个“迁移工具”,更像是一位全天候在线、懂业务、精架构、会写代码的应用转型助手。 三类迁移痛点,一站解决 1. .NET 应用迁移:自动分析 + 代码重写 + Linux 适配验证 面对一堆依赖Windows环境、代码风格迥异的.NET Framework老应用,Amazon Transform支持用自然语言对话的方式输入迁移目标,自动识别项目背景、依赖项、私有包、平台约束,并将其迁移至Linux上的现代.NET平台。它还能自动执行单元测试、生成验证报告,并输出一份可交付文档,帮助团队快速评估迁移结果。其优势在于代码可重用率高,许可证节省最高可达 40%,并且在多个项目并行迁移时能够保持高度一致性。 2. Mainframe大型机系统:COBOL 分解 + Java ...
- 下一篇
玩转OurBMC第十九期:BMC用户管理模块
【栏目介绍:“玩转OurBMC”是OurBMC社区开创的知识分享类栏目,主要聚焦于社区和BMC全栈技术相关基础知识的分享,全方位涵盖了从理论原理到实践操作的知识传递。OurBMC社区将通过“玩转OurBMC”栏目,帮助开发者们深入了解到社区文化、理念及特色,增进开发者对BMC全栈技术的理解。 欢迎各位关注“玩转OurBMC”栏目,共同探索OurBMC社区的精彩世界。同时,我们诚挚地邀请各位开发者向“玩转OurBMC”栏目投稿,共同学习进步,将栏目打造成为汇聚智慧、激发创意的知识园地。】 用户管理是系统中运维管理的重要组成部分,主要涉及权限的划分和资源的分配。通过为不同用户设定不同的权限级别,可以确保系统的安全性和稳定性。例如,管理员拥有最高权限,可以进行系统配置、用户管理、网络配置等操作;而普通用户则仅可以查看系统的配置状态。 在OpenBMC软件栈中,默认支持本地用户和ldap远端用户两种模式。前者将用户信息保存在BMC文件系统中,通过shadow提供的工具(如useradd、usermod等)或glibc提供的函数接口(如getspnam_r、fgetpwent_r等)进行管理;后...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS7设置SWAP分区,小内存服务器的救世主
- Mario游戏-低调大师作品
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- 2048小游戏-低调大师作品
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题