MySQL 存储过程运行的内存管理

2023-02-23 10:20:38 浏览数 (1)

* GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源。

  • 一、讲解例子
  • 二、function内存管理过程讲解
  • 三、function内存管理过程图例
  • 四、总结

一、讲解例子

MySQL的存储过程在运行过程中的内存管理跟table等运行时候是不一样的,它涉及多层内存管理,在开发时候如果不注意内存管理很容易造成内存泄露。接下来我用以下function的例子来说明,procedure的也是类似的,只是少了return result的过程。

function语句示例:

代码语言:javascript复制
set global log_bin_trust_function_creators=1;
CREATE FUNCTION f1 (a VARCHAR(32)) RETURN VARCHAR(32) no sql
is result VARCHAR(32);
BEGIN
  result := CONCAT(a,'y');
  RETURN(result);
END;

二、function内存管理过程讲解

1、在debug模式下查看function的code。可以看到一共分了3个步骤实现。

代码语言:javascript复制
mysql> show function code f1;
 ----- ------------------------------ 
| Pos | Instruction                  |
 ----- ------------------------------ 
|   0 | set result@1 NULL            |
|   1 | set result@1 concat(a@0,'y') |
|   2 | freturn 15 result@1          |
 ----- ------------------------------ 
3 rows in set (0.01 sec)

2、return result内存管理

代码语言:javascript复制
mysql> select f1('mysql');

gdb跟踪return result的代码:

代码语言:javascript复制
#0  sp_head::create_result_field (this=0x7fff400fd258, thd=0x7fffe83a9ca0, field_max_length=93825047739703, 
    field_name_or_null=0x7fffe83a9c90 "300234:350377177", table=0x7fff400fd258)
    at /home/wuyy/greatdb/gitmerge/percona-server/sql/sp_head.cc:1972
#1  0x0000555558b52165 in Item_func_sp::init_result_field (this=0x7fff400fbe50, thd=0x7fff40001060)
    at /home/wuyy/greatdb/gitmerge/percona-server/sql/item_func.cc:8768
#2  0x0000555558b53209 in Item_func_sp::fix_fields (this=0x7fff400fbe50, thd=0x7fff40001060, ref=0x7fff400fc2d0)
    at /home/wuyy/greatdb/gitmerge/percona-server/sql/item_func.cc:9021
#3  0x0000555558e37345 in setup_fields (thd=0x7fff40001060, want_privilege=1, allow_sum_func=true, split_sum_funcs=true, 
    column_update=false, typed_items=0x0, fields=0x7fff400fadd0, ref_item_array=..., is_ora_update_set=false)
    at /home/wuyy/greatdb/gitmerge/percona-server/sql/sql_base.cc:9244
#4  0x0000555558f9d023 in Query_block::prepare (this=0x7fff400fadc8, thd=0x7fff40001060, insert_field_list=0x0)
    at /home/wuyy/greatdb/gitmerge/percona-server/sql/sql_resolver.cc:275
#5  0x0000555558fcba8b in Sql_cmd_select::prepare_inner (this=0x7fff400fc4d0, thd=0x7fff40001060)
    at /home/wuyy/greatdb/gitmerge/percona-server/sql/sql_select.cc:470
#6  0x0000555558fcb52c in Sql_cmd_dml::prepare (this=0x7fff400fc4d0, thd=0x7fff40001060)
    at /home/wuyy/greatdb/gitmerge/percona-server/sql/sql_select.cc:392
#7  0x0000555558fcbd43 in Sql_cmd_dml::execute (this=0x7fff400fc4d0, thd=0x7fff40001060)
    at /home/wuyy/greatdb/gitmerge/percona-server/sql/sql_select.cc:525
#8  0x0000555558f42e77 in mysql_execute_command (thd=0x7fff40001060, first_level=true)
    at /home/wuyy/greatdb/gitmerge/percona-server/sql/sql_parse.cc:4784

在sp_head::create_result_field看到该result field是建立在thd->mem_root的,也就是一开始thd的内存里面。
table->record[0] = thd->mem_root->ArrayAlloc<uchar>(m_return_field_def.pack_length()   1);

3、执行function的内存管理

执行function的内存管理相关代码,sp_head::execute_function函数:

代码语言:javascript复制
1、在sp_head::execute_function有如下代码用来创建运行内存:
  thd->swap_query_arena(call_arena, &backup_arena); 建立新的内存块call_arena用来存放funciton运行产生的数据。
  sp_rcontext *func_runtime_ctx =
      sp_rcontext::create(thd, m_root_parsing_ctx, return_value_fld); sp_rcontext::create运行的内存在call_arena
  thd->swap_query_arena(backup_arena, &call_arena); 内存切换回一开始thd的内存。
2、接着是每个instr步骤的内存管理:
thd->swap_query_arena(call_arena, &backup_arena); 内存切换到call_arena
err_status = execute(thd, true);
thd->swap_query_arena(backup_arena, &call_arena); 内存切换回一开始thd的内存。
3、以上第2步的execute(thd, true)内存管理
thd->swap_query_arena(execute_arena, &backup_arena);建立新的内存块execute_arena用来存放funciton运行每一个步骤产生的数据
do{
    i = get_instr(ip);
    err_status = i->execute(thd, &ip);
    free_root(&execute_mem_root, MYF(0));
}while        每个步骤的内存块都在execute_arena上,每个sp_instr都是单独管理内存,该sp_instr执行完毕立即释放内存。因此这个内存块是临时的,所有希望    永久存放的数据都不应该存放在这个内存上。
thd->swap_query_arena(backup_arena, &execute_arena); 内存切换回call_arena

4、内存释放

以上产生的内存块call_arena释放的代码

代码语言:javascript复制
sp_head::execute_function代码结束时候释放call_arena:
err_with_cleanup:
  ::destroy(func_runtime_ctx);
  call_arena.free_items();
  free_root(&call_mem_root, MYF(0)); 这里释放call_arena内存块

三、function内存管理过程图例

上面的过程总结如图所示,每个阶段内存产生的数据包括item和field都应该使用对应的arena,即thd->swap_query_arena来管理内存,这样才不会造成数据管理错乱,数据丢失等问题。procedure的内存管理也是一样的,只是少了return result相关的处理过程。

代码语言:javascript复制
 ------------------------------------------ 
| thd(return result)                       |
            ------------------------------- 
|           |call_arena(create)            |
            |    -------------------------- 
|           |    |execute_arena(sp_instr)  |
|           |    |                         |
 ------------------------------------------ 

四、总结

MySQL存储过程的内存管理过程很精妙,代码中会出现多次的thd->swap_query_arena来进行内存切换,必须严格区分哪些数据应该放在对应的那个arena,才能正确管理sp数据。

Enjoy GreatSQL :)

《深入浅出MGR》视频课程

戳此小程序即可直达B站

https://www.bilibili.com/medialist/play/1363850082?business=space_collection&business_id=343928&desc=0


文章推荐:

  • MySQL 8.0有趣的新特性:CHECK约束
  • MySQL 启停过程了解一二
  • 技术分享 | 微服务架构的数据库为什么喜欢分库分表?
  • MySQL内存管理机制浅析
  • 技术分析 | 浅析MySQL与ElasticSearch的组合使用

关于 GreatSQL

GreatSQL是由万里数据库维护的MySQL分支,专注于提升MGR可靠性及性能,支持InnoDB并行查询特性,是适用于金融级应用的MySQL分支版本。

Gitee: https://gitee.com/GreatSQL/GreatSQL

GitHub: https://github.com/GreatSQL/GreatSQL

Bilibili:

https://space.bilibili.com/1363850082/video

0 人点赞