Commit 8de5603d authored by evgen@moonbone.local's avatar evgen@moonbone.local
Browse files

Bug#24989: The DEADLOCK error is improperly handled by InnoDB.

When innodb detects a deadlock it calls ha_rollback_trans() to rollback the 
main transaction. But such action isn't allowed from inside of triggers and
functions. When it happen the 'Explicit or implicit commit' error is thrown
even if there is no commit/rollback statements in the trigger/function. This
leads to the user confusion.

Now the convert_error_code_to_mysql() function doesn't call the 
ha_rollback_trans() function directly but rather calls the
mark_transaction_to_rollback function and returns an error.
The sp_rcontext::find_handler() now doesn't allow errors to be caught by the
trigger/function error handlers when the thd->is_fatal_sub_stmt_error flag
is set. Procedures are still allowed to catch such errors.
The sp_rcontext::find_handler function now accepts a THD handle as a parameter.
The transaction_rollback_request and the is_fatal_sub_stmt_error flags are 
added to the THD class. The are initialized by the THD class constructor.
Now the ha_autocommit_or_rollback function rolls back main transaction
when not in a sub statement and the thd->transaction_rollback_request
is set.
The THD::restore_sub_statement_state function now resets the 
thd->is_fatal_sub_stmt_error flag on exit from a sub-statement.
parent debb054d
Loading
Loading
Loading
Loading
+66 −0
Original line number Diff line number Diff line
@@ -32,3 +32,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;
+106 −0
Original line number Diff line number Diff line
@@ -44,3 +44,109 @@ INSERT INTO t3 SELECT concat(id),id from t2 ORDER BY -id;
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;
+3 −9
Original line number Diff line number Diff line
@@ -455,9 +455,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);

@@ -467,9 +465,7 @@ 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, row_rollback_on_timeout);

   		return(HA_ERR_LOCK_WAIT_TIMEOUT);

@@ -521,9 +517,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 {
+5 −0
Original line number Diff line number Diff line
@@ -821,6 +821,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
@@ -858,6 +861,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);

+15 −3
Original line number Diff line number Diff line
@@ -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)
    {
Loading