diff options
-rw-r--r-- | mysql-test/include/handler.inc | 5 | ||||
-rw-r--r-- | mysql-test/r/handler_innodb.result | 11 | ||||
-rw-r--r-- | mysql-test/r/handler_myisam.result | 11 | ||||
-rw-r--r-- | mysql-test/r/mdl_sync.result | 30 | ||||
-rw-r--r-- | mysql-test/t/mdl_sync.test | 56 | ||||
-rw-r--r-- | sql/ha_ndbcluster_binlog.cc | 8 | ||||
-rw-r--r-- | sql/handler.cc | 12 | ||||
-rw-r--r-- | sql/log_event.cc | 12 | ||||
-rw-r--r-- | sql/log_event_old.cc | 5 | ||||
-rw-r--r-- | sql/rpl_rli.cc | 11 | ||||
-rw-r--r-- | sql/sp_head.cc | 34 | ||||
-rw-r--r-- | sql/sql_admin.cc | 16 | ||||
-rw-r--r-- | sql/sql_base.cc | 38 | ||||
-rw-r--r-- | sql/sql_base.h | 6 | ||||
-rw-r--r-- | sql/sql_cache.cc | 9 | ||||
-rw-r--r-- | sql/sql_handler.cc | 3 | ||||
-rw-r--r-- | sql/sql_parse.cc | 39 | ||||
-rw-r--r-- | sql/sql_prepare.cc | 19 | ||||
-rw-r--r-- | sql/sql_table.cc | 28 | ||||
-rw-r--r-- | sql/transaction.cc | 45 | ||||
-rw-r--r-- | sql/transaction.h | 1 |
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); |