Fix for bug#14188793 - "DEADLOCK CAUSED BY ALTER TABLE DOEN'T CLEAR

STATUS OF ROLLBACKED TRANSACTION" and bug  - "TRANSACTION
IS NOT FULLY ROLLED BACK IN CASE OF INNODB DEADLOCK".

The problem in the first bug report was that although deadlock involving
metadata locks was reported using the same error code and message as InnoDB
deadlock it didn't rollback transaction like the latter. This caused
confusion to users as in some cases after ER_LOCK_DEADLOCK transaction
could have been restarted immediately and in some cases rollback was
required.

The problem in the second bug report was that although InnoDB deadlock
caused transaction rollback in all storage engines it didn't cause release
of metadata locks. So concurrent DDL on the tables used in transaction was
blocked until implicit or explicit COMMIT or ROLLBACK was issued in the
connection which got InnoDB deadlock.

The former issue has stemmed from the fact that when support for detection
and reporting metadata locks deadlocks was added we erroneously assumed
that InnoDB doesn't rollback transaction on deadlock but only last statement
(while this is what happens on InnoDB lock timeout actually) and so didn't
implement rollback of transactions on MDL deadlocks.

The latter issue was caused by the fact that rollback of transaction due
to deadlock is carried out by setting THD::transaction_rollback_request
flag at the point where deadlock is detected and performing rollback
inside of trans_rollback_stmt() call when this flag is set. And
trans_rollback_stmt() is not aware of MDL locks, so no MDL locks are
released.

This patch solves these two problems in the following way:

- In case when MDL deadlock is detect transaction rollback is requested
  by setting THD::transaction_rollback_request flag.

- Code performing rollback of transaction if THD::transaction_rollback_request
  is moved out from trans_rollback_stmt(). Now we handle rollback request
  on the same level as we call trans_rollback_stmt() and release statement/
  transaction MDL locks.
This commit is contained in:
Dmitry Lenev 2013-08-20 13:12:34 +04:00
parent 894b948341
commit b07ec61f85
21 changed files with 257 additions and 142 deletions

View file

@ -1087,8 +1087,7 @@ connection con1;
connection default;
--echo #
--echo # Demonstrate that HANDLER locks and transaction locks
--echo # reside in the same context, and we don't back-off
--echo # when have transaction or handler locks.
--echo # reside in the same context.
--echo #
create table t1 (a int, key a (a));
insert into t1 (a) values (1), (2), (3), (4), (5);
@ -1109,9 +1108,9 @@ let $wait_condition=select count(*)=1 from information_schema.processlist
--source include/wait_condition.inc
--echo # --> connection default
connection default;
--echo # We back-off on hitting deadlock condition.
--error ER_LOCK_DEADLOCK
handler t0 open;
--error ER_LOCK_DEADLOCK
select * from t0;
handler t1 open;
commit;

View file

@ -1104,8 +1104,7 @@ handler t1 close;
# --> connection default
#
# Demonstrate that HANDLER locks and transaction locks
# reside in the same context, and we don't back-off
# when have transaction or handler locks.
# reside in the same context.
#
create table t1 (a int, key a (a));
insert into t1 (a) values (1), (2), (3), (4), (5);
@ -1125,10 +1124,16 @@ rename table t0 to t3, t1 to t0, t3 to t1;
# --> connection con1
# Waiting for 'rename table ...' to get blocked...
# --> connection default
# We back-off on hitting deadlock condition.
handler t0 open;
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
select * from t0;
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
a
1
2
3
4
5
handler t1 open;
commit;
handler t1 close;

View file

@ -1100,8 +1100,7 @@ handler t1 close;
# --> connection default
#
# Demonstrate that HANDLER locks and transaction locks
# reside in the same context, and we don't back-off
# when have transaction or handler locks.
# reside in the same context.
#
create table t1 (a int, key a (a));
insert into t1 (a) values (1), (2), (3), (4), (5);
@ -1121,10 +1120,16 @@ rename table t0 to t3, t1 to t0, t3 to t1;
# --> connection con1
# Waiting for 'rename table ...' to get blocked...
# --> connection default
# We back-off on hitting deadlock condition.
handler t0 open;
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
select * from t0;
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
a
1
2
3
4
5
handler t1 open;
commit;
handler t1 close;

View file

@ -1843,15 +1843,11 @@ rename table t2 to t0, t1 to t2, t0 to t1;;
# for 'deadlock_con1' which holds shared metadata lock on 't2'.
#
# The below statement should not wait as doing so will cause deadlock.
# Instead it should fail and emit ER_LOCK_DEADLOCK statement.
# Instead it should fail and emit ER_LOCK_DEADLOCK statement and
# transaction should be rolled back.
select * from t1;
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
#
# Let us check that failure of the above statement has not released
# metadata lock on table 't1', i.e. that RENAME TABLE is still blocked.
# Commit transaction to unblock RENAME TABLE.
commit;
#
# Switching to connection 'default'.
# Reap RENAME TABLE.
#
@ -1888,16 +1884,10 @@ unlock tables;
# Switching to connection 'deadlock_con1'.
# Since the latest RENAME TABLE entered in deadlock with SELECT
# statement the latter should be aborted and emit ER_LOCK_DEADLOCK
# error.
# error and transaction should be rolled back.
# Reap SELECT * FROM t1.
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
#
# Again let us check that failure of the SELECT statement has not
# released metadata lock on table 't2', i.e. that the latest RENAME
# is blocked.
# Commit transaction to unblock this RENAME TABLE.
commit;
#
# Switching to connection 'deadlock_con2'.
# Reap RENAME TABLE ... .
#
@ -1931,14 +1921,10 @@ alter table t1 add column j int, rename to t2;;
# metadata lock on 't2' and starts waiting for connection
# 'deadlock_con1' which holds shared lock on 't1'.
# The below statement should not wait as it will cause deadlock.
# An appropriate error should be reported instead.
# An appropriate error should be reported instead and transaction
# should be rolled back.
select * from t2;
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
# Again let us check that failure of the above statement has not
# released all metadata locks in connection 'deadlock_con1' and
# so ALTER TABLE ... RENAME is still blocked.
# Commit transaction to unblock ALTER TABLE ... RENAME.
commit;
#
# Switching to connection 'default'.
# Reap ALTER TABLE ... RENAME.
@ -2426,12 +2412,6 @@ set debug_sync='mdl_acquire_lock_wait SIGNAL alter_go';
update t1 set c3=c3+1 where c2 = 3;
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
#
# Let us check that failure of the above statement has not released
# metadata lock on table 't1', i.e. that ALTER TABLE is still blocked.
# Unblock ALTER TABLE by commiting transaction and thus releasing
# metadata lock on 't1'.
commit;
#
# Switching to connection 'con46273'.
# Reap ALTER TABLE.
#

View file

@ -2619,21 +2619,11 @@ let $wait_condition=
--source include/wait_condition.inc
--echo #
--echo # The below statement should not wait as doing so will cause deadlock.
--echo # Instead it should fail and emit ER_LOCK_DEADLOCK statement.
--echo # Instead it should fail and emit ER_LOCK_DEADLOCK statement and
--echo # transaction should be rolled back.
--error ER_LOCK_DEADLOCK
select * from t1;
--echo #
--echo # Let us check that failure of the above statement has not released
--echo # metadata lock on table 't1', i.e. that RENAME TABLE is still blocked.
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Waiting for table metadata lock" and
info = "rename table t2 to t0, t1 to t2, t0 to t1";
--source include/wait_condition.inc
--echo # Commit transaction to unblock RENAME TABLE.
commit;
--echo #
--echo # Switching to connection 'default'.
connection default;
@ -2696,23 +2686,11 @@ unlock tables;
connection deadlock_con1;
--echo # Since the latest RENAME TABLE entered in deadlock with SELECT
--echo # statement the latter should be aborted and emit ER_LOCK_DEADLOCK
--echo # error.
--echo # error and transaction should be rolled back.
--echo # Reap SELECT * FROM t1.
--error ER_LOCK_DEADLOCK
--reap
--echo #
--echo # Again let us check that failure of the SELECT statement has not
--echo # released metadata lock on table 't2', i.e. that the latest RENAME
--echo # is blocked.
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Waiting for table metadata lock" and
info = "rename table t1 to t0, t2 to t1, t0 to t2";
--source include/wait_condition.inc
--echo # Commit transaction to unblock this RENAME TABLE.
commit;
--echo #
--echo # Switching to connection 'deadlock_con2'.
connection deadlock_con2;
@ -2761,22 +2739,11 @@ let $wait_condition=
--source include/wait_condition.inc
--echo # The below statement should not wait as it will cause deadlock.
--echo # An appropriate error should be reported instead.
--echo # An appropriate error should be reported instead and transaction
--echo # should be rolled back.
--error ER_LOCK_DEADLOCK
select * from t2;
--echo # Again let us check that failure of the above statement has not
--echo # released all metadata locks in connection 'deadlock_con1' and
--echo # so ALTER TABLE ... RENAME is still blocked.
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Waiting for table metadata lock" and
info = "alter table t1 add column j int, rename to t2";
--source include/wait_condition.inc
--echo # Commit transaction to unblock ALTER TABLE ... RENAME.
commit;
--echo #
--echo # Switching to connection 'default'.
connection default;
@ -3577,19 +3544,6 @@ set debug_sync='mdl_acquire_lock_wait SIGNAL alter_go';
--error ER_LOCK_DEADLOCK
update t1 set c3=c3+1 where c2 = 3;
--echo #
--echo # Let us check that failure of the above statement has not released
--echo # metadata lock on table 't1', i.e. that ALTER TABLE is still blocked.
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Waiting for table metadata lock" and
info = "alter table t1 add column e int, rename to t2";
--source include/wait_condition.inc
--echo # Unblock ALTER TABLE by commiting transaction and thus releasing
--echo # metadata lock on 't1'.
commit;
--echo #
--echo # Switching to connection 'con46273'.
connection con46273;

View file

@ -1,5 +1,5 @@
/*
Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -2422,6 +2422,12 @@ add_ndb_binlog_index_err:
thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
thd->stmt_da->can_overwrite_status= FALSE;
close_thread_tables(thd);
/*
There should be no need for rolling back transaction due to deadlock
(since ndb_binlog_index is non transactional).
*/
DBUG_ASSERT(! thd->transaction_rollback_request);
thd->mdl_context.release_transactional_locks();
ndb_binlog_index= 0;
thd->variables.option_bits= saved_options;

View file

@ -1480,10 +1480,16 @@ int ha_rollback_trans(THD *thd, bool all)
}
trans->ha_list= 0;
trans->no_2pc=0;
if (is_real_trans && thd->transaction_rollback_request &&
thd->transaction.xid_state.xa_state != XA_NOTR)
thd->transaction.xid_state.rm_error= thd->stmt_da->sql_errno();
}
/*
Thanks to possibility of MDL deadlock rollback request can come even if
transaction hasn't been started in any transactional storage engine.
*/
if (is_real_trans && thd->transaction_rollback_request &&
thd->transaction.xid_state.xa_state != XA_NOTR)
thd->transaction.xid_state.rm_error= thd->stmt_da->sql_errno();
/* Always cleanup. Even if nht==0. There may be savepoints. */
if (is_real_trans)
thd->transaction.cleanup();

View file

@ -5104,6 +5104,8 @@ error:
thd->stmt_da->can_overwrite_status= FALSE;
close_thread_tables(thd);
/*
- If transaction rollback was requested due to deadlock
perform it and release metadata locks.
- If inside a multi-statement transaction,
defer the release of metadata locks until the current
transaction is either committed or rolled back. This prevents
@ -5113,7 +5115,12 @@ error:
- If in autocommit mode, or outside a transactional context,
automatically release metadata locks of the current statement.
*/
if (! thd->in_multi_stmt_transaction_mode())
if (thd->transaction_rollback_request)
{
trans_rollback_implicit(thd);
thd->mdl_context.release_transactional_locks();
}
else if (! thd->in_multi_stmt_transaction_mode())
thd->mdl_context.release_transactional_locks();
else
thd->mdl_context.release_statement_locks();
@ -8197,7 +8204,10 @@ static int rows_event_stmt_cleanup(Relay_log_info const *rli, THD * thd)
Xid_log_event will come next which will, if some transactional engines
are involved, commit the transaction and flush the pending event to the
binlog.
If there was a deadlock the transaction should have been rolled back
already. So there should be no need to rollback the transaction.
*/
DBUG_ASSERT(! thd->transaction_rollback_request);
error|= (error ? trans_rollback_stmt(thd) : trans_commit_stmt(thd));
/*

View file

@ -1,4 +1,4 @@
/* Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
/* Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -1808,7 +1808,10 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli)
Xid_log_event will come next which will, if some transactional engines
are involved, commit the transaction and flush the pending event to the
binlog.
If there was a deadlock the transaction should have been rolled back
already. So there should be no need to rollback the transaction.
*/
DBUG_ASSERT(! thd->transaction_rollback_request);
if ((error= (binlog_error ? trans_rollback_stmt(thd) : trans_commit_stmt(thd))))
rli->report(ERROR_LEVEL, error,
"Error in %s event: commit of row events failed, "

View file

@ -1,4 +1,4 @@
/* Copyright (c) 2006, 2012, Oracle and/or its affiliates. All rights reserved.
/* Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -1317,6 +1317,8 @@ void Relay_log_info::slave_close_thread_tables(THD *thd)
close_thread_tables(thd);
/*
- If transaction rollback was requested due to deadlock
perform it and release metadata locks.
- If inside a multi-statement transaction,
defer the release of metadata locks until the current
transaction is either committed or rolled back. This prevents
@ -1326,7 +1328,12 @@ void Relay_log_info::slave_close_thread_tables(THD *thd)
- If in autocommit mode, or outside a transactional context,
automatically release metadata locks of the current statement.
*/
if (! thd->in_multi_stmt_transaction_mode())
if (thd->transaction_rollback_request)
{
trans_rollback_implicit(thd);
thd->mdl_context.release_transactional_locks();
}
else if (! thd->in_multi_stmt_transaction_mode())
thd->mdl_context.release_transactional_locks();
else
thd->mdl_context.release_statement_locks();

View file

@ -1,5 +1,5 @@
/*
Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -2155,10 +2155,18 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)
close_thread_tables(thd);
thd_proc_info(thd, 0);
if (! thd->in_sub_stmt && ! thd->in_multi_stmt_transaction_mode())
thd->mdl_context.release_transactional_locks();
else if (! thd->in_sub_stmt)
thd->mdl_context.release_statement_locks();
if (! thd->in_sub_stmt)
{
if (thd->transaction_rollback_request)
{
trans_rollback_implicit(thd);
thd->mdl_context.release_transactional_locks();
}
else if (! thd->in_multi_stmt_transaction_mode())
thd->mdl_context.release_transactional_locks();
else
thd->mdl_context.release_statement_locks();
}
thd->rollback_item_tree_changes();
@ -3008,10 +3016,18 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
close_thread_tables(thd);
thd_proc_info(thd, 0);
if (! thd->in_sub_stmt && ! thd->in_multi_stmt_transaction_mode())
thd->mdl_context.release_transactional_locks();
else if (! thd->in_sub_stmt)
thd->mdl_context.release_statement_locks();
if (! thd->in_sub_stmt)
{
if (thd->transaction_rollback_request)
{
trans_rollback_implicit(thd);
thd->mdl_context.release_transactional_locks();
}
else if (! thd->in_multi_stmt_transaction_mode())
thd->mdl_context.release_transactional_locks();
else
thd->mdl_context.release_statement_locks();
}
}
if (m_lex->query_tables_own_last)

View file

@ -859,8 +859,20 @@ send_result_message:
}
}
/* Error path, a admin command failed. */
trans_commit_stmt(thd);
trans_commit_implicit(thd);
if (thd->transaction_rollback_request)
{
/*
Unlikely, but transaction rollback was requested by one of storage
engines (e.g. due to deadlock). Perform it.
*/
if (trans_rollback_stmt(thd) || trans_rollback_implicit(thd))
goto err;
}
else
{
if (trans_commit_stmt(thd) || trans_commit_implicit(thd))
goto err;
}
close_thread_tables(thd);
thd->mdl_context.release_transactional_locks();

View file

@ -1,4 +1,4 @@
/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -3941,7 +3941,8 @@ end_unlock:
/** Open_table_context */
Open_table_context::Open_table_context(THD *thd, uint flags)
:m_failed_table(NULL),
:m_thd(thd),
m_failed_table(NULL),
m_start_of_statement_svp(thd->mdl_context.mdl_savepoint()),
m_timeout(flags & MYSQL_LOCK_IGNORE_TIMEOUT ?
LONG_TIMEOUT : thd->variables.lock_wait_timeout),
@ -4018,6 +4019,7 @@ request_backoff_action(enum_open_table_action action_arg,
if (action_arg != OT_REOPEN_TABLES && m_has_locks)
{
my_error(ER_LOCK_DEADLOCK, MYF(0));
mark_transaction_to_rollback(m_thd, true);
return TRUE;
}
/*
@ -4027,7 +4029,7 @@ request_backoff_action(enum_open_table_action action_arg,
if (table)
{
DBUG_ASSERT(action_arg == OT_DISCOVER || action_arg == OT_REPAIR);
m_failed_table= (TABLE_LIST*) current_thd->alloc(sizeof(TABLE_LIST));
m_failed_table= (TABLE_LIST*) m_thd->alloc(sizeof(TABLE_LIST));
if (m_failed_table == NULL)
return TRUE;
m_failed_table->init_one_table(table->db, table->db_length,
@ -4044,8 +4046,6 @@ request_backoff_action(enum_open_table_action action_arg,
/**
Recover from failed attempt of open table by performing requested action.
@param thd Thread context
@pre This function should be called only with "action" != OT_NO_ACTION
and after having called @sa close_tables_for_reopen().
@ -4055,7 +4055,7 @@ request_backoff_action(enum_open_table_action action_arg,
bool
Open_table_context::
recover_from_failed_open(THD *thd)
recover_from_failed_open()
{
bool result= FALSE;
/* Execute the action. */
@ -4067,33 +4067,33 @@ recover_from_failed_open(THD *thd)
break;
case OT_DISCOVER:
{
if ((result= lock_table_names(thd, m_failed_table, NULL,
if ((result= lock_table_names(m_thd, m_failed_table, NULL,
get_timeout(),
MYSQL_OPEN_SKIP_TEMPORARY)))
break;
tdc_remove_table(thd, TDC_RT_REMOVE_ALL, m_failed_table->db,
tdc_remove_table(m_thd, TDC_RT_REMOVE_ALL, m_failed_table->db,
m_failed_table->table_name, FALSE);
ha_create_table_from_engine(thd, m_failed_table->db,
ha_create_table_from_engine(m_thd, m_failed_table->db,
m_failed_table->table_name);
thd->warning_info->clear_warning_info(thd->query_id);
thd->clear_error(); // Clear error message
thd->mdl_context.release_transactional_locks();
m_thd->warning_info->clear_warning_info(m_thd->query_id);
m_thd->clear_error(); // Clear error message
m_thd->mdl_context.release_transactional_locks();
break;
}
case OT_REPAIR:
{
if ((result= lock_table_names(thd, m_failed_table, NULL,
if ((result= lock_table_names(m_thd, m_failed_table, NULL,
get_timeout(),
MYSQL_OPEN_SKIP_TEMPORARY)))
break;
tdc_remove_table(thd, TDC_RT_REMOVE_ALL, m_failed_table->db,
tdc_remove_table(m_thd, TDC_RT_REMOVE_ALL, m_failed_table->db,
m_failed_table->table_name, FALSE);
result= auto_repair_table(thd, m_failed_table);
thd->mdl_context.release_transactional_locks();
result= auto_repair_table(m_thd, m_failed_table);
m_thd->mdl_context.release_transactional_locks();
break;
}
default:
@ -4926,7 +4926,7 @@ restart:
TABLE_LIST element. Altough currently this assumption is valid
it may change in future.
*/
if (ot_ctx.recover_from_failed_open(thd))
if (ot_ctx.recover_from_failed_open())
goto err;
error= FALSE;
@ -4979,7 +4979,7 @@ restart:
{
close_tables_for_reopen(thd, start,
ot_ctx.start_of_statement_svp());
if (ot_ctx.recover_from_failed_open(thd))
if (ot_ctx.recover_from_failed_open())
goto err;
error= FALSE;
@ -5417,7 +5417,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type,
*/
thd->mdl_context.rollback_to_savepoint(ot_ctx.start_of_statement_svp());
table_list->mdl_request.ticket= 0;
if (ot_ctx.recover_from_failed_open(thd))
if (ot_ctx.recover_from_failed_open())
break;
}

View file

@ -1,4 +1,4 @@
/* Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved.
/* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -519,7 +519,7 @@ public:
};
Open_table_context(THD *thd, uint flags);
bool recover_from_failed_open(THD *thd);
bool recover_from_failed_open();
bool request_backoff_action(enum_open_table_action action_arg,
TABLE_LIST *table);
@ -559,6 +559,8 @@ public:
}
private:
/* THD for which tables are opened. */
THD *m_thd;
/**
For OT_DISCOVER and OT_REPAIR actions, the table list element for
the table which definition should be re-discovered or which

View file

@ -1,4 +1,4 @@
/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -1730,7 +1730,12 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
}
else
thd->lex->safe_to_cache_query= 0; // Don't try to cache this
/* End the statement transaction potentially started by engine. */
/*
End the statement transaction potentially started by engine.
Currently our engines do not request rollback from callbacks.
If this is going to change code needs to be reworked.
*/
DBUG_ASSERT(! thd->transaction_rollback_request);
trans_rollback_stmt(thd);
goto err_unlock; // Parse query
}

View file

@ -589,7 +589,10 @@ retry:
/*
Always close statement transaction explicitly,
so that the engine doesn't have to count locks.
There should be no need to perform transaction
rollback due to deadlock.
*/
DBUG_ASSERT(! thd->transaction_rollback_request);
trans_rollback_stmt(thd);
mysql_ha_close_table(thd, hash_tables);
goto retry;

View file

@ -1,4 +1,4 @@
/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -1183,6 +1183,18 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
close_thread_tables(thd);
thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
if (thd->transaction_rollback_request)
{
/*
Transaction rollback was requested since MDL deadlock was
discovered while trying to open tables. Rollback transaction
in all storage engines including binary log and release all
locks.
*/
trans_rollback_implicit(thd);
thd->mdl_context.release_transactional_locks();
}
thd->cleanup_after_query();
break;
}
@ -1833,7 +1845,7 @@ err:
can free its locks if LOCK TABLES locked some tables before finding
that it can't lock a table in its list
*/
trans_commit_implicit(thd);
trans_rollback(thd);
/* Close tables and release metadata locks. */
close_thread_tables(thd);
DBUG_ASSERT(!thd->locked_tables_mode);
@ -1884,6 +1896,13 @@ mysql_execute_command(THD *thd)
#endif
DBUG_ASSERT(thd->transaction.stmt.is_empty() || thd->in_sub_stmt);
/*
Each statement or replication event which might produce deadlock
should handle transaction rollback on its own. So by the start of
the next statement transaction rollback request should be fulfilled
already.
*/
DBUG_ASSERT(! thd->transaction_rollback_request || thd->in_sub_stmt);
/*
In many cases first table of main SELECT_LEX have special meaning =>
check that it is first table in global list and relink it first in
@ -2070,8 +2089,8 @@ mysql_execute_command(THD *thd)
or triggers as all such statements prohibited there.
*/
DBUG_ASSERT(! thd->in_sub_stmt);
/* Commit or rollback the statement transaction. */
thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
/* Statement transaction still should not be started. */
DBUG_ASSERT(thd->transaction.stmt.is_empty());
/* Commit the normal transaction if one is active. */
if (trans_commit_implicit(thd))
goto error;
@ -4503,7 +4522,17 @@ finish:
DEBUG_SYNC(thd, "execute_command_after_close_tables");
#endif
if (stmt_causes_implicit_commit(thd, CF_IMPLICIT_COMMIT_END))
if (! thd->in_sub_stmt && thd->transaction_rollback_request)
{
/*
We are not in sub-statement and transaction rollback was requested by
one of storage engines (e.g. due to deadlock). Rollback transaction in
all storage engines including binary log.
*/
trans_rollback_implicit(thd);
thd->mdl_context.release_transactional_locks();
}
else if (stmt_causes_implicit_commit(thd, CF_IMPLICIT_COMMIT_END))
{
/* No transaction control allowed in sub-statements. */
DBUG_ASSERT(! thd->in_sub_stmt);

View file

@ -1,4 +1,4 @@
/* Copyright (c) 2002, 2012, Oracle and/or its affiliates. All rights reserved.
/* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -113,6 +113,7 @@ When one supplies long data for a placeholder:
#include <mysql_com.h>
#endif
#include "lock.h" // MYSQL_OPEN_FORCE_SHARED_MDL
#include "transaction.h" // trans_rollback_implicit
/**
A result class used to send cursor rows using the binary protocol.
@ -3297,6 +3298,22 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
close_thread_tables(thd);
thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
/*
Transaction rollback was requested since MDL deadlock was discovered
while trying to open tables. Rollback transaction in all storage
engines including binary log and release all locks.
Once dynamic SQL is allowed as substatements the below if-statement
has to be adjusted to not do rollback in substatement.
*/
DBUG_ASSERT(! thd->in_sub_stmt);
if (thd->transaction_rollback_request)
{
trans_rollback_implicit(thd);
thd->mdl_context.release_transactional_locks();
}
lex_end(lex);
cleanup_stmt();
thd->restore_backup_statement(this, &stmt_backup);

View file

@ -4797,7 +4797,6 @@ mysql_discard_or_import_tablespace(THD *thd,
error= write_bin_log(thd, FALSE, thd->query(), thd->query_length());
err:
trans_rollback_stmt(thd);
thd->tablespace_op=FALSE;
if (error == 0)
@ -7331,6 +7330,12 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables,
Protocol *protocol= thd->protocol;
DBUG_ENTER("mysql_checksum_table");
/*
CHECKSUM TABLE returns results and rollbacks statement transaction,
so it should not be used in stored function or trigger.
*/
DBUG_ASSERT(! thd->in_sub_stmt);
field_list.push_back(item = new Item_empty_string("Table", NAME_LEN*2));
item->maybe_null= 1;
field_list.push_back(item= new Item_int("Checksum", (longlong) 1,
@ -7349,7 +7354,6 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables,
strxmov(table_name, table->db ,".", table->table_name, NullS);
t= table->table= open_n_lock_single_table(thd, table, TL_READ, 0);
thd->clear_error(); // these errors shouldn't get client
protocol->prepare_for_resend();
protocol->store(table_name, system_charset_info);
@ -7358,7 +7362,6 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables,
{
/* Table didn't exist */
protocol->store_null();
thd->clear_error();
}
else
{
@ -7443,9 +7446,7 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables,
t->file->ha_rnd_end();
}
}
thd->clear_error();
if (! thd->in_sub_stmt)
trans_rollback_stmt(thd);
trans_rollback_stmt(thd);
close_thread_tables(thd);
/*
Don't release metadata locks, this will be done at
@ -7453,6 +7454,21 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables,
*/
table->table=0; // For query cache
}
if (thd->transaction_rollback_request)
{
/*
If transaction rollback was requested we honor it. To do this we
abort statement and return error as not only CHECKSUM TABLE is
rolled back but the whole transaction in which it was used.
*/
thd->protocol->remove_last_row();
goto err;
}
/* Hide errors from client. Return NULL for problematic tables instead. */
thd->clear_error();
if (protocol->write())
goto err;
}

View file

@ -1,4 +1,4 @@
/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -267,6 +267,47 @@ bool trans_rollback(THD *thd)
}
/**
Implicitly rollback the current transaction, typically
after deadlock was discovered.
@param thd Current thread
@retval False Success
@retval True Failure
@note ha_rollback_low() which is indirectly called by this
function will mark XA transaction for rollback by
setting appropriate RM error status if there was
transaction rollback request.
*/
bool trans_rollback_implicit(THD *thd)
{
int res;
DBUG_ENTER("trans_rollback_implict");
/*
Always commit/rollback statement transaction before manipulating
with the normal one.
Don't perform rollback in the middle of sub-statement, wait till
its end.
*/
DBUG_ASSERT(thd->transaction.stmt.is_empty() && !thd->in_sub_stmt);
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
res= ha_rollback_trans(thd, true);
thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
thd->transaction.all.modified_non_trans_table= false;
/* Rollback should clear transaction_rollback_request flag. */
DBUG_ASSERT(! thd->transaction_rollback_request);
DBUG_RETURN(test(res));
}
/**
Commit the single statement transaction.
@ -339,8 +380,6 @@ bool trans_rollback_stmt(THD *thd)
if (thd->transaction.stmt.ha_list)
{
ha_rollback_trans(thd, FALSE);
if (thd->transaction_rollback_request && !thd->in_sub_stmt)
ha_rollback_trans(thd, TRUE);
if (! thd->in_active_multi_stmt_transaction())
thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
}

View file

@ -30,6 +30,7 @@ bool trans_begin(THD *thd, uint flags= 0);
bool trans_commit(THD *thd);
bool trans_commit_implicit(THD *thd);
bool trans_rollback(THD *thd);
bool trans_rollback_implicit(THD *thd);
bool trans_commit_stmt(THD *thd);
bool trans_rollback_stmt(THD *thd);