新聞中心
一、MySQL的sp運行sql語句兩個步驟介紹
MySQL的sp運行SQL語句需要執(zhí)行2個步驟:prepare和execute。第一次執(zhí)行的時候先執(zhí)行prepare,進行相關(guān)語句parse、itemize、fix_fields等操作,然后才開始進行execute操作。等第二次再執(zhí)行該sp的時候就直接運行execute而不需要再次進行重復(fù)的prepare操作,這樣可以節(jié)省sp運行時候重復(fù)prepare的開銷。但是,對于表操作就有一個問題產(chǎn)生,那就是如果執(zhí)行第二遍的時候表的結(jié)構(gòu)發(fā)生改變了,那么不進行reprepare而直接execute是會發(fā)生錯誤的。因此,本文章的目的在于尋找sp多次運行時候如何確認表版本更新并進行正確的操作。

成都創(chuàng)新互聯(lián)公司專注于企業(yè)營銷型網(wǎng)站、網(wǎng)站重做改版、達拉特網(wǎng)站定制設(shè)計、自適應(yīng)品牌網(wǎng)站建設(shè)、H5開發(fā)、商城網(wǎng)站開發(fā)、集團公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)公司、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計等建站業(yè)務(wù),價格優(yōu)惠性價比高,為達拉特等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。
先看下面的例子:
CREATE TABLE t1 (a INT, b VARCHAR(10));
INSERT INTO t1 VALUES (1,'11');
INSERT INTO t1 VALUES (2,'21');
MySQL> select * from t1;
+------+------+
| a | b |
+------+------+
| 1 | 11 |
| 2 | 21 |
+------+------+
2 rows in set (0.01 sec)
DELIMITER $$
CREATE PROCEDURE p1()
BEGIN
update t1 set b='aa' where a=1;
END $$
DELIMITER ;
MySQL> call p1; #這里第一次操作,因此會先執(zhí)行update這句SQL的prepare再進行execute。
Query OK, 1 row affected (0.05 sec)
MySQL> select * from t1;
+------+------+
| a | b |
+------+------+
| 1 | aa |
| 2 | 21 |
+------+------+
2 rows in set (0.01 sec)
MySQL> call p1; #這里第二次操作,直接執(zhí)行update這句SQL的execute。
Query OK, 0 rows affected (13.78 sec)
#接著我們執(zhí)行表結(jié)構(gòu)的更新。
MySQL> alter table t1 add i int;
Query OK, 0 rows affected (0.41 sec)
Records: 0 Duplicates: 0 Warnings: 0
#然后再次執(zhí)行sp,就會發(fā)現(xiàn)這次執(zhí)行了這句SQL的prepare再進行execute。
MySQL> call p1;
Query OK, 0 rows affected (34.24 sec)
二、代碼跟蹤
現(xiàn)在跟蹤一下這個sp看看上面在哪里check表版本并且能正確執(zhí)行reprepare的。
#首先看一下這個sp涉及的instr。
MySQL> show procedure code p1;
+-----+---------------------------------------+
| Pos | Instruction |
+-----+---------------------------------------+
| 0 | stmt "update t1 set b='aa' where a=1" |
+-----+---------------------------------------+
1 row in set (0.01 sec)
可以看到這個sp只涉及了sp_instr_stmt::execute的運行,因此跟蹤一下代碼找到sp_lex_instr::validate_lex_and_execute_core,可以發(fā)現(xiàn)這個函數(shù)里面有一個無限while循環(huán),如果發(fā)現(xiàn)is_invalid()的話就重新執(zhí)行parse動作,如果!is_invalid()就直接執(zhí)行exec_core,這個跟上面的運行步驟就對的上了。但是表結(jié)構(gòu)變更后在哪里被判定為rc=true的呢,那就從reset_lex_and_exec_core這個函數(shù)繼續(xù)跟蹤下去。
bool sp_lex_instr::validate_lex_and_execute_core(THD *thd, uint *nextp,
bool open_tables) {
while (true) {
if (is_invalid() || (m_lex->has_udf() && !m_first_execution)) {
LEX *lex = parse_expr(thd, thd->sp_runtime_ctx->sp);
}
bool rc = reset_lex_and_exec_core(thd, nextp, open_tables);
if (!rc) return false;
thd->clear_error();
invalidate();
}
}
#跟蹤代碼發(fā)現(xiàn)有一個check_and_update_table_version函數(shù)是用來check表版本是否一致的
#打印堆??匆幌麓a調(diào)用過程:
Thread 51 "mysqld" hit Breakpoint 6, check_and_update_table_version (thd=0x7fff70001060, tables=0x7fff702c4e20,
table_share=0x7fff70297640) at /mysql/sql/sql_base.cc:3722
3722 if (!tables->is_table_ref_id_equal(table_share)) {
(gdb) bt
#0 check_and_update_table_version (thd=0x7fff70001060, tables=0x7fff702c4e20, table_share=0x7fff70297640)
at /mysql/sql/sql_base.cc:3722
#1 0x0000000003340f71 in open_and_process_table (thd=0x7fff70001060, lex=0x7fff702c2650, tables=0x7fff702c4e20,
counter=0x7fff702c26a8, prelocking_strategy=0x7fffec2e7478, has_prelocking_list=false, ot_ctx=0x7fffec2e7368)
at /MySQL/sql/sql_base.cc:5223
#2 0x000000000333f788 in open_tables (thd=0x7fff70001060, start=0x7fffec2e7488, counter=0x7fff702c26a8, flags=0,
prelocking_strategy=0x7fffec2e7478) at /mysql/sql/sql_base.cc:5968
#3 0x0000000003343c96 in open_tables_for_query (thd=0x7fff70001060, tables=0x7fff702c4e20, flags=0)
at /MySQL/sql/sql_base.cc:6958
#4 0x0000000003514334 in Sql_cmd_dml::execute (this=0x7fff702c6138, thd=0x7fff70001060)
at /MySQL/sql/sql_select.cc:543
#5 0x0000000003475097 in mysql_execute_command (thd=0x7fff70001060, first_level=false)
at /MySQL/sql/sql_parse.cc:3832
#6 0x00000000033075c6 in sp_instr_stmt::exec_core (this=0x7fff70249a80, thd=0x7fff70001060, nextp=0x7fffec2eac38)
at /MySQL/sql/sp_instr.cc:1008
#7 0x00000000033052ed in sp_lex_instr::reset_lex_and_exec_core (this=0x7fff70249a80, thd=0x7fff70001060,
nextp=0x7fffec2eac38, open_tables=false) at /mysql/sql/sp_instr.cc:457
#8 0x00000000033060a4 in sp_lex_instr::validate_lex_and_execute_core (this=0x7fff70249a80, thd=0x7fff70001060,
nextp=0x7fffec2eac38, open_tables=false) at /mysql/sql/sp_instr.cc:741
#9 0x0000000003306748 in sp_instr_stmt::execute (this=0x7fff70249a80, thd=0x7fff70001060, nextp=0x7fffec2eac38)
at /MySQL/sql/sp_instr.cc:925
#10 0x00000000032f4d74 in sp_head::execute (this=0x7fff7018e7a0, thd=0x7fff70001060, merge_da_on_success=true)
at /MySQL/sql/sp_head.cc:2272
#11 0x00000000032f80e1 in sp_head::execute_procedure (this=0x7fff7018e7a0, thd=0x7fff70001060, args=0x0)
at /MySQL/sql/sp_head.cc:2977
#可以發(fā)現(xiàn)open_tables函數(shù)調(diào)用了這個函數(shù),這個函數(shù)調(diào)用了ask_to_reprepare,
#在sp運行中這個ask_to_reprepare返回的是true。因此這里就解開了之前的問題,
#為何表版本更新了會return true然后重新進行parse操作。
static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables,
TABLE_SHARE *table_share) {
if (!tables->is_table_ref_id_equal(table_share)) {
/*
Version of the table share is different from the
previous execution of the prepared statement, and it is
unacceptable for this SQLCOM.
*/
if (ask_to_reprepare(thd)) return true;
/* Always maintain the latest version and type */
tables->set_table_ref_id(table_share);
}
return false;
}
三、知識應(yīng)用
如果想開發(fā)一種動態(tài)表類型,需要每次執(zhí)行sp都重新parse這個表,那就可以借用這個ask_to_reprepare函數(shù)來保證多次執(zhí)行sp的時候都會進行重新reprepare。
四、總結(jié)
在MySQL的sp操作中涉及表操作的sql語句一定會執(zhí)行check_and_update_table_version這個函數(shù),每次會根據(jù)這個函數(shù)的結(jié)果來確定要不要重新parse該sql語句,如果沒有版本改變就直接進行execute操作,如果有改變就重新parse,先prepare再execute,這樣可以保證每次執(zhí)行sp的SQL語句的時候表結(jié)構(gòu)一定是最新的。
當前題目:MySQL中Sp運行Check表版本更新流程解析
鏈接分享:http://www.dlmjj.cn/article/djcoccc.html


咨詢
建站咨詢
