mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 03:52:35 +01:00
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
This commit is contained in:
commit
48eb7f8c85
11 changed files with 303 additions and 33 deletions
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
***************************************************************************/
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue