GreatSQL社區原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。 GreatSQL是MySQL的國產分支版本,使用上與MySQL一致。 作者: wuyy 文章來源:GreatSQL社區原創 目錄 MySQL的sp運行SQL語句兩個步驟介紹 代碼跟蹤 知識應用 總結 一、MySQL的sp運 ...
- GreatSQL社區原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。
- GreatSQL是MySQL的國產分支版本,使用上與MySQL一致。
- 作者: wuyy
- 文章來源:GreatSQL社區原創
目錄
- MySQL的sp運行SQL語句兩個步驟介紹
- 代碼跟蹤
- 知識應用
- 總結
一、MySQL的sp運行sql語句兩個步驟介紹
MySQL的sp運行SQL語句需要執行2個步驟:prepare和execute。第一次執行的時候先執行prepare,進行相關語句parse、itemize、fix_fields等操作,然後才開始進行execute操作。等第二次再執行該sp的時候就直接運行execute而不需要再次進行重覆的prepare操作,這樣可以節省sp運行時候重覆prepare的開銷。但是,對於表操作就有一個問題產生,那就是如果執行第二遍的時候表的結構發生改變了,那麼不進行reprepare而直接execute是會發生錯誤的。因此,本文章的目的在於尋找sp多次運行時候如何確認表版本更新併進行正確的操作。
先看下麵的例子:
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; #這裡第一次操作,因此會先執行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; #這裡第二次操作,直接執行update這句SQL的execute。
Query OK, 0 rows affected (13.78 sec)
#接著我們執行表結構的更新。
MySQL> alter table t1 add i int;
Query OK, 0 rows affected (0.41 sec)
Records: 0 Duplicates: 0 Warnings: 0
#然後再次執行sp,就會發現這次執行了這句SQL的prepare再進行execute。
MySQL> call p1;
Query OK, 0 rows affected (34.24 sec)
二、代碼跟蹤
現在跟蹤一下這個sp看看上面在哪裡check表版本並且能正確執行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,可以發現這個函數裡面有一個無限while迴圈,如果發現is_invalid()的話就重新執行parse動作,如果!is_invalid()就直接執行exec_core,這個跟上面的運行步驟就對的上了。但是表結構變更後在哪裡被判定為rc=true的呢,那就從reset_lex_and_exec_core這個函數繼續跟蹤下去。
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();
}
}
#跟蹤代碼發現有一個check_and_update_table_version函數是用來check表版本是否一致的
#列印堆棧看一下代碼調用過程:
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
#可以發現open_tables函數調用了這個函數,這個函數調用了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;
}
三、知識應用
如果想開發一種動態表類型,需要每次執行sp都重新parse這個表,那就可以借用這個ask_to_reprepare函數來保證多次執行sp的時候都會進行重新reprepare。
四、總結
在MySQL的sp操作中涉及表操作的sql語句一定會執行check_and_update_table_version這個函數,每次會根據這個函數的結果來確定要不要重新parse該sql語句,如果沒有版本改變就直接進行execute操作,如果有改變就重新parse,先prepare再execute,這樣可以保證每次執行sp的SQL語句的時候表結構一定是最新的。
Enjoy GreatSQL