diff --git a/mysql-test/r/sp-threads.result b/mysql-test/r/sp-threads.result index 0bb5c3423e2..a081e520496 100644 --- a/mysql-test/r/sp-threads.result +++ b/mysql-test/r/sp-threads.result @@ -23,3 +23,20 @@ select * from t1; s1 s2 s3 drop table t1; drop procedure bug4934; +drop procedure if exists bug9486; +drop table if exists t1, t2; +create table t1 (id1 int, val int); +create table t2 (id2 int); +create procedure bug9486() +update t1, t2 set val= 1 where id1=id2; +call bug9486(); +lock tables t2 write; + call bug9486(); +show processlist; +Id User Host db Command Time State Info +# root localhost test Sleep # NULL +# root localhost test Query # Locked call bug9486() +# root localhost test Query # NULL show processlist +unlock tables; +drop procedure bug9486; +drop table t1, t2; diff --git a/mysql-test/t/sp-threads.test b/mysql-test/t/sp-threads.test index 0ced60a610f..608ac3e2ee7 100644 --- a/mysql-test/t/sp-threads.test +++ b/mysql-test/t/sp-threads.test @@ -54,6 +54,37 @@ drop table t1; drop procedure bug4934; +# +# BUG #9486 "Can't perform multi-update in stored procedure" +# +--disable_warnings +drop procedure if exists bug9486; +drop table if exists t1, t2; +--enable_warnings +create table t1 (id1 int, val int); +create table t2 (id2 int); + +create procedure bug9486() + update t1, t2 set val= 1 where id1=id2; +call bug9486(); +# Let us check that SP invocation requires write lock for t2. +connection con2root; +lock tables t2 write; +connection con1root; +send call bug9486(); +connection con2root; +--sleep 2 +# There should be call statement in locked state. +--replace_column 1 # 6 # +show processlist; +unlock tables; +connection con1root; +reap; + +drop procedure bug9486; +drop table t1, t2; + + # # BUG#NNNN: New bug synopsis # diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 2d9cad61cd6..ad14116a4c7 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -2060,6 +2060,12 @@ typedef struct st_sp_table LEX_STRING qname; bool temp; TABLE_LIST *table; + /* + We can't use table->lock_type as lock type for table + in multi-set since it can be changed by statement during + its execution (e.g. as this happens for multi-update). + */ + thr_lock_type lock_type; uint lock_count; uint query_lock_count; } SP_TABLE; @@ -2131,8 +2137,8 @@ sp_head::merge_table_list(THD *thd, TABLE_LIST *table, LEX *lex_for_tmp_check) */ if ((tab= (SP_TABLE *)hash_search(&m_sptabs, (byte *)tname, tlen))) { - if (tab->table->lock_type < table->lock_type) - tab->table= table; // Use the table with the highest lock type + if (tab->lock_type < table->lock_type) + tab->lock_type= table->lock_type; // Use the table with the highest lock type tab->query_lock_count++; if (tab->query_lock_count > tab->lock_count) tab->lock_count++; @@ -2150,6 +2156,7 @@ sp_head::merge_table_list(THD *thd, TABLE_LIST *table, LEX *lex_for_tmp_check) lex_for_tmp_check->create_info.options & HA_LEX_CREATE_TMP_TABLE) tab->temp= TRUE; tab->table= table; + tab->lock_type= table->lock_type; tab->lock_count= tab->query_lock_count= 1; my_hash_insert(&m_sptabs, (byte *)tab); } @@ -2222,7 +2229,7 @@ sp_head::add_used_tables_to_table_list(THD *thd, table->alias= otable->alias; table->table_name= otable->table_name; table->table_name_length= otable->table_name_length; - table->lock_type= otable->lock_type; + table->lock_type= stab->lock_type; table->cacheable_table= 1; table->prelocking_placeholder= 1; diff --git a/sql/sql_lex.h b/sql/sql_lex.h index d8c83434423..94f1a8e0df4 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -732,7 +732,7 @@ typedef struct st_lex USER_RESOURCES mqh; ulong type; enum_sql_command sql_command, orig_sql_command; - thr_lock_type lock_option, multi_lock_option; + thr_lock_type lock_option; enum SSL_type ssl_type; /* defined in violite.h */ enum my_lex_states next_state; enum enum_duplicates duplicates; diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 7c00299d482..56d09d7c563 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1012,11 +1012,6 @@ static int mysql_test_update(Prepared_statement *stmt, DBUG_PRINT("info", ("Switch to multi-update")); /* pass counter value */ thd->lex->table_count= table_count; - /* - give correct value to multi_lock_option, because it will be used - in multiupdate - */ - thd->lex->multi_lock_option= table_list->lock_type; /* convert to multiupdate */ return 2; } diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 8b8dd32b22d..86aa0bf9890 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -145,11 +145,6 @@ int mysql_update(THD *thd, DBUG_PRINT("info", ("Switch to multi-update")); /* pass counter value */ thd->lex->table_count= table_count; - /* - give correct value to multi_lock_option, because it will be used - in multiupdate - */ - thd->lex->multi_lock_option= table_list->lock_type; /* convert to multiupdate */ return 2; } @@ -692,8 +687,10 @@ bool mysql_multi_update_prepare(THD *thd) } DBUG_PRINT("info",("setting table `%s` for update", tl->alias)); - tl->lock_type= lex->multi_lock_option; - tl->updating= 1; + /* + If table will be updated we should not downgrade lock for it and + leave it as is. + */ } else { @@ -705,15 +702,15 @@ bool mysql_multi_update_prepare(THD *thd) */ tl->lock_type= using_update_log ? TL_READ_NO_INSERT : TL_READ; tl->updating= 0; + /* Update TABLE::lock_type accordingly. */ + if (!tl->placeholder() && !tl->schema_table && !using_lock_tables) + tl->table->reginfo.lock_type= tl->lock_type; } /* Check access privileges for table */ if (!tl->derived && !tl->belong_to_view) { uint want_privilege= tl->updating ? UPDATE_ACL : SELECT_ACL; - if (!using_lock_tables) - tl->table->reginfo.lock_type= tl->lock_type; - if (check_access(thd, want_privilege, tl->db, &tl->grant.privilege, 0, 0) || (grant_option && check_grant(thd, want_privilege, tl, 0, 1, 0))) @@ -847,7 +844,7 @@ bool mysql_multi_update(THD *thd, result, unit, select_lex); delete result; thd->abort_on_warning= 0; - DBUG_RETURN(TRUE); + DBUG_RETURN(FALSE); } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index ea919c77e99..27e75fd9940 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -5992,10 +5992,7 @@ update: { LEX *lex= Lex; if (lex->select_lex.table_list.elements > 1) - { lex->sql_command= SQLCOM_UPDATE_MULTI; - lex->multi_lock_option= $3; - } else if (lex->select_lex.get_table_list()->derived) { /* it is single table update and it is update of derived table */ @@ -6003,8 +6000,12 @@ update: lex->select_lex.get_table_list()->alias, "UPDATE"); YYABORT; } - else - Select->set_lock_for_tables($3); + /* + In case of multi-update setting write lock for all tables may + be too pessimistic. We will decrease lock level if possible in + mysql_multi_update(). + */ + Select->set_lock_for_tables($3); } where_clause opt_order_clause delete_limit_clause {} ;