summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Lenev <Dmitry.Lenev@oracle.com>2013-08-20 13:12:34 +0400
committerDmitry Lenev <Dmitry.Lenev@oracle.com>2013-08-20 13:12:34 +0400
commitfc2c66929737594f5d6b31a25c65e1ec4a524664 (patch)
treebe0756ebecacc780efe430d822f5e093c1c5136b
parentc250dbf38501bfa8be51e7c2b3e0a6d8397b8403 (diff)
downloadmariadb-git-fc2c66929737594f5d6b31a25c65e1ec4a524664.tar.gz
Fix for bug#14188793 - "DEADLOCK CAUSED BY ALTER TABLE DOEN'T CLEAR
STATUS OF ROLLBACKED TRANSACTION" and bug #17054007 - "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.
-rw-r--r--mysql-test/include/handler.inc5
-rw-r--r--mysql-test/r/handler_innodb.result11
-rw-r--r--mysql-test/r/handler_myisam.result11
-rw-r--r--mysql-test/r/mdl_sync.result30
-rw-r--r--mysql-test/t/mdl_sync.test56
-rw-r--r--sql/ha_ndbcluster_binlog.cc8
-rw-r--r--sql/handler.cc12
-rw-r--r--sql/log_event.cc12
-rw-r--r--sql/log_event_old.cc5
-rw-r--r--sql/rpl_rli.cc11
-rw-r--r--sql/sp_head.cc34
-rw-r--r--sql/sql_admin.cc16
-rw-r--r--sql/sql_base.cc38
-rw-r--r--sql/sql_base.h6
-rw-r--r--sql/sql_cache.cc9
-rw-r--r--sql/sql_handler.cc3
-rw-r--r--sql/sql_parse.cc39
-rw-r--r--sql/sql_prepare.cc19
-rw-r--r--sql/sql_table.cc28
-rw-r--r--sql/transaction.cc45
-rw-r--r--sql/transaction.h1
21 files changed, 257 insertions, 142 deletions
diff --git a/mysql-test/include/handler.inc b/mysql-test/include/handler.inc
index 57d368960bf..2a015ca9c80 100644
--- a/mysql-test/include/handler.inc
+++ b/mysql-test/include/handler.inc
@@ -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;
diff --git a/mysql-test/r/handler_innodb.result b/mysql-test/r/handler_innodb.result
index dd4cac669c8..8f75ba7ff03 100644
--- a/mysql-test/r/handler_innodb.result
+++ b/mysql-test/r/handler_innodb.result
@@ -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;
diff --git a/mysql-test/r/handler_myisam.result b/mysql-test/r/handler_myisam.result
index 69d791b8263..2b328a9f02c 100644
--- a/mysql-test/r/handler_myisam.result
+++ b/mysql-test/r/handler_myisam.result
@@ -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;
diff --git a/mysql-test/r/mdl_sync.result b/mysql-test/r/mdl_sync.result
index b2e71faf741..4f929a5760f 100644
--- a/mysql-test/r/mdl_sync.result
+++ b/mysql-test/r/mdl_sync.result
@@ -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.
#
diff --git a/mysql-test/t/mdl_sync.test b/mysql-test/t/mdl_sync.test
index 197cad536e4..f6caa299fd4 100644
--- a/mysql-test/t/mdl_sync.test
+++ b/mysql-test/t/mdl_sync.test
@@ -2619,22 +2619,12 @@ 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;
--echo # Reap RENAME TABLE.
@@ -2696,24 +2686,12 @@ 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;
--echo # Reap RENAME TABLE ... .
@@ -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;
@@ -3578,19 +3545,6 @@ set debug_sync='mdl_acquire_lock_wait SIGNAL alter_go';
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;
--echo # Reap ALTER TABLE.
diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc
index 48edca0a705..15e0b068826 100644
--- a/sql/ha_ndbcluster_binlog.cc
+++ b/sql/ha_ndbcluster_binlog.cc
@@ -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;
diff --git a/sql/handler.cc b/sql/handler.cc
index 98d6e8fb103..c3ad1cade99 100644
--- a/sql/handler.cc
+++ b/sql/handler.cc
@@ -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();
diff --git a/sql/log_event.cc b/sql/log_event.cc
index d2682a3eb9e..31ee950dbe4 100644
--- a/sql/log_event.cc
+++ b/sql/log_event.cc
@@ -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));
/*
diff --git a/sql/log_event_old.cc b/sql/log_event_old.cc
index 55d8d72ec5d..1462b08e993 100644
--- a/sql/log_event_old.cc
+++ b/sql/log_event_old.cc
@@ -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, "
diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc
index 2d302011b44..e4f2e4fd382 100644
--- a/sql/rpl_rli.cc
+++ b/sql/rpl_rli.cc
@@ -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();
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
index 552a44c7a96..13d1b310599 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -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)
diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc
index 16fcf120128..f07a8089853 100644
--- a/sql/sql_admin.cc
+++ b/sql/sql_admin.cc
@@ -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();
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index a8279ec0347..040c0425577 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -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;
}
diff --git a/sql/sql_base.h b/sql/sql_base.h
index 96ca569dd1f..b118c93ac28 100644
--- a/sql/sql_base.h
+++ b/sql/sql_base.h
@@ -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
diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc
index 34a61e99007..ca3db3b48c0 100644
--- a/sql/sql_cache.cc
+++ b/sql/sql_cache.cc
@@ -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
}
diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc
index e20ee243b79..b179d54eadf 100644
--- a/sql/sql_handler.cc
+++ b/sql/sql_handler.cc
@@ -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;
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index ef3454ec9c9..dac42457a87 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -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);
@@ -1885,6 +1897,13 @@ mysql_execute_command(THD *thd)
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
queries_tables list if it is necessary (we need such relinking only
@@ -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);
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
index f3deafc6035..74279c5539d 100644
--- a/sql/sql_prepare.cc
+++ b/sql/sql_prepare.cc
@@ -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);
diff --git a/sql/sql_table.cc b/sql/sql_table.cc
index ac121572ab2..bfed754a90c 100644
--- a/sql/sql_table.cc
+++ b/sql/sql_table.cc
@@ -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;
}
diff --git a/sql/transaction.cc b/sql/transaction.cc
index 3b0af4db710..4fd6af39135 100644
--- a/sql/transaction.cc
+++ b/sql/transaction.cc
@@ -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
@@ -268,6 +268,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.
@note Note that if the autocommit is on, then the following call
@@ -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;
}
diff --git a/sql/transaction.h b/sql/transaction.h
index e002cd4a9dc..abe7823cf9b 100644
--- a/sql/transaction.h
+++ b/sql/transaction.h
@@ -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);