diff options
author | unknown <tsmith@ramayana.hindu.god> | 2007-08-01 18:59:41 -0600 |
---|---|---|
committer | unknown <tsmith@ramayana.hindu.god> | 2007-08-01 18:59:41 -0600 |
commit | 48eb7f8c857493ca7d30da4b78b279e4938f8c83 (patch) | |
tree | 1fa7c6a583124fd9e3ba466b15acf7b8518f3303 | |
parent | 15835a5693da749cc9635d127ff708286831872e (diff) | |
parent | d07385965a42e086f53b94b8f0f435c1e586d28d (diff) | |
download | mariadb-git-48eb7f8c857493ca7d30da4b78b279e4938f8c83.tar.gz |
Merge 50 -> 51 (-opt changesets)
sql/handler.cc:
Auto merged
sql/handler.h:
Auto merged
sql/sp_head.cc:
Auto merged
sql/sp_rcontext.cc:
Auto merged
sql/sql_base.cc:
Auto merged
sql/sql_select.cc:
Auto merged
sql/sql_select.h:
Auto merged
sql/sql_table.cc:
Auto merged
sql/sql_union.cc:
Auto merged
sql/sql_yacc.yy:
Auto merged
sql/table.cc:
Auto merged
mysql-test/include/read_many_rows.inc:
Manual merge
mysql-test/r/read_many_rows_innodb.result:
Manual merge
sql/sql_class.cc:
Manual merge
sql/sql_class.h:
Manual merge
storage/innobase/handler/ha_innodb.cc:
Manual merge
-rw-r--r-- | mysql-test/include/read_many_rows.inc | 106 | ||||
-rw-r--r-- | mysql-test/r/read_many_rows_innodb.result | 66 | ||||
-rw-r--r-- | sql/handler.cc | 5 | ||||
-rw-r--r-- | sql/sp_rcontext.cc | 18 | ||||
-rw-r--r-- | sql/sp_rcontext.h | 6 | ||||
-rw-r--r-- | sql/sql_class.cc | 23 | ||||
-rw-r--r-- | sql/sql_class.h | 30 | ||||
-rw-r--r-- | sql/sql_select.cc | 2 | ||||
-rw-r--r-- | sql/sql_select.h | 10 | ||||
-rw-r--r-- | sql/sql_union.cc | 57 | ||||
-rw-r--r-- | storage/innobase/handler/ha_innodb.cc | 13 |
11 files changed, 303 insertions, 33 deletions
diff --git a/mysql-test/include/read_many_rows.inc b/mysql-test/include/read_many_rows.inc index e69f5a05cd6..ff57c0ca13d 100644 --- a/mysql-test/include/read_many_rows.inc +++ b/mysql-test/include/read_many_rows.inc @@ -59,3 +59,109 @@ INSERT INTO t4 SELECT * FROM t3 ORDER BY CONCAT(a); SELECT SUM(id) FROM t3; DROP TABLE t1,t2,t3,t4; + +# +# Bug#24989: The DEADLOCK error is improperly handled by InnoDB. +# +CREATE TABLE t1 (f1 int NOT NULL) ENGINE=InnoDB; +CREATE TABLE t2 (f2 int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT) ENGINE=InnoDB; +DELIMITER |; +CREATE TRIGGER t1_bi before INSERT + ON t1 FOR EACH ROW +BEGIN + DECLARE CONTINUE HANDLER FOR SQLSTATE '40001' SET @a:= 'deadlock'; + DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET @a:= 'exception'; + INSERT INTO t2 (f2) VALUES (1); + DELETE FROM t2 WHERE f2 = 1; +END;| + +CREATE PROCEDURE proc24989() +BEGIN + DECLARE CONTINUE HANDLER FOR SQLSTATE '40001' SET @b:= 'deadlock'; + DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET @a:= 'exception'; + INSERT INTO t2 (f2) VALUES (1); + DELETE FROM t2 WHERE f2 = 1; +END;| + +create procedure proc24989_2() + deterministic +begin + declare continue handler for sqlexception + select 'Outer handler' as 'exception'; + + insert into t1 values(1); + select "continued"; +end| + +DELIMITER ;| + +connect (con1,localhost,root,,); +connect (con2,localhost,root,,); + +connection con1; +start transaction; +insert into t1 values(1); + +connection con2; +start transaction; +insert into t2 values(123); +send insert into t1 values(1); + +connection con1; +--sleep 1 +insert into t1 values(1); + +connection con2; +--error 1213 +reap; +select @a; +# check that the whole transaction was rolled back +select * from t2; + +connection con1; +commit; +start transaction; +insert into t1 values(1); + +connection con2; +start transaction; +insert into t2 values(123); +send call proc24989(); + +connection con1; +--sleep 1 +insert into t1 values(1); + +connection con2; +reap; +select @a,@b; +# check that the whole transaction was rolled back +select * from t2; + +connection con1; +commit; +start transaction; +insert into t1 values(1); + +connection con2; +start transaction; +insert into t2 values(123); +send call proc24989_2(); + +connection con1; +--sleep 1 +insert into t1 values(1); +commit; + +connection con2; +reap; +# check that the whole transaction was rolled back +select * from t2; + +disconnect con1; +disconnect con2; +connection default; +drop procedure proc24989; +drop procedure proc24989_2; +drop table t1,t2; + diff --git a/mysql-test/r/read_many_rows_innodb.result b/mysql-test/r/read_many_rows_innodb.result index ff11535f687..29ec509db5a 100644 --- a/mysql-test/r/read_many_rows_innodb.result +++ b/mysql-test/r/read_many_rows_innodb.result @@ -33,3 +33,69 @@ SELECT SUM(id) FROM t3; SUM(id) 2199024304128 DROP TABLE t1,t2,t3,t4; +CREATE TABLE t1 (f1 int NOT NULL) ENGINE=InnoDB; +CREATE TABLE t2 (f2 int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT) ENGINE=InnoDB; +CREATE TRIGGER t1_bi before INSERT +ON t1 FOR EACH ROW +BEGIN +DECLARE CONTINUE HANDLER FOR SQLSTATE '40001' SET @a:= 'deadlock'; +DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET @a:= 'exception'; +INSERT INTO t2 (f2) VALUES (1); +DELETE FROM t2 WHERE f2 = 1; +END;| +CREATE PROCEDURE proc24989() +BEGIN +DECLARE CONTINUE HANDLER FOR SQLSTATE '40001' SET @b:= 'deadlock'; +DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET @a:= 'exception'; +INSERT INTO t2 (f2) VALUES (1); +DELETE FROM t2 WHERE f2 = 1; +END;| +create procedure proc24989_2() +deterministic +begin +declare continue handler for sqlexception +select 'Outer handler' as 'exception'; +insert into t1 values(1); +select "continued"; +end| +start transaction; +insert into t1 values(1); +start transaction; +insert into t2 values(123); +insert into t1 values(1); +insert into t1 values(1); +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +select @a; +@a +NULL +select * from t2; +f2 +commit; +start transaction; +insert into t1 values(1); +start transaction; +insert into t2 values(123); +call proc24989(); +insert into t1 values(1); +select @a,@b; +@a @b +exception deadlock +select * from t2; +f2 +commit; +start transaction; +insert into t1 values(1); +start transaction; +insert into t2 values(123); +call proc24989_2(); +insert into t1 values(1); +commit; +exception +Outer handler +continued +continued +select * from t2; +f2 +drop procedure proc24989; +drop procedure proc24989_2; +drop table t1,t2; diff --git a/sql/handler.cc b/sql/handler.cc index 54ae258d560..e828c9cdde8 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -851,6 +851,9 @@ int ha_rollback_trans(THD *thd, bool all) } } #endif /* USING_TRANSACTIONS */ + if (all) + thd->transaction_rollback_request= FALSE; + /* If a non-transactional table was updated, warn; don't warn if this is a slave thread (because when a slave thread executes a ROLLBACK, it has @@ -887,6 +890,8 @@ int ha_autocommit_or_rollback(THD *thd, int error) if (ha_commit_stmt(thd)) error=1; } + else if (thd->transaction_rollback_request && !thd->in_sub_stmt) + (void) ha_rollback(thd); else (void) ha_rollback_stmt(thd); diff --git a/sql/sp_rcontext.cc b/sql/sp_rcontext.cc index b94c733cb0a..60a0c962c28 100644 --- a/sql/sp_rcontext.cc +++ b/sql/sp_rcontext.cc @@ -37,6 +37,7 @@ sp_rcontext::sp_rcontext(sp_pcontext *root_parsing_ctx, m_var_items(0), m_return_value_fld(return_value_fld), m_return_value_set(FALSE), + in_sub_stmt(FALSE), m_hcount(0), m_hsp(0), m_ihsp(0), @@ -67,6 +68,8 @@ sp_rcontext::~sp_rcontext() bool sp_rcontext::init(THD *thd) { + in_sub_stmt= thd->in_sub_stmt; + if (init_var_table(thd) || init_var_items()) return TRUE; @@ -191,7 +194,7 @@ sp_rcontext::set_return_value(THD *thd, Item **return_value_item) */ bool -sp_rcontext::find_handler(uint sql_errno, +sp_rcontext::find_handler(THD *thd, uint sql_errno, MYSQL_ERROR::enum_warning_level level) { if (m_hfound >= 0) @@ -200,6 +203,15 @@ sp_rcontext::find_handler(uint sql_errno, const char *sqlstate= mysql_errno_to_sqlstate(sql_errno); int i= m_hcount, found= -1; + /* + If this is a fatal sub-statement error, and this runtime + context corresponds to a sub-statement, no CONTINUE/EXIT + handlers from this context are applicable: try to locate one + in the outer scope. + */ + if (thd->is_fatal_sub_stmt_error && in_sub_stmt) + i= 0; + /* Search handlers from the latest (innermost) to the oldest (outermost) */ while (i--) { @@ -252,7 +264,7 @@ sp_rcontext::find_handler(uint sql_errno, */ if (m_prev_runtime_ctx && IS_EXCEPTION_CONDITION(sqlstate) && level == MYSQL_ERROR::WARN_LEVEL_ERROR) - return m_prev_runtime_ctx->find_handler(sql_errno, level); + return m_prev_runtime_ctx->find_handler(thd, sql_errno, level); return FALSE; } m_hfound= found; @@ -298,7 +310,7 @@ sp_rcontext::handle_error(uint sql_errno, elevated_level= MYSQL_ERROR::WARN_LEVEL_ERROR; } - if (find_handler(sql_errno, elevated_level)) + if (find_handler(thd, sql_errno, elevated_level)) { if (elevated_level == MYSQL_ERROR::WARN_LEVEL_ERROR) { diff --git a/sql/sp_rcontext.h b/sql/sp_rcontext.h index fbf479f52aa..0104b71a648 100644 --- a/sql/sp_rcontext.h +++ b/sql/sp_rcontext.h @@ -125,7 +125,7 @@ class sp_rcontext : public Sql_alloc // Returns 1 if a handler was found, 0 otherwise. bool - find_handler(uint sql_errno,MYSQL_ERROR::enum_warning_level level); + find_handler(THD *thd, uint sql_errno,MYSQL_ERROR::enum_warning_level level); // If there is an error handler for this error, handle it and return TRUE. bool @@ -236,6 +236,10 @@ private: during execution. */ bool m_return_value_set; + /** + TRUE if the context is created for a sub-statement. + */ + bool in_sub_stmt; sp_handler_t *m_handler; // Visible handlers uint m_hcount; // Stack pointer for m_handler diff --git a/sql/sql_class.cc b/sql/sql_class.cc index ba9d832dfa9..c7c6a164e10 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -367,6 +367,8 @@ THD::THD() stmt_depends_on_first_successful_insert_id_in_prev_stmt(FALSE), global_read_lock(0), is_fatal_error(0), + transaction_rollback_request(0), + is_fatal_sub_stmt_error(0), rand_used(0), time_zone_used(0), in_lock_tables(0), @@ -1304,7 +1306,7 @@ void select_send::abort() { DBUG_ENTER("select_send::abort"); if (status && thd->spcont && - thd->spcont->find_handler(thd->net.last_errno, + thd->spcont->find_handler(thd, thd->net.last_errno, MYSQL_ERROR::WARN_LEVEL_ERROR)) { /* @@ -2682,6 +2684,13 @@ void THD::restore_sub_statement_state(Sub_statement_state *backup) limit_found_rows= backup->limit_found_rows; sent_row_count= backup->sent_row_count; client_capabilities= backup->client_capabilities; + /* + If we've left sub-statement mode, reset the fatal error flag. + Otherwise keep the current value, to propagate it up the sub-statement + stack. + */ + if (!in_sub_stmt) + is_fatal_sub_stmt_error= FALSE; if ((options & OPTION_BIN_LOG) && is_update_query(lex->sql_command) && !current_stmt_binlog_row_based) @@ -2696,6 +2705,18 @@ void THD::restore_sub_statement_state(Sub_statement_state *backup) } +/** + Mark transaction to rollback and mark error as fatal to a sub-statement. + + @param thd Thread handle + @param all TRUE <=> rollback main transaction. +*/ + +void mark_transaction_to_rollback(THD *thd, bool all) +{ + thd->is_fatal_sub_stmt_error= TRUE; + thd->transaction_rollback_request= all; +} /*************************************************************************** Handling of XA id cacheing ***************************************************************************/ diff --git a/sql/sql_class.h b/sql/sql_class.h index 00b68420f56..996009f3f5d 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1411,7 +1411,33 @@ public: bool current_stmt_binlog_row_based; bool locked, some_tables_deleted; bool last_cuted_field; - bool no_errors, password, is_fatal_error; + bool no_errors, password; + /** + Set to TRUE if execution of the current compound statement + can not continue. In particular, disables activation of + CONTINUE or EXIT handlers of stored routines. + Reset in the end of processing of the current user request, in + @see mysql_reset_thd_for_next_command(). + */ + bool is_fatal_error; + /** + Set by a storage engine to request the entire + transaction (that possibly spans multiple engines) to + rollback. Reset in ha_rollback. + */ + bool transaction_rollback_request; + /** + TRUE if we are in a sub-statement and the current error can + not be safely recovered until we left the sub-statement mode. + In particular, disables activation of CONTINUE and EXIT + handlers inside sub-statements. E.g. if it is a deadlock + error and requires a transaction-wide rollback, this flag is + raised (traditionally, MySQL first has to close all the reads + via @see handler::ha_index_or_rnd_end() and only then perform + the rollback). + Reset to FALSE when we leave the sub-statement mode. + */ + bool is_fatal_sub_stmt_error; bool query_start_used, rand_used, time_zone_used; /* for IS NULL => = last_insert_id() fix in remove_eq_conds() */ bool substitute_null_with_insert_id; @@ -2455,4 +2481,6 @@ public: void add_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var); void add_diff_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var, STATUS_VAR *dec_var); +void mark_transaction_to_rollback(THD *thd, bool all); + #endif /* MYSQL_SERVER */ diff --git a/sql/sql_select.cc b/sql/sql_select.cc index f333e754ff7..9ea67af1bf9 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2455,7 +2455,7 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables, COND *conds, no_partitions_used) && !s->dependent && (table->file->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT) && - !table->fulltext_searched) + !table->fulltext_searched && !join->no_const_tables) { set_position(join,const_count++,s,(KEYUSE*) 0); } diff --git a/sql/sql_select.h b/sql/sql_select.h index 64173a91162..d1acad1c5d6 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -316,6 +316,14 @@ public: SELECT_LEX_UNIT *unit; // select that processed SELECT_LEX *select_lex; + /* + TRUE <=> optimizer must not mark any table as a constant table. + This is needed for subqueries in form "a IN (SELECT .. UNION SELECT ..): + when we optimize the select that reads the results of the union from a + temporary table, we must not mark the temp. table as constant because + the number of rows in it may vary from one subquery execution to another. + */ + bool no_const_tables; JOIN *tmp_join; // copy of this JOIN to be used with temporary tables ROLLUP rollup; // Used with rollup @@ -445,6 +453,8 @@ public: tmp_table_param.init(); tmp_table_param.end_write_records= HA_POS_ERROR; rollup.state= ROLLUP::STATE_NONE; + + no_const_tables= FALSE; } int prepare(Item ***rref_pointer_array, TABLE_LIST *tables, uint wind_num, diff --git a/sql/sql_union.cc b/sql/sql_union.cc index c872d3cb241..5da4b97cc5d 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -547,6 +547,10 @@ bool st_select_lex_unit::exec() /* allocate JOIN for fake select only once (prevent mysql_select automatic allocation) + TODO: The above is nonsense. mysql_select() will not allocate the + join if one already exists. There must be some other reason why we + don't let it allocate the join. Perhaps this is because we need + some special parameter values passed to join constructor? */ if (!(fake_select_lex->join= new JOIN(thd, item_list, fake_select_lex->options, result))) @@ -554,33 +558,52 @@ bool st_select_lex_unit::exec() fake_select_lex->table_list.empty(); DBUG_RETURN(TRUE); } + fake_select_lex->join->no_const_tables= TRUE; /* Fake st_select_lex should have item list for correctref_array allocation. */ fake_select_lex->item_list= item_list; + saved_error= mysql_select(thd, &fake_select_lex->ref_pointer_array, + &result_table_list, + 0, item_list, NULL, + global_parameters->order_list.elements, + (ORDER*)global_parameters->order_list.first, + (ORDER*) NULL, NULL, (ORDER*) NULL, + fake_select_lex->options | SELECT_NO_UNLOCK, + result, this, fake_select_lex); } else { - JOIN_TAB *tab,*end; - for (tab=join->join_tab, end=tab+join->tables ; - tab && tab != end ; - tab++) - { - delete tab->select; - delete tab->quick; - } - join->init(thd, item_list, fake_select_lex->options, result); + if (describe) + { + /* + In EXPLAIN command, constant subqueries that do not use any + tables are executed two times: + - 1st time is a real evaluation to get the subquery value + - 2nd time is to produce EXPLAIN output rows. + 1st execution sets certain members (e.g. select_result) to perform + subquery execution rather than EXPLAIN line production. In order + to reset them back, we re-do all of the actions (yes it is ugly): + */ + join->init(thd, item_list, fake_select_lex->options, result); + saved_error= mysql_select(thd, &fake_select_lex->ref_pointer_array, + &result_table_list, + 0, item_list, NULL, + global_parameters->order_list.elements, + (ORDER*)global_parameters->order_list.first, + (ORDER*) NULL, NULL, (ORDER*) NULL, + fake_select_lex->options | SELECT_NO_UNLOCK, + result, this, fake_select_lex); + } + else + { + join->examined_rows= 0; + saved_error= join->reinit(); + join->exec(); + } } - saved_error= mysql_select(thd, &fake_select_lex->ref_pointer_array, - &result_table_list, - 0, item_list, NULL, - global_parameters->order_list.elements, - (ORDER*)global_parameters->order_list.first, - (ORDER*) NULL, NULL, (ORDER*) NULL, - fake_select_lex->options | SELECT_NO_UNLOCK, - result, this, fake_select_lex); fake_select_lex->table_list.empty(); if (!saved_error) diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 52cd09c351e..0a1e63a4c65 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -601,9 +601,7 @@ convert_error_code_to_mysql( tell it also to MySQL so that MySQL knows to empty the cached binlog for this transaction */ - if (thd) { - ha_rollback(thd); - } + mark_transaction_to_rollback(thd, TRUE); return(HA_ERR_LOCK_DEADLOCK); @@ -613,9 +611,8 @@ convert_error_code_to_mysql( latest SQL statement in a lock wait timeout. Previously, we rolled back the whole transaction. */ - if (thd && row_rollback_on_timeout) { - ha_rollback(thd); - } + mark_transaction_to_rollback(thd, + (bool)row_rollback_on_timeout); return(HA_ERR_LOCK_WAIT_TIMEOUT); @@ -667,9 +664,7 @@ convert_error_code_to_mysql( tell it also to MySQL so that MySQL knows to empty the cached binlog for this transaction */ - if (thd) { - ha_rollback(thd); - } + mark_transaction_to_rollback(thd, TRUE); return(HA_ERR_LOCK_TABLE_FULL); } else if (error == DB_TOO_MANY_CONCURRENT_TRXS) { |