当前位置: 首页 > 科技观察

MySQLsp运行Checktable的版本更新过程分析

时间:2023-03-14 11:48:41 科技观察

1.MySQLsp运行SQL语句的两个步骤简介MySQLsp运行SQL语句需要执行2个步骤:prepare和execute。第一次执行时,先执行prepare,执行相关语句parse、itemize、fix_fields等操作,然后再开始execute操作。第二次执行sp时,直接执行,不用再次重复prepare操作,这样可以节省sp运行时重复prepare的开销。但是表操作有一个问题,就是如果在第二遍执行的时候表的结构发生了变化,不重新准备直接执行就会出错。因此,本文的目的是了解在sp多次运行时,如何确认表版本的更新并进行正确的操作。先看下面的例子:CREATETABLEt1(aINT,bVARCHAR(10));INSERTINTOt1VALUES(1,'11');INSERTINTOt1VALUES(2,'21');MySQL>select*fromt1;+-----+-----+|一个|b|+-----+-----+|1|11||2|21|+------+-----+2rowsinset(0.01sec)DELIMITER$$CREATEPROCEDUREp1()BEGINupdatet1setb='aa'wherea=1;END$$DELIMITER;MySQL>调用p1;#这里是第一次操作,所以会先执行update的sqlprepare,然后再执行。查询正常,1行受影响(0.05秒)MySQL>select*fromt1;+-----+-----+|一个|b|+-----+------+|1|一个||2|21|+-----+-----+2组中的行(0.01秒)MySQL>callp1;#这里的第二个操作是直接执行update这条SQL的执行。查询成功,0行受影响(13.78秒)#然后我们更新表结构。MySQL>altertablet1addiint;QueryOK,0rowsaffected(0.41sec)Records:0Duplicates:0Warnings:0#然后再次执行sp,你会发现这条sqlprepare执行完了再执行。MySQL>callp1;QueryOK,0rowsaffected(34.24sec)2.代码追踪现在追踪这个sp,看看哪里可以正确执行checktableversion。#首先看一下这个sp中涉及到的instr。MySQL>showprocedurecodep1;+-----+------------------------------------+|位置|指令|+-----+--------------------------------------+|0|stmt"更新t1集b='aa'其中a=1"|+-----+------------------------------------+1rowinset(0.01sec)可以看到这个sp只涉及到sp_instr_stmt::execute的操作,所以按照代码找到sp_lex_instr::validate_lex_and_execute_core,即可发现这个函数里面有一个无限while循环,如果发现is_invalid(),那就重新执行parse动作,if!is_invalid()直接执行exec_core,和上面的操作步骤是对的。但是表结构改变后哪里判断为rc=true,则从reset_lex_and_exec_core函数继续跟踪。boolsp_lex_instr::validate_lex_and_execute_core(THD*thd,uint*nextp,boolopen_tables){while(true){if(is_invalid()||(m_lex->has_udf()&&!m_first_execution)){LEX*lex=parse_expr(thd,thd->sp_runtime_ctx->sp);}boolrc=reset_lex_and_exec_core(thd,nextp,open_tables);如果(!rc)返回false;thd->clear_error();无效();check_and_update_table_version函数用于检查表版本是否一致。#打印栈看代码调用过程:Thread51"mysqld"hitBreakpoint6,check_and_update_table_version(thd=0x7fff70001060,tables=0x7fff702c4e20,table_share=0x7fff70297640)at/sql/basesql.cc:37223722if(!tables->is_table_ref_id_equal(table_share)){(gdb)bt#0check_and_update_table_version(thd=0x7fff70001060,tables=0x7fff702c4e20,table_share=0x7fff70297640)在/mysql/sql/sql_base.cc:3722#10x0000000003340f71inopen_and_process0x01fff67(thd0,lex=0x7fff702c2650,tables=0x7fff702c4e20,counter=0x7fff702c26a8,prelocking_strategy=0x7fffec2e7478,has_prelocking_list=false,ot_ctx=0x7fffec2e7368)at/My??SQL/sql/sql_base.cc:5223#20x000000000333f788inopen_tables(thd=0x7fff70001060,start=0x7fffec2e7488,counter=0x7fff702c26a8,flags=0,prelocking_strategy=0x7fffec2e7478)在/mysql/sql/sql_base.cc:5968#30x0000000003343c96inopen_tables_for_query(thd=0x7fff70001060,表=0x7fff702c=e20)在/flags_sql_sql/sql_sql_20cc:6958#40x0000000003514334inSql_cmd_dml::execute(this=0x7fff702c6138,thd=0x7fff70001060)at/My??SQL/sql/sql_select.cc:543#50x0000000003475097inmysql_execute_command(thd=0x7fff70001060,first_level=false)at/My??SQL/sql/sql_parse.cc:3832#60x000000000000033075c6insp_instr_stmt::exec_core(exec_core(this=0x7fff70249a80,thd=0x7fff70001060,nextp=0x7fffec2eclement_ssp_00000:000:000:000:200;ec_core(this=0x7fff70249a80,thd=0x7fff70001060,nextp=0x7fffec2eac38,open_tables=false)at/mysql/sql/sp_instr.cc:457#80x00000000033060a4insp_lex_instr::validate_lex_and_execute_core(this=0x7fff70249a80,thd=0x7fff70001060,nextp=0x7fffec2eac38,open_tables=false)at/mysql/sql/sql/sp_instr.cc:741#90x000000000003306748在sp_instr_stmt::execute(this=0x7fff70249a80,thd在sp_head::execute(this=0x7fff7018e7a0,thd=0x7fff70001060,merge_da_on_success=true)at/mysql/sql/sql/sp_head.cc:2272#110x0000000000000000000000000000000000000000000000000000000000000000000000000007070eceplure::eceplure::eceplure::eceplure::ecepnef70=tisterefereute:7at/My??SQL/sql/sp_head.cc:2977#可以发现open_tables函数调用了这个函数,而这个函数调用了ask_to_reprepare,#在sp运行中,这个ask_to_reprepare返回true,这样就解决了前面的问题,#为什么如果表版本更新,则返回true和t然后再次执行解析操作。staticboolcheck_and_update_table_version(THD*thd,TABLE_LIST*tables,TABLE_SHARE*table_share){if(!tables->is_table_ref_id_equal(table_share)){/*tableshare的版本与之前执行的preparedstatement不同,是此SQLCOM不可接受。*/if(ask_to_reprepare(thd))返回真;/*始终保持最新的版本和类型*/tables->set_table_ref_id(table_share);}returnfalse;}动态表类型每次sp执行时都需要重新解析表,所以可以使用ask_to_reprepare函数来保证sp多次执行时都会进行reprepare。4、总结在MySQL的sp操作中,涉及到表操作的sql语句肯定会执行check_and_update_table_version函数。每次都会根据这个函数的结果来判断是否重新解析sql语句。如果没有版本变化,直接执行操作。如果有变化,就重新解析,先准备再执行,保证每次执行sp的sql语句时,表结构一定是最新的。