diff options
author | Marko Mäkelä <marko.makela@mariadb.com> | 2018-03-11 23:34:23 +0200 |
---|---|---|
committer | Marko Mäkelä <marko.makela@mariadb.com> | 2018-03-16 15:50:04 +0200 |
commit | bd7ed1b923e8ddd896103d73461c4313a175cca6 (patch) | |
tree | f7309f0a96715fe8582949820360bfc0dbb21b56 | |
parent | e15e879fae949a05de549a6676ae66d4f7f8c566 (diff) | |
download | mariadb-git-bd7ed1b923e8ddd896103d73461c4313a175cca6.tar.gz |
MDEV-13935 INSERT stuck at state Unlocking tables
Revert the dead code for MySQL 5.7 multi-master replication (GCS),
also known as
WL#6835: InnoDB: GCS Replication: Deterministic Deadlock Handling
(High Prio Transactions in InnoDB).
Also, make innodb_lock_schedule_algorithm=vats skip SPATIAL INDEX,
because the code does not seem to be compatible with them.
Add FIXME comments to some SPATIAL INDEX locking code. It looks
like Galera write-set replication might not work with SPATIAL INDEX.
27 files changed, 521 insertions, 2283 deletions
diff --git a/extra/mariabackup/xtrabackup.cc b/extra/mariabackup/xtrabackup.cc index e560ce1e042..c5bdec35f90 100644 --- a/extra/mariabackup/xtrabackup.cc +++ b/extra/mariabackup/xtrabackup.cc @@ -69,6 +69,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA #include <row0mysql.h> #include <row0quiesce.h> #include <srv0start.h> +#include "trx0sys.h" #include <buf0dblwr.h> #include <list> diff --git a/storage/innobase/gis/gis0rtree.cc b/storage/innobase/gis/gis0rtree.cc index b8220d73ec0..d45e40c8151 100644 --- a/storage/innobase/gis/gis0rtree.cc +++ b/storage/innobase/gis/gis0rtree.cc @@ -34,7 +34,7 @@ Created 2013/03/27 Allen Lai and Jimmy Yang #include "rem0cmp.h" #include "lock0lock.h" #include "ibuf0ibuf.h" -#include "trx0trx.h" +#include "trx0undo.h" #include "srv0mon.h" #include "gis0geo.h" diff --git a/storage/innobase/gis/gis0sea.cc b/storage/innobase/gis/gis0sea.cc index dcf8cc6f781..173fc76ddfc 100644 --- a/storage/innobase/gis/gis0sea.cc +++ b/storage/innobase/gis/gis0sea.cc @@ -37,6 +37,7 @@ Created 2014/01/16 Jimmy Yang #include "ibuf0ibuf.h" #include "trx0trx.h" #include "srv0mon.h" +#include "que0que.h" #include "gis0geo.h" /** Restore the stored position of a persistent cursor bufferfixing the page */ diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 2a860ff8e7c..b8e72f398f1 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -1721,41 +1721,6 @@ innobase_reset_background_thd(MYSQL_THD thd) } -#if 0 -/** -Check if the transaction can be rolled back -@param[in] requestor Session requesting the lock -@param[in] holder Session that holds the lock -@return the session that will be rolled back, null don't care */ - -THD* -thd_trx_arbitrate(THD* requestor, THD* holder) -{ - /* Non-user (thd==0) transactions by default can't rollback, in - practice DDL transactions should never rollback and that's because - they should never wait on table/record locks either */ - - ut_a(holder != NULL); - ut_a(holder != requestor); - - THD* victim = thd_tx_arbitrate(requestor, holder); - - ut_a(victim == NULL || victim == requestor || victim == holder); - - return(victim); -} - -/** -@param[in] thd Session to check -@return the priority */ - -int -thd_trx_priority(THD* thd) -{ - return(thd == NULL ? 0 : thd_tx_priority(thd)); -} -#endif - /******************************************************************//** Check if the transaction is an auto-commit transaction. TRUE also implies that it is a SELECT (read-only) transaction. @@ -2057,7 +2022,6 @@ convert_error_code_to_mysql( case DB_RECORD_NOT_FOUND: return(HA_ERR_NO_ACTIVE_RECORD); - case DB_FORCED_ABORT: case DB_DEADLOCK: /* Since we rolled back the whole transaction, we must tell it also to MySQL so that MySQL knows to empty the @@ -2879,10 +2843,6 @@ check_trx_exists( return trx; } else { trx = innobase_trx_allocate(thd); - /* User trx can be forced to rollback, - so we unset the disable flag. */ - ut_ad(trx->in_innodb & TRX_FORCE_ROLLBACK_DISABLE); - trx->in_innodb &= TRX_FORCE_ROLLBACK_MASK; thd_set_ha_data(thd, innodb_hton_ptr, trx); return trx; } @@ -3094,11 +3054,8 @@ ha_innobase::update_thd( trx_t* trx = check_trx_exists(thd); - TrxInInnoDB trx_in_innodb(trx); - - ut_ad(trx_in_innodb.is_aborted() - || (trx->dict_operation_lock_mode == 0 - && trx->dict_operation == TRX_DICT_OP_NONE)); + ut_ad(trx->dict_operation_lock_mode == 0); + ut_ad(trx->dict_operation == TRX_DICT_OP_NONE); if (m_prebuilt->trx != trx) { @@ -3564,8 +3521,6 @@ ha_innobase::init_table_handle_for_HANDLER(void) trx_start_if_not_started_xa(m_prebuilt->trx, false); - TrxInInnoDB trx_in_innodb(m_prebuilt->trx); - /* Assign a read view if the transaction does not have it yet */ trx_assign_read_view(m_prebuilt->trx); @@ -4549,8 +4504,6 @@ innobase_start_trx_and_assign_read_view( trx_t* trx = check_trx_exists(thd); - TrxInInnoDB trx_in_innodb(trx); - innobase_srv_conc_force_exit_innodb(trx); /* The transaction should not be active yet, start it */ @@ -4687,7 +4640,6 @@ innobase_commit_ordered( DBUG_ASSERT(hton == innodb_hton_ptr); trx = check_trx_exists(thd); - TrxInInnoDB trx_in_innodb(trx); if (!trx_is_registered_for_2pc(trx) && trx_is_started(trx)) { /* We cannot throw error here; instead we will catch this error @@ -4731,16 +4683,6 @@ innobase_commit( trx_t* trx = check_trx_exists(thd); - TrxInInnoDB trx_in_innodb(trx); - - if (trx_in_innodb.is_aborted()) { - - innobase_rollback(hton, thd, commit_trx); - - DBUG_RETURN(convert_error_code_to_mysql( - DB_FORCED_ABORT, 0, thd)); - } - ut_ad(trx->dict_operation_lock_mode == 0); ut_ad(trx->dict_operation == TRX_DICT_OP_NONE); @@ -4832,11 +4774,8 @@ innobase_rollback( trx_t* trx = check_trx_exists(thd); - TrxInInnoDB trx_in_innodb(trx); - - ut_ad(trx_in_innodb.is_aborted() - || (trx->dict_operation_lock_mode == 0 - && trx->dict_operation == TRX_DICT_OP_NONE)); + ut_ad(trx->dict_operation_lock_mode == 0); + ut_ad(trx->dict_operation == TRX_DICT_OP_NONE); innobase_srv_conc_force_exit_innodb(trx); @@ -4847,10 +4786,7 @@ innobase_rollback( /* If we had reserved the auto-inc lock for some table (if we come here to roll back the latest SQL statement) we release it now before a possibly lengthy rollback */ - - if (!trx_in_innodb.is_aborted()) { - lock_unlock_table_autoinc(trx); - } + lock_unlock_table_autoinc(trx); /* This is a statement level variable. */ @@ -4863,18 +4799,6 @@ innobase_rollback( error = trx_rollback_for_mysql(trx); - if (trx->state == TRX_STATE_FORCED_ROLLBACK) { -#ifndef DBUG_OFF - char buffer[1024]; - - DBUG_LOG("trx", "Forced rollback: " - << thd_get_error_context_description( - thd, buffer, sizeof buffer, 512)); -#endif /* !DBUG_OFF */ - - trx->state = TRX_STATE_NOT_STARTED; - } - trx_deregister_from_2pc(trx); } else { @@ -4901,9 +4825,7 @@ innobase_rollback_trx( /* If we had reserved the auto-inc lock for some table (if we come here to roll back the latest SQL statement) we release it now before a possibly lengthy rollback */ - if (!TrxInInnoDB::is_aborted(trx)) { - lock_unlock_table_autoinc(trx); - } + lock_unlock_table_autoinc(trx); if (!trx->has_logged()) { trx->will_lock = 0; @@ -5075,8 +4997,6 @@ innobase_rollback_to_savepoint( trx_t* trx = check_trx_exists(thd); - TrxInInnoDB trx_in_innodb(trx); - innobase_srv_conc_force_exit_innodb(trx); /* TODO: use provided savepoint data area to store savepoint data */ @@ -5117,8 +5037,6 @@ innobase_rollback_to_savepoint_can_release_mdl( trx_t* trx = check_trx_exists(thd); - TrxInInnoDB trx_in_innodb(trx); - /* If transaction has not acquired any locks then it is safe to release MDL after rollback to savepoint */ if (UT_LIST_GET_LEN(trx->lock.trx_locks) == 0) { @@ -5152,8 +5070,6 @@ innobase_release_savepoint( trx = check_trx_exists(thd); - TrxInInnoDB trx_in_innodb(trx); - /* TODO: use provided savepoint data area to store savepoint data */ longlong2str((ulint) savepoint, name, 36); @@ -5187,8 +5103,6 @@ innobase_savepoint( trx_t* trx = check_trx_exists(thd); - TrxInInnoDB trx_in_innodb(trx); - innobase_srv_conc_force_exit_innodb(trx); /* Cannot happen outside of transaction */ @@ -5224,7 +5138,6 @@ innobase_close_connection( DBUG_ASSERT(hton == innodb_hton_ptr); trx_t* trx = thd_to_trx(thd); - bool free_trx = false; /* During server initialization MySQL layer will try to open some of the master-slave tables those residing in InnoDB. @@ -5241,16 +5154,6 @@ innobase_close_connection( if (trx) { - TrxInInnoDB trx_in_innodb(trx); - - if (trx_in_innodb.is_aborted()) { - - while (trx_is_started(trx)) { - - os_thread_sleep(20); - } - } - if (!trx_is_registered_for_2pc(trx) && trx_is_started(trx)) { sql_print_error("Transaction not registered for MariaDB 2PC, " @@ -5268,9 +5171,8 @@ innobase_close_connection( if (trx->has_logged_persistent()) { trx_disconnect_prepared(trx); } else { - trx_rollback_for_mysql(trx); trx_deregister_from_2pc(trx); - free_trx = true; + goto rollback_and_free; } } else { sql_print_warning( @@ -5278,24 +5180,15 @@ innobase_close_connection( "InnoDB transaction. " TRX_ID_FMT " row modifications " "will roll back.", trx->undo_no); - ut_d(ib::warn() - << "trx: " << trx << " started on: " - << innobase_basename(trx->start_file) - << ":" << trx->start_line); - innobase_rollback_trx(trx); - free_trx = true; + goto rollback_and_free; } } else { +rollback_and_free: innobase_rollback_trx(trx); - free_trx = true; + trx_free_for_mysql(trx); } } - /* Free trx only after TrxInInnoDB is deleted. */ - if (free_trx) { - trx_free_for_mysql(trx); - } - DBUG_RETURN(0); } @@ -8320,15 +8213,6 @@ ha_innobase::write_row( DBUG_ENTER("ha_innobase::write_row"); trx_t* trx = thd_to_trx(m_user_thd); - TrxInInnoDB trx_in_innodb(trx); - - if (trx_in_innodb.is_aborted()) { - - innobase_rollback(ht, m_user_thd, false); - - DBUG_RETURN(convert_error_code_to_mysql( - DB_FORCED_ABORT, 0, m_user_thd)); - } /* Validation checks before we commence write_row operation. */ if (high_level_read_only) { @@ -9161,14 +9045,6 @@ ha_innobase::update_row( goto func_exit; } - if (TrxInInnoDB::is_aborted(trx)) { - - innobase_rollback(ht, m_user_thd, false); - - DBUG_RETURN(convert_error_code_to_mysql( - DB_FORCED_ABORT, 0, m_user_thd)); - } - /* This is not a delete */ m_prebuilt->upd_node->is_delete = FALSE; @@ -9258,18 +9134,9 @@ ha_innobase::delete_row( { dberr_t error; trx_t* trx = thd_to_trx(m_user_thd); - TrxInInnoDB trx_in_innodb(trx); DBUG_ENTER("ha_innobase::delete_row"); - if (trx_in_innodb.is_aborted()) { - - innobase_rollback(ht, m_user_thd, false); - - DBUG_RETURN(convert_error_code_to_mysql( - DB_FORCED_ABORT, 0, m_user_thd)); - } - ut_a(m_prebuilt->trx == trx); if (high_level_read_only) { @@ -9340,19 +9207,7 @@ ha_innobase::unlock_row(void) DBUG_VOID_RETURN; } - TrxInInnoDB trx_in_innodb(m_prebuilt->trx); - - if (trx_in_innodb.is_aborted()) { - DBUG_VOID_RETURN; - } - - /* Ideally, this assert must be in the beginning of the function. - But there are some calls to this function from the SQL layer when the - transaction is in state TRX_STATE_NOT_STARTED. The check on - m_prebuilt->select_lock_type above gets around this issue. */ - - ut_ad(trx_state_eq(m_prebuilt->trx, TRX_STATE_ACTIVE, true) - || trx_state_eq(m_prebuilt->trx, TRX_STATE_FORCED_ROLLBACK, true)); + ut_ad(trx_state_eq(m_prebuilt->trx, TRX_STATE_ACTIVE, true)); switch (m_prebuilt->row_read_type) { case ROW_READ_WITH_LOCKS: @@ -9634,14 +9489,6 @@ ha_innobase::index_read( innobase_srv_conc_enter_innodb(m_prebuilt); - if (TrxInInnoDB::is_aborted(m_prebuilt->trx)) { - - innobase_rollback(ht, m_user_thd, false); - - DBUG_RETURN(convert_error_code_to_mysql( - DB_FORCED_ABORT, 0, m_user_thd)); - } - ret = row_search_mvcc( buf, mode, m_prebuilt, match_mode, 0); @@ -9822,16 +9669,6 @@ ha_innobase::change_active_index( ut_ad(m_user_thd == ha_thd()); ut_a(m_prebuilt->trx == thd_to_trx(m_user_thd)); - TrxInInnoDB trx_in_innodb(m_prebuilt->trx); - - if (trx_in_innodb.is_aborted()) { - - innobase_rollback(ht, m_user_thd, false); - - DBUG_RETURN(convert_error_code_to_mysql( - DB_FORCED_ABORT, 0, m_user_thd)); - } - active_index = keynr; m_prebuilt->index = innobase_get_index(keynr); @@ -9950,14 +9787,6 @@ ha_innobase::general_fetch( ut_ad(trx == thd_to_trx(m_user_thd)); - if (TrxInInnoDB::is_aborted(trx)) { - - innobase_rollback(ht, m_user_thd, false); - - DBUG_RETURN(convert_error_code_to_mysql( - DB_FORCED_ABORT, 0, m_user_thd)); - } - if (m_prebuilt->table->is_readable()) { } else if (m_prebuilt->table->corrupted) { DBUG_RETURN(HA_ERR_CRASHED); @@ -10124,7 +9953,6 @@ ha_innobase::rnd_init( /*==================*/ bool scan) /*!< in: true if table/index scan FALSE otherwise */ { - TrxInInnoDB trx_in_innodb(m_prebuilt->trx); int err; /* Store the active index value so that we can restore the original @@ -10174,8 +10002,6 @@ ha_innobase::rnd_next( DBUG_ENTER("rnd_next"); - TrxInInnoDB trx_in_innodb(m_prebuilt->trx); - if (m_start_of_scan) { error = index_first(buf); @@ -10299,21 +10125,6 @@ ha_innobase::ft_init_ext( trx_t* trx = m_prebuilt->trx; - TrxInInnoDB trx_in_innodb(trx); - - if (trx_in_innodb.is_aborted()) { - - innobase_rollback(ht, m_user_thd, false); - - int err; - err = convert_error_code_to_mysql( - DB_FORCED_ABORT, 0, m_user_thd); - - my_error(err, MYF(0)); - - return(NULL); - } - /* FTS queries are not treated as autocommit non-locking selects. This is because the FTS implementation can acquire locks behind the scenes. This has not been verified but it is safer to treat @@ -10455,16 +10266,6 @@ ha_innobase::ft_read( /*=================*/ uchar* buf) /*!< in/out: buf contain result row */ { - TrxInInnoDB trx_in_innodb(m_prebuilt->trx); - - if (trx_in_innodb.is_aborted()) { - - innobase_rollback(ht, m_user_thd, false); - - return(convert_error_code_to_mysql( - DB_FORCED_ABORT, 0, m_user_thd)); - } - row_prebuilt_t* ft_prebuilt; ft_prebuilt = reinterpret_cast<NEW_FT_INFO*>(ft_handler)->ft_prebuilt; @@ -13268,15 +13069,6 @@ ha_innobase::discard_or_import_tablespace( DBUG_RETURN(HA_ERR_TABLE_NEEDS_UPGRADE); } - TrxInInnoDB trx_in_innodb(m_prebuilt->trx); - - if (trx_in_innodb.is_aborted()) { - innobase_rollback(ht, m_user_thd, false); - - DBUG_RETURN(convert_error_code_to_mysql( - DB_FORCED_ABORT, 0, m_user_thd)); - } - trx_start_if_not_started(m_prebuilt->trx, true); /* Obtain an exclusive lock on the table. */ @@ -13376,8 +13168,6 @@ ha_innobase::truncate() update_thd(ha_thd()); - TrxInInnoDB trx_in_innodb(m_prebuilt->trx); - if (!trx_is_started(m_prebuilt->trx)) { ++m_prebuilt->trx->will_lock; } @@ -13452,8 +13242,6 @@ ha_innobase::delete_table( trx_t* parent_trx = check_trx_exists(thd); - TrxInInnoDB trx_in_innodb(parent_trx); - /* Remove the to-be-dropped table from the list of modified tables by parent_trx. Otherwise we may end up with an orphaned pointer to the table object from parent_trx::mod_tables. This could happen in: @@ -13697,8 +13485,6 @@ innobase_rename_table( DEBUG_SYNC_C("innodb_rename_table_ready"); - TrxInInnoDB trx_in_innodb(trx); - trx_start_if_not_started(trx, true); /* Serialize data dictionary operations with dictionary mutex: @@ -13790,13 +13576,6 @@ ha_innobase::rename_table( DBUG_RETURN(HA_ERR_TABLE_READONLY); } - /* Get the transaction associated with the current thd, or create one - if not yet created */ - - trx_t* parent_trx = check_trx_exists(thd); - - TrxInInnoDB trx_in_innodb(parent_trx); - trx_t* trx = innobase_trx_allocate(thd); /* We are doing a DDL operation. */ @@ -13880,8 +13659,6 @@ ha_innobase::records_in_range( m_prebuilt->trx->op_info = "estimating records in index range"; - TrxInInnoDB trx_in_innodb(m_prebuilt->trx); - active_index = keynr; key = table->key_info + active_index; @@ -14013,8 +13790,6 @@ ha_innobase::estimate_rows_upper_bound() update_thd(ha_thd()); - TrxInInnoDB trx_in_innodb(m_prebuilt->trx); - m_prebuilt->trx->op_info = "calculating upper bound for table rows"; index = dict_table_get_first_index(m_prebuilt->table); @@ -14856,8 +14631,6 @@ ha_innobase::optimize( HA_CHECK_OPT* check_opt) /*!< in: currently ignored */ { - TrxInInnoDB trx_in_innodb(m_prebuilt->trx); - /* FTS-FIXME: Since MySQL doesn't support engine-specific commands, we have to hijack some existing command in order to be able to test the new admin commands added in InnoDB's FTS support. For now, we @@ -14926,8 +14699,6 @@ ha_innobase::check( ut_a(m_prebuilt->trx->magic_n == TRX_MAGIC_N); ut_a(m_prebuilt->trx == thd_to_trx(thd)); - TrxInInnoDB trx_in_innodb(m_prebuilt->trx); - if (m_prebuilt->mysql_template == NULL) { /* Build the template; we will use a dummy template in index scans done in checking */ @@ -15418,8 +15189,6 @@ ha_innobase::get_foreign_key_list( { update_thd(ha_thd()); - TrxInInnoDB trx_in_innodb(m_prebuilt->trx); - m_prebuilt->trx->op_info = "getting list of foreign keys"; mutex_enter(&dict_sys->mutex); @@ -15458,8 +15227,6 @@ ha_innobase::get_parent_foreign_key_list( { update_thd(ha_thd()); - TrxInInnoDB trx_in_innodb(m_prebuilt->trx); - m_prebuilt->trx->op_info = "getting list of referencing foreign keys"; mutex_enter(&dict_sys->mutex); @@ -15556,8 +15323,6 @@ ha_innobase::get_cascade_foreign_key_table_list( THD* thd, List<st_handler_tablename>* fk_table_list) { - TrxInInnoDB trx_in_innodb(m_prebuilt->trx); - m_prebuilt->trx->op_info = "getting cascading foreign keys"; std::list<table_list_item, ut_allocator<table_list_item> > table_list; @@ -15807,15 +15572,6 @@ ha_innobase::end_stmt() /* This is a statement level counter. */ m_prebuilt->autoinc_last_value = 0; - /* This transaction had called ha_innobase::start_stmt() */ - trx_t* trx = m_prebuilt->trx; - - if (trx->lock.start_stmt) { - TrxInInnoDB::end_stmt(trx); - - trx->lock.start_stmt = false; - } - return(0); } @@ -15855,8 +15611,6 @@ ha_innobase::start_stmt( ut_ad(m_prebuilt->table != NULL); - TrxInInnoDB trx_in_innodb(trx); - trx = m_prebuilt->trx; innobase_srv_conc_force_exit_innodb(trx); @@ -15929,14 +15683,6 @@ ha_innobase::start_stmt( ++trx->will_lock; } - /* Only do it once per transaction. */ - if (!trx->lock.start_stmt && lock_type != TL_UNLOCK) { - - TrxInInnoDB::begin_stmt(trx); - - trx->lock.start_stmt = true; - } - DBUG_RETURN(0); } @@ -16167,13 +15913,8 @@ ha_innobase::external_lock( ++trx->will_lock; } - TrxInInnoDB::begin_stmt(trx); - DBUG_RETURN(0); } else { - - TrxInInnoDB::end_stmt(trx); - DEBUG_SYNC_C("ha_innobase_end_statement"); } @@ -16268,8 +16009,6 @@ innodb_show_status( innobase_srv_conc_force_exit_innodb(trx); - TrxInInnoDB trx_in_innodb(trx); - /* We let the InnoDB Monitor to output at most MAX_STATUS_SIZE bytes of text. */ @@ -16854,8 +16593,6 @@ ha_innobase::store_lock( trx_t* trx = check_trx_exists(thd); - TrxInInnoDB trx_in_innodb(trx); - /* NOTE: MySQL can call this function with lock 'type' TL_IGNORE! Be careful to ignore TL_IGNORE if we are going to do something with only 'real' locks! */ @@ -17176,8 +16913,6 @@ ha_innobase::get_auto_increment( trx = m_prebuilt->trx; - TrxInInnoDB trx_in_innodb(trx); - /* Note: We can't rely on *first_value since some MySQL engines, in particular the partition engine, don't initialize it to 0 when invoking this method. So we are not sure if it's guaranteed to @@ -17578,16 +17313,6 @@ innobase_xa_prepare( innobase_srv_conc_force_exit_innodb(trx); - TrxInInnoDB trx_in_innodb(trx); - - if (trx_in_innodb.is_aborted()) { - - innobase_rollback(hton, thd, prepare_trx); - - return(convert_error_code_to_mysql( - DB_FORCED_ABORT, 0, thd)); - } - if (!trx_is_registered_for_2pc(trx) && trx_is_started(trx)) { sql_print_error("Transaction not registered for MariaDB 2PC," @@ -17602,18 +17327,7 @@ innobase_xa_prepare( ut_ad(trx_is_registered_for_2pc(trx)); - dberr_t err = trx_prepare_for_mysql(trx); - - ut_ad(err == DB_SUCCESS || err == DB_FORCED_ABORT); - - if (err == DB_FORCED_ABORT) { - - innobase_rollback(hton, thd, prepare_trx); - - return(convert_error_code_to_mysql( - DB_FORCED_ABORT, 0, thd)); - } - + trx_prepare_for_mysql(trx); } else { /* We just mark the SQL statement ended and do not do a transaction prepare */ @@ -17690,14 +17404,10 @@ innobase_commit_by_xid( } if (trx_t* trx = trx_get_trx_by_xid(xid)) { - ut_ad(trx->in_innodb & TRX_FORCE_ROLLBACK_DISABLE); /* use cases are: disconnected xa, slave xa, recovery */ - { - TrxInInnoDB trx_in_innodb(trx); - innobase_commit_low(trx); - ut_ad(trx->mysql_thd == NULL); - trx_deregister_from_2pc(trx); - } + innobase_commit_low(trx); + ut_ad(trx->mysql_thd == NULL); + trx_deregister_from_2pc(trx); ut_ad(!trx->will_lock); /* trx cache requirement */ trx_free_for_background(trx); @@ -17726,14 +17436,9 @@ innobase_rollback_by_xid( } if (trx_t* trx = trx_get_trx_by_xid(xid)) { - int ret; - ut_ad(trx->in_innodb & TRX_FORCE_ROLLBACK_DISABLE); - { - TrxInInnoDB trx_in_innodb(trx); - ret = innobase_rollback_trx(trx); - trx_deregister_from_2pc(trx); - ut_ad(!trx->will_lock); - } + int ret = innobase_rollback_trx(trx); + trx_deregister_from_2pc(trx); + ut_ad(!trx->will_lock); trx_free_for_background(trx); return(ret); diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc index c7368a43192..074401e37d9 100644 --- a/storage/innobase/handler/handler0alter.cc +++ b/storage/innobase/handler/handler0alter.cc @@ -2804,8 +2804,7 @@ online_retry_drop_indexes_with_trx( dict_table_t* table, /*!< in/out: table */ trx_t* trx) /*!< in/out: transaction */ { - ut_ad(trx_state_eq(trx, TRX_STATE_NOT_STARTED) - || trx_state_eq(trx, TRX_STATE_FORCED_ROLLBACK)); + ut_ad(trx_state_eq(trx, TRX_STATE_NOT_STARTED)); ut_ad(trx->dict_operation_lock_mode == RW_X_LATCH); diff --git a/storage/innobase/include/ha_prototypes.h b/storage/innobase/include/ha_prototypes.h index 5b97b4b3a88..86defe9b166 100644 --- a/storage/innobase/include/ha_prototypes.h +++ b/storage/innobase/include/ha_prototypes.h @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 2006, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2017, MariaDB Corporation. All Rights Reserved. +Copyright (c) 2017, 2018, MariaDB Corporation. 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 the Free Software @@ -361,27 +361,6 @@ thd_trx_is_read_only( /*=================*/ THD* thd); /*!< in/out: thread handle */ -#if 0 -/** -Check if the transaction can be rolled back -@param[in] requestor Session requesting the lock -@param[in] holder Session that holds the lock -@return the session that will be rolled back, null don't care */ - -THD* -thd_trx_arbitrate(THD* requestor, THD* holder); - -/** -@param[in] thd Session to check -@return the priority */ - -int -thd_trx_priority(THD* thd); - -#else -static inline THD* thd_trx_arbitrate(THD*, THD*) { return NULL; } -static inline int thd_trx_priority(THD*) { return 0; } -#endif /******************************************************************//** Check if the transaction is an auto-commit transaction. TRUE also implies that it is a SELECT (read-only) transaction. diff --git a/storage/innobase/include/hash0hash.h b/storage/innobase/include/hash0hash.h index 3d099cd2f3a..cbb6da488b5 100644 --- a/storage/innobase/include/hash0hash.h +++ b/storage/innobase/include/hash0hash.h @@ -1,6 +1,7 @@ /***************************************************************************** Copyright (c) 1997, 2016, Oracle and/or its affiliates. All Rights Reserved. +Copyright (c) 2018, MariaDB Corporation. 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 the Free Software @@ -122,7 +123,6 @@ do {\ }\ } while (0) -#ifdef WITH_WSREP /*******************************************************************//** Inserts a struct to the head of hash table. */ @@ -148,7 +148,6 @@ do { \ cell3333->node = DATA; \ } \ } while (0) -#endif /*WITH_WSREP */ #ifdef UNIV_HASH_DEBUG # define HASH_ASSERT_VALID(DATA) ut_a((void*) (DATA) != (void*) -1) # define HASH_INVALIDATE(DATA, NAME) *(void**) (&DATA->NAME) = (void*) -1 diff --git a/storage/innobase/include/lock0lock.h b/storage/innobase/include/lock0lock.h index 03535b6aaed..ddbbdeebfee 100644 --- a/storage/innobase/include/lock0lock.h +++ b/storage/innobase/include/lock0lock.h @@ -954,6 +954,30 @@ struct lock_sys_t{ is running */ }; +/*********************************************************************//** +Creates a new record lock and inserts it to the lock queue. Does NOT check +for deadlocks or lock compatibility! +@return created lock */ +UNIV_INLINE +lock_t* +lock_rec_create( +/*============*/ +#ifdef WITH_WSREP + lock_t* c_lock, /*!< conflicting lock */ + que_thr_t* thr, /*!< thread owning trx */ +#endif + ulint type_mode,/*!< in: lock mode and wait + flag, type is ignored and + replaced by LOCK_REC */ + const buf_block_t* block, /*!< in: buffer block containing + the record */ + ulint heap_no,/*!< in: heap number of the record */ + dict_index_t* index, /*!< in: index of record */ + trx_t* trx, /*!< in,out: transaction */ + bool caller_owns_trx_mutex); + /*!< in: true if caller owns + trx mutex */ + /*************************************************************//** Removes a record lock request, waiting or granted, from the queue. */ void @@ -963,6 +987,61 @@ lock_rec_discard( record locks which are contained in this lock object are removed */ +/** Create a new record lock and inserts it to the lock queue, +without checking for deadlocks or conflicts. +@param[in] type_mode lock mode and wait flag; type will be replaced + with LOCK_REC +@param[in] space tablespace id +@param[in] page_no index page number +@param[in] page R-tree index page, or NULL +@param[in] heap_no record heap number in the index page +@param[in] index the index tree +@param[in,out] trx transaction +@param[in] holds_trx_mutex whether the caller holds trx->mutex +@return created lock */ +lock_t* +lock_rec_create_low( +#ifdef WITH_WSREP + lock_t* c_lock, /*!< conflicting lock */ + que_thr_t* thr, /*!< thread owning trx */ +#endif + ulint type_mode, + ulint space, + ulint page_no, + const page_t* page, + ulint heap_no, + dict_index_t* index, + trx_t* trx, + bool holds_trx_mutex); +/** Enqueue a waiting request for a lock which cannot be granted immediately. +Check for deadlocks. +@param[in] type_mode the requested lock mode (LOCK_S or LOCK_X) + possibly ORed with LOCK_GAP or + LOCK_REC_NOT_GAP, ORed with + LOCK_INSERT_INTENTION if this + waiting lock request is set + when performing an insert of + an index record +@param[in] block leaf page in the index +@param[in] heap_no record heap number in the block +@param[in] index index tree +@param[in,out] thr query thread +@param[in] prdt minimum bounding box (spatial index) +@retval DB_LOCK_WAIT if the waiting lock was enqueued +@retval DB_DEADLOCK if this transaction was chosen as the victim +@retval DB_SUCCESS_LOCKED_REC if the other transaction was chosen as a victim + (or it happened to commit) */ +dberr_t +lock_rec_enqueue_waiting( +#ifdef WITH_WSREP + lock_t* c_lock, /*!< conflicting lock */ +#endif + ulint type_mode, + const buf_block_t* block, + ulint heap_no, + dict_index_t* index, + que_thr_t* thr, + lock_prdt_t* prdt); /*************************************************************//** Moves the explicit locks on user records to another page if a record list start is moved to another page. */ diff --git a/storage/innobase/include/lock0lock.ic b/storage/innobase/include/lock0lock.ic index b73843e7a1f..475f2ccedf1 100644 --- a/storage/innobase/include/lock0lock.ic +++ b/storage/innobase/include/lock0lock.ic @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1996, 2015, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2017, MariaDB Corporation. +Copyright (c) 2017, 2018, MariaDB Corporation. 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 the Free Software @@ -24,19 +24,9 @@ The transaction lock system Created 5/7/1996 Heikki Tuuri *******************************************************/ -#include "srv0srv.h" #include "dict0dict.h" -#include "row0row.h" -#include "trx0sys.h" -#include "trx0trx.h" #include "buf0buf.h" #include "page0page.h" -#include "page0cur.h" -#include "row0vers.h" -#include "que0que.h" -#include "btr0cur.h" -#include "read0read.h" -#include "log0recv.h" /*********************************************************************//** Calculates the fold value of a page file address: used in inserting or @@ -108,3 +98,37 @@ lock_hash_get( } } +/*********************************************************************//** +Creates a new record lock and inserts it to the lock queue. Does NOT check +for deadlocks or lock compatibility! +@return created lock */ +UNIV_INLINE +lock_t* +lock_rec_create( +/*============*/ +#ifdef WITH_WSREP + lock_t* c_lock, /*!< conflicting lock */ + que_thr_t* thr, /*!< thread owning trx */ +#endif + ulint type_mode,/*!< in: lock mode and wait + flag, type is ignored and + replaced by LOCK_REC */ + const buf_block_t* block, /*!< in: buffer block containing + the record */ + ulint heap_no,/*!< in: heap number of the record */ + dict_index_t* index, /*!< in: index of record */ + trx_t* trx, /*!< in,out: transaction */ + bool caller_owns_trx_mutex) + /*!< in: TRUE if caller owns + trx mutex */ +{ + btr_assert_not_corrupted(block, index); + return lock_rec_create_low( +#ifdef WITH_WSREP + c_lock, thr, +#endif + type_mode, + block->page.id.space(), block->page.id.page_no(), + block->frame, heap_no, + index, trx, caller_owns_trx_mutex); +} diff --git a/storage/innobase/include/lock0priv.h b/storage/innobase/include/lock0priv.h index 43f59151991..0f35e0ca6d0 100644 --- a/storage/innobase/include/lock0priv.h +++ b/storage/innobase/include/lock0priv.h @@ -562,407 +562,6 @@ enum lock_rec_req_status { LOCK_REC_SUCCESS_CREATED }; -/** -Record lock ID */ -struct RecID { - - RecID(ulint space_id, ulint page_no, ulint heap_no) - : - m_space_id(static_cast<uint32_t>(space_id)), - m_page_no(static_cast<uint32_t>(page_no)), - m_heap_no(static_cast<uint32_t>(heap_no)), - m_fold(lock_rec_fold(m_space_id, m_page_no)) - { - ut_ad(space_id < UINT32_MAX); - ut_ad(page_no < UINT32_MAX); - ut_ad(heap_no < UINT32_MAX); - } - - RecID(const buf_block_t* block, ulint heap_no) - : - m_space_id(block->page.id.space()), - m_page_no(block->page.id.page_no()), - m_heap_no(static_cast<uint32_t>(heap_no)), - m_fold(lock_rec_fold(m_space_id, m_page_no)) - { - ut_ad(heap_no < UINT32_MAX); - } - - /** - @return the "folded" value of {space, page_no} */ - ulint fold() const - { - return(m_fold); - } - - /** - Tablespace ID */ - uint32_t m_space_id; - - /** - Page number within the space ID */ - uint32_t m_page_no; - - /** - Heap number within the page */ - uint32_t m_heap_no; - - /** - Hashed key value */ - ulint m_fold; -}; - -/** -Create record locks */ -class RecLock { -public: - - /** - @param[in,out] thr Transaction query thread requesting the record - lock - @param[in] index Index on which record lock requested - @param[in] rec_id Record lock tuple {space, page_no, heap_no} - @param[in] mode The lock mode */ - RecLock(que_thr_t* thr, - dict_index_t* index, - const RecID& rec_id, - ulint mode) - : - m_thr(thr), - m_trx(thr_get_trx(thr)), - m_mode(mode), - m_index(index), - m_rec_id(rec_id) - { - ut_ad(is_predicate_lock(m_mode)); - - init(NULL); - } - - /** - @param[in,out] thr Transaction query thread requesting the record - lock - @param[in] index Index on which record lock requested - @param[in] block Buffer page containing record - @param[in] heap_no Heap number within the block - @param[in] mode The lock mode - @param[in] prdt The predicate for the rtree lock */ - RecLock(que_thr_t* thr, - dict_index_t* index, - const buf_block_t* - block, - ulint heap_no, - ulint mode, - lock_prdt_t* prdt = NULL) - : - m_thr(thr), - m_trx(thr_get_trx(thr)), - m_mode(mode), - m_index(index), - m_rec_id(block, heap_no) - { - btr_assert_not_corrupted(block, index); - - init(block->frame); - } - - /** - @param[in] index Index on which record lock requested - @param[in] rec_id Record lock tuple {space, page_no, heap_no} - @param[in] mode The lock mode */ - RecLock(dict_index_t* index, - const RecID& rec_id, - ulint mode) - : - m_thr(), - m_trx(), - m_mode(mode), - m_index(index), - m_rec_id(rec_id) - { - ut_ad(is_predicate_lock(m_mode)); - - init(NULL); - } - - /** - @param[in] index Index on which record lock requested - @param[in] block Buffer page containing record - @param[in] heap_no Heap number withing block - @param[in] mode The lock mode */ - RecLock(dict_index_t* index, - const buf_block_t* - block, - ulint heap_no, - ulint mode) - : - m_thr(), - m_trx(), - m_mode(mode), - m_index(index), - m_rec_id(block, heap_no) - { - btr_assert_not_corrupted(block, index); - - init(block->frame); - } - - /** - Enqueue a lock wait for a transaction. If it is a high priority - transaction (cannot rollback) then jump ahead in the record lock wait - queue and if the transaction at the head of the queue is itself waiting - roll it back. - @param[in, out] wait_for The lock that the the joining - transaction is waiting for - @param[in] prdt Predicate [optional] - @return DB_LOCK_WAIT, DB_DEADLOCK, or - DB_SUCCESS_LOCKED_REC; DB_SUCCESS_LOCKED_REC means that - there was a deadlock, but another transaction was chosen - as a victim, and we got the lock immediately: no need to - wait then */ - dberr_t add_to_waitq( - lock_t* wait_for, - const lock_prdt_t* - prdt = NULL); - - /** - Create a lock for a transaction and initialise it. - @param[in, out] trx Transaction requesting the new lock - @param[in] owns_trx_mutex true if caller owns the trx_t::mutex - @param[in] add_to_hash add the lock to hash table - @param[in] prdt Predicate lock (optional) - @param[in,out] c_lock Conflicting lock request or NULL - in Galera conflicting lock is selected - as deadlock victim if requester - is BF transaction. - @return new lock instance */ - lock_t* create( - trx_t* trx, - bool owns_trx_mutex, - bool add_to_hash, - const lock_prdt_t* - prdt = NULL -#ifdef WITH_WSREP - ,lock_t* c_lock = NULL -#endif /* WITH_WSREP */ - ); - - /** - Check of the lock is on m_rec_id. - @param[in] lock Lock to compare with - @return true if the record lock is on m_rec_id*/ - bool is_on_row(const lock_t* lock) const; - - /** - Create the lock instance - @param[in, out] trx The transaction requesting the lock - @param[in, out] index Index on which record lock is required - @param[in] mode The lock mode desired - @param[in] rec_id The record id - @param[in] size Size of the lock + bitmap requested - @return a record lock instance */ - static lock_t* lock_alloc( - trx_t* trx, - dict_index_t* index, - ulint mode, - const RecID& rec_id, - ulint size); - -private: - /* - @return the record lock size in bytes */ - size_t lock_size() const - { - return(m_size); - } - - /** - Do some checks and prepare for creating a new record lock */ - void prepare() const; - - /** - Collect the transactions that will need to be rolled back asynchronously - @param[in, out] trx Transaction to be rolled back */ - void mark_trx_for_rollback(trx_t* trx); - - /** - Jump the queue for the record over all low priority transactions and - add the lock. If all current granted locks are compatible, grant the - lock. Otherwise, mark all granted transaction for asynchronous - rollback and add to hit list. - @param[in, out] lock Lock being requested - @param[in] conflict_lock First conflicting lock from the head - @return true if the lock is granted */ - bool jump_queue(lock_t* lock, const lock_t* conflict_lock); - - /** Find position in lock queue and add the high priority transaction - lock. Intention and GAP only locks can be granted even if there are - waiting locks in front of the queue. To add the High priority - transaction in a safe position we keep the following rule. - - 1. If the lock can be granted, add it before the first waiting lock - in the queue so that all currently waiting locks need to do conflict - check before getting granted. - - 2. If the lock has to wait, add it after the last granted lock or the - last waiting high priority transaction in the queue whichever is later. - This ensures that the transaction is granted only after doing conflict - check with all granted transactions. - @param[in] lock Lock being requested - @param[in] conflict_lock First conflicting lock from the head - @param[out] high_priority high priority transaction ahead in queue - @return true if the lock can be granted */ - bool - lock_add_priority( - lock_t* lock, - const lock_t* conflict_lock, - bool* high_priority); - - /** Iterate over the granted locks and prepare the hit list for ASYNC Rollback. - If the transaction is waiting for some other lock then wake up with deadlock error. - Currently we don't mark following transactions for ASYNC Rollback. - 1. Read only transactions - 2. Background transactions - 3. Other High priority transactions - @param[in] lock Lock being requested - @param[in] conflict_lock First conflicting lock from the head */ - void make_trx_hit_list(lock_t* lock, const lock_t* conflict_lock); - - /** - Setup the requesting transaction state for lock grant - @param[in,out] lock Lock for which to change state */ - void set_wait_state(lock_t* lock); - - /** - Add the lock to the record lock hash and the transaction's lock list - @param[in,out] lock Newly created record lock to add to the - rec hash and the transaction lock list - @param[in] add_to_hash If the lock should be added to the hash table */ - void lock_add(lock_t* lock, bool add_to_hash); - - /** - Check and resolve any deadlocks - @param[in, out] lock The lock being acquired - @return DB_LOCK_WAIT, DB_DEADLOCK, or - DB_SUCCESS_LOCKED_REC; DB_SUCCESS_LOCKED_REC means that - there was a deadlock, but another transaction was chosen - as a victim, and we got the lock immediately: no need to - wait then */ - dberr_t deadlock_check(lock_t* lock); - - /** - Check the outcome of the deadlock check - @param[in,out] victim_trx Transaction selected for rollback - @param[in,out] lock Lock being requested - @return DB_LOCK_WAIT, DB_DEADLOCK or DB_SUCCESS_LOCKED_REC */ - dberr_t check_deadlock_result(const trx_t* victim_trx, lock_t* lock); - - /** - Setup the context from the requirements */ - void init(const page_t* page) - { - ut_ad(lock_mutex_own()); - ut_ad(!srv_read_only_mode); - ut_ad(dict_index_is_clust(m_index) - || !dict_index_is_online_ddl(m_index)); - ut_ad(m_thr == NULL || m_trx == thr_get_trx(m_thr)); - - m_size = is_predicate_lock(m_mode) - ? lock_size(m_mode) : lock_size(page); - - /** If rec is the supremum record, then we reset the - gap and LOCK_REC_NOT_GAP bits, as all locks on the - supremum are automatically of the gap type */ - - if (m_rec_id.m_heap_no == PAGE_HEAP_NO_SUPREMUM) { - ut_ad(!(m_mode & LOCK_REC_NOT_GAP)); - - m_mode &= ~(LOCK_GAP | LOCK_REC_NOT_GAP); - } - } - - /** - Calculate the record lock physical size required for a predicate lock. - @param[in] mode For predicate locks the lock mode - @return the size of the lock data structure required in bytes */ - static size_t lock_size(ulint mode) - { - ut_ad(is_predicate_lock(mode)); - - /* The lock is always on PAGE_HEAP_NO_INFIMUM(0), - so we only need 1 bit (which is rounded up to 1 - byte) for lock bit setting */ - - size_t n_bytes; - - if (mode & LOCK_PREDICATE) { - const ulint align = UNIV_WORD_SIZE - 1; - - /* We will attach the predicate structure - after lock. Make sure the memory is - aligned on 8 bytes, the mem_heap_alloc - will align it with MEM_SPACE_NEEDED - anyway. */ - - n_bytes = (1 + sizeof(lock_prdt_t) + align) & ~align; - - /* This should hold now */ - - ut_ad(n_bytes == sizeof(lock_prdt_t) + UNIV_WORD_SIZE); - - } else { - n_bytes = 1; - } - - return(n_bytes); - } - - /** - Calculate the record lock physical size required, non-predicate lock. - @param[in] page For non-predicate locks the buffer page - @return the size of the lock data structure required in bytes */ - static size_t lock_size(const page_t* page) - { - ulint n_recs = page_dir_get_n_heap(page); - - /* Make lock bitmap bigger by a safety margin */ - - return(1 + ((n_recs + LOCK_PAGE_BITMAP_MARGIN) / 8)); - } - - /** - @return true if the requested lock mode is for a predicate - or page lock */ - static bool is_predicate_lock(ulint mode) - { - return(mode & (LOCK_PREDICATE | LOCK_PRDT_PAGE)); - } - -private: - /** The query thread of the transaction */ - que_thr_t* m_thr; - - /** - Transaction requesting the record lock */ - trx_t* m_trx; - - /** - Lock mode requested */ - ulint m_mode; - - /** - Size of the record lock in bytes */ - size_t m_size; - - /** - Index on which the record lock is required */ - dict_index_t* m_index; - - /** - The record lock tuple {space, page_no, heap_no} */ - RecID m_rec_id; -}; - #ifdef UNIV_DEBUG /** The count of the types of locks. */ static const ulint lock_types = UT_ARR_SIZE(lock_compatibility_matrix); diff --git a/storage/innobase/include/lock0priv.ic b/storage/innobase/include/lock0priv.ic index f6e5f7acb8f..150a80b7be4 100644 --- a/storage/innobase/include/lock0priv.ic +++ b/storage/innobase/include/lock0priv.ic @@ -32,6 +32,8 @@ methods but they are used only in that file. */ #error Do not include lock0priv.ic outside of the lock/ module #endif +#include "row0row.h" + /*********************************************************************//** Gets the type of a lock. @return LOCK_TABLE or LOCK_REC */ diff --git a/storage/innobase/include/trx0trx.h b/storage/innobase/include/trx0trx.h index 50e270fb3c5..6ed69e6c5c4 100644 --- a/storage/innobase/include/trx0trx.h +++ b/storage/innobase/include/trx0trx.h @@ -28,13 +28,11 @@ Created 3/26/1996 Heikki Tuuri #define trx0trx_h #include <set> -#include <list> #include "ha_prototypes.h" #include "dict0types.h" #include "trx0types.h" -#include "ut0new.h" #include "lock0types.h" #include "log0log.h" @@ -43,7 +41,6 @@ Created 3/26/1996 Heikki Tuuri #include "trx0xa.h" #include "ut0vec.h" #include "fts0fts.h" -#include "srv0srv.h" // Forward declaration struct mtr_t; @@ -255,14 +252,9 @@ dberr_t trx_commit_for_mysql( /*=================*/ trx_t* trx); /*!< in/out: transaction */ - -/** -Does the transaction prepare for MySQL. -@param[in, out] trx Transaction instance to prepare */ - -dberr_t -trx_prepare_for_mysql(trx_t* trx); - +/** XA PREPARE a transaction. +@param[in,out] trx transaction to prepare */ +void trx_prepare_for_mysql(trx_t* trx); /**********************************************************************//** This function is used to find number of prepared transactions and their transaction objects for a recovery. @@ -552,29 +544,6 @@ Check if the transaction is being referenced. */ #define trx_is_referenced(t) ((t)->n_ref > 0) /** -@param[in] requestor Transaction requesting the lock -@param[in] holder Transaction holding the lock -@return the transaction that will be rolled back, null don't care */ - -UNIV_INLINE -const trx_t* -trx_arbitrate(const trx_t* requestor, const trx_t* holder); - -/** -@param[in] trx Transaction to check -@return true if the transaction is a high priority transaction.*/ -UNIV_INLINE -bool -trx_is_high_priority(const trx_t* trx); - -/** -Kill all transactions that are blocking this transaction from acquiring locks. -@param[in,out] trx High priority transaction */ - -void -trx_kill_blocking(trx_t* trx); - -/** Transactions that aren't started by the MySQL server don't set the trx_t::mysql_thd field. For such transactions we set the lock wait timeout to 0 instead of the user configured value that comes @@ -622,7 +591,6 @@ Check transaction state */ case TRX_STATE_COMMITTED_IN_MEMORY: \ continue; \ case TRX_STATE_NOT_STARTED: \ - case TRX_STATE_FORCED_ROLLBACK: \ break; \ } \ ut_error; \ @@ -631,8 +599,7 @@ Check transaction state */ /** Check if transaction is free so that it can be re-initialized. @param t transaction handle */ #define assert_trx_is_free(t) do { \ - ut_ad(trx_state_eq((t), TRX_STATE_NOT_STARTED) \ - || trx_state_eq((t), TRX_STATE_FORCED_ROLLBACK)); \ + ut_ad(trx_state_eq((t), TRX_STATE_NOT_STARTED)); \ ut_ad(!trx->has_logged()); \ ut_ad(!MVCC::is_view_active((t)->read_view)); \ ut_ad((t)->lock.wait_thr == NULL); \ @@ -662,7 +629,6 @@ The tranasction must be in the mysql_trx_list. */ ut_ad(!(t)->in_rw_trx_list); \ ut_ad((t)->in_mysql_trx_list); \ ut_ad(t_state == TRX_STATE_NOT_STARTED \ - || t_state == TRX_STATE_FORCED_ROLLBACK \ || t_state == TRX_STATE_ACTIVE); \ } else { \ check_trx_state(t); \ @@ -769,10 +735,6 @@ struct trx_lock_t { Protected by both the lock sys mutex and the trx_t::mutex. */ ulint n_rec_locks; /*!< number of rec locks in this trx */ - - /** The transaction called ha_innobase::start_stmt() to - lock a table. Most likely a temporary table. */ - bool start_stmt; }; /** Type used to store the list of tables that are modified by a given @@ -858,47 +820,12 @@ struct trx_rsegs_t { trx_temp_undo_t m_noredo; }; -struct TrxVersion { - TrxVersion(trx_t* trx); - - /** - @return true if the trx_t instance is the same */ - bool operator==(const TrxVersion& rhs) const - { - return(rhs.m_trx == m_trx); - } - - trx_t* m_trx; - ulint m_version; -}; - -typedef std::list<TrxVersion, ut_allocator<TrxVersion> > hit_list_t; - struct trx_t { TrxMutex mutex; /*!< Mutex protecting the fields state and lock (except some fields of lock, which are protected by lock_sys->mutex) */ - /* Note: in_depth was split from in_innodb for fixing a RO - performance issue. Acquiring the trx_t::mutex for each row - costs ~3% in performance. It is not required for correctness. - Therefore we increment/decrement in_depth without holding any - mutex. The assumption is that the Server will only ever call - the handler from one thread. This is not true for kill_connection. - Therefore in innobase_kill_connection. We don't increment this - counter via TrxInInnoDB. */ - - ib_uint32_t in_depth; /*!< Track nested TrxInInnoDB - count */ - - ib_uint32_t in_innodb; /*!< if the thread is executing - in the InnoDB context count > 0. */ - - bool abort; /*!< if this flag is set then - this transaction must abort when - it can */ - trx_id_t id; /*!< transaction id */ trx_id_t no; /*!< transaction serialization number: @@ -915,7 +842,6 @@ struct trx_t { Possible states: TRX_STATE_NOT_STARTED - TRX_STATE_FORCED_ROLLBACK TRX_STATE_ACTIVE TRX_STATE_PREPARED TRX_STATE_COMMITTED_IN_MEMORY (alias below COMMITTED) @@ -995,22 +921,6 @@ struct trx_t { protected by trx_sys->mutex when trx->in_rw_trx_list holds */ - hit_list_t hit_list; /*!< List of transactions to kill, - when a high priority transaction - is blocked on a lock wait. */ - - os_thread_id_t killed_by; /*!< The thread ID that wants to - kill this transaction asynchronously. - This is required because we recursively - enter the handlerton methods and need - to distinguish between the kill thread - and the transaction thread. - - Note: We need to be careful w.r.t the - Thread Pool. The thread doing the kill - should not leave InnoDB between the - mark and the actual async kill because - the running thread can change. */ /* These fields are not protected by any mutex. */ const char* op_info; /*!< English text describing the @@ -1223,12 +1133,6 @@ struct trx_t { signify that it is no longer "active". */ - /** Version of this instance. It is incremented each time the - instance is re-used in trx_start_low(). It is used to track - whether a transaction has been restarted since it was tagged - for asynchronous rollback. */ - ulint version; - XID* xid; /*!< X/Open XA transaction identification to identify a transaction branch */ @@ -1292,13 +1196,9 @@ private: Check if transaction is started. @param[in] trx Transaction whose state we need to check @reutrn true if transaction is in state started */ -inline -bool -trx_is_started( - const trx_t* trx) +inline bool trx_is_started(const trx_t* trx) { - return(trx->state != TRX_STATE_NOT_STARTED - && trx->state != TRX_STATE_FORCED_ROLLBACK); + return trx->state != TRX_STATE_NOT_STARTED; } /* Transaction isolation levels (trx->isolation_level) */ @@ -1371,224 +1271,6 @@ struct commit_node_t{ mutex_exit(&t->mutex); \ } while (0) -/** Track if a transaction is executing inside InnoDB code. It acts -like a gate between the Server and InnoDB. */ -class TrxInInnoDB { -public: - /** - @param[in,out] trx Transaction entering InnoDB via the handler - @param[in] disable true if called from COMMIT/ROLLBACK method */ - TrxInInnoDB(trx_t* trx, bool disable = false) - : - m_trx(trx) - { - enter(trx, disable); - } - - /** - Destructor */ - ~TrxInInnoDB() - { - exit(m_trx); - } - - /** - @return true if the transaction has been marked for asynchronous - rollback */ - bool is_aborted() const - { - return(is_aborted(m_trx)); - } - - /** - @return true if the transaction can't be rolled back asynchronously */ - bool is_rollback_disabled() const - { - return((m_trx->in_innodb & TRX_FORCE_ROLLBACK_DISABLE) > 0); - } - - /** - @return true if the transaction has been marked for asynchronous - rollback */ - static bool is_aborted(const trx_t* trx) - { - if (trx->state == TRX_STATE_NOT_STARTED) { - return(false); - } - - ut_ad(srv_read_only_mode || trx->in_depth > 0); - ut_ad(srv_read_only_mode || trx->in_innodb > 0); - - return(trx->abort - || trx->state == TRX_STATE_FORCED_ROLLBACK); - } - - /** - Start statement requested for transaction. - @param[in, out] trx Transaction at the start of a SQL statement */ - static void begin_stmt(trx_t* trx) - { - enter(trx, false); - } - - /** - Note an end statement for transaction - @param[in, out] trx Transaction at end of a SQL statement */ - static void end_stmt(trx_t* trx) - { - exit(trx); - } - - /** - @return true if the rollback is being initiated by the thread that - marked the transaction for asynchronous rollback */ - static bool is_async_rollback(const trx_t* trx) - { - return(trx->killed_by == os_thread_get_curr_id()); - } - -private: - /** - Note that we have crossed into InnoDB code. - @param[in] disable true if called from COMMIT/ROLLBACK method */ - static void enter(trx_t* trx, bool disable) - { - if (srv_read_only_mode) { - - return; - } - - ut_ad(!is_async_rollback(trx)); - - /* If it hasn't already been marked for async rollback. - and it will be committed/rolled back. */ - if (disable) { - - trx_mutex_enter(trx); - if (!is_forced_rollback(trx) - && is_started(trx) - && !trx_is_autocommit_non_locking(trx)) { - - ut_ad(trx->killed_by == 0); - - /* This transaction has crossed the point of - no return and cannot be rolled back - asynchronously now. It must commit or rollback - synhronously. */ - - trx->in_innodb |= TRX_FORCE_ROLLBACK_DISABLE; - } - trx_mutex_exit(trx); - } - - /* Avoid excessive mutex acquire/release */ - ++trx->in_depth; - - /* If trx->in_depth is greater than 1 then - transaction is already in InnoDB. */ - if (trx->in_depth > 1) { - - return; - } - - trx_mutex_enter(trx); - - wait(trx); - - ut_ad((trx->in_innodb & TRX_FORCE_ROLLBACK_MASK) == 0); - - ++trx->in_innodb; - - trx_mutex_exit(trx); - } - - /** - Note that we are exiting InnoDB code */ - static void exit(trx_t* trx) - { - if (srv_read_only_mode) { - - return; - } - - /* Avoid excessive mutex acquire/release */ - - ut_ad(trx->in_depth > 0); - - --trx->in_depth; - - if (trx->in_depth > 0) { - - return; - } - - trx_mutex_enter(trx); - - ut_ad((trx->in_innodb & TRX_FORCE_ROLLBACK_MASK) > 0); - - --trx->in_innodb; - - trx_mutex_exit(trx); - } - - /* - @return true if it is a forced rollback, asynchronously */ - static bool is_forced_rollback(const trx_t* trx) - { - ut_ad(trx_mutex_own(trx)); - - return((trx->in_innodb & TRX_FORCE_ROLLBACK)) > 0; - } - - /** - Wait for the asynchronous rollback to complete, if it is in progress */ - static void wait(trx_t* trx) - { - ut_ad(trx_mutex_own(trx)); - - ulint loop_count = 0; - /* start with optimistic sleep time - 20 micro seconds. */ - ulint sleep_time = 20; - - while (is_forced_rollback(trx)) { - - /* Wait for the async rollback to complete */ - - trx_mutex_exit(trx); - - loop_count++; - /* If the wait is long, don't hog the cpu. */ - if (loop_count < 100) { - /* 20 microseconds */ - sleep_time = 20; - } else if (loop_count < 1000) { - /* 1 millisecond */ - sleep_time = 1000; - } else { - /* 100 milliseconds */ - sleep_time = 100000; - } - - os_thread_sleep(sleep_time); - - trx_mutex_enter(trx); - } - } - - /** - @return true if transaction is started */ - static bool is_started(const trx_t* trx) - { - ut_ad(trx_mutex_own(trx)); - - return(trx_is_started(trx)); - } -private: - /** - Transaction instance crossing the handler boundary from the Server. */ - trx_t* m_trx; -}; - #include "trx0trx.ic" #endif diff --git a/storage/innobase/include/trx0trx.ic b/storage/innobase/include/trx0trx.ic index 6fa00c5333f..7721e28bfb6 100644 --- a/storage/innobase/include/trx0trx.ic +++ b/storage/innobase/include/trx0trx.ic @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1996, 2015, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2016, MariaDB Corporation. All Rights Reserved. +Copyright (c) 2016, 2018, MariaDB Corporation. 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 the Free Software @@ -64,11 +64,8 @@ trx_state_eq( return(state == trx->state); case TRX_STATE_NOT_STARTED: - case TRX_STATE_FORCED_ROLLBACK: - /* These states are not allowed for running transactions. */ ut_a(state == TRX_STATE_NOT_STARTED - || state == TRX_STATE_FORCED_ROLLBACK || (relaxed && thd_get_error_number(trx->mysql_thd))); @@ -280,67 +277,3 @@ trx_get_read_view( { return(!MVCC::is_view_active(trx->read_view) ? NULL : trx->read_view); } - -/** -@param[in] trx Transaction to check -@return true if the transaction is a high priority transaction.*/ -UNIV_INLINE -bool -trx_is_high_priority(const trx_t* trx) -{ - if (trx->mysql_thd == NULL) { - return(false); - } - - return(thd_trx_priority(trx->mysql_thd) > 0); -} - -/** -@param[in] requestor Transaction requesting the lock -@param[in] holder Transaction holding the lock -@return the transaction that will be rolled back, null don't care */ -UNIV_INLINE -const trx_t* -trx_arbitrate(const trx_t* requestor, const trx_t* holder) -{ - ut_ad(!trx_is_autocommit_non_locking(holder)); - ut_ad(!trx_is_autocommit_non_locking(requestor)); - - /* Note: Background stats collection transactions also acquire - locks on user tables. They don't have an associated MySQL session - instance. */ - - if (requestor->mysql_thd == NULL) { - - ut_ad(!trx_is_high_priority(requestor)); - - if (trx_is_high_priority(holder)) { - return(requestor); - } else { - return(NULL); - } - - } else if (holder->mysql_thd == NULL) { - - ut_ad(!trx_is_high_priority(holder)); - - if (trx_is_high_priority(requestor)) { - return(holder); - } - - return(NULL); - } - - const THD* victim = thd_trx_arbitrate( - requestor->mysql_thd, holder->mysql_thd); - - ut_ad(victim == NULL - || victim == requestor->mysql_thd - || victim == holder->mysql_thd); - - if (victim != NULL) { - return(victim == requestor->mysql_thd ? requestor : holder); - } - - return(NULL); -} diff --git a/storage/innobase/include/trx0types.h b/storage/innobase/include/trx0types.h index 8092246c7fa..b42871bef31 100644 --- a/storage/innobase/include/trx0types.h +++ b/storage/innobase/include/trx0types.h @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1996, 2014, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2017, MariaDB Corporation. +Copyright (c) 2017, 2018, MariaDB Corporation. 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 the Free Software @@ -53,21 +53,6 @@ static const ulint TRX_SYS_SPACE = 0; /** Random value to check for corruption of trx_t */ static const ulint TRX_MAGIC_N = 91118598; -/** If this flag is set then the transaction cannot be rolled back -asynchronously. */ -static const ib_uint32_t TRX_FORCE_ROLLBACK_DISABLE = 1 << 29; - -/** Was the transaction rolled back asynchronously or by the -owning thread. This flag is relevant only if TRX_FORCE_ROLLBACK -is set. */ -static const ib_uint32_t TRX_FORCE_ROLLBACK_ASYNC = 1 << 30; - -/** Mark the transaction for forced rollback */ -static const ib_uint32_t TRX_FORCE_ROLLBACK = 1U << 31; - -/** For masking out the above four flags */ -static const ib_uint32_t TRX_FORCE_ROLLBACK_MASK = 0x1FFFFFFF; - /** Transaction execution states when trx->state == TRX_STATE_ACTIVE */ enum trx_que_t { TRX_QUE_RUNNING, /*!< transaction is running */ @@ -79,13 +64,8 @@ enum trx_que_t { /** Transaction states (trx_t::state) */ enum trx_state_t { - TRX_STATE_NOT_STARTED, - /** Same as not started but with additional semantics that it - was rolled back asynchronously the last time it was active. */ - TRX_STATE_FORCED_ROLLBACK, - TRX_STATE_ACTIVE, /** Support for 2PC/XA */ diff --git a/storage/innobase/include/trx0undo.h b/storage/innobase/include/trx0undo.h index 20e7aaa4913..f744364d966 100644 --- a/storage/innobase/include/trx0undo.h +++ b/storage/innobase/include/trx0undo.h @@ -28,12 +28,7 @@ Created 3/26/1996 Heikki Tuuri #define trx0undo_h #ifndef UNIV_INNOCHECKSUM -#include "univ.i" -#include "trx0types.h" -#include "mtr0mtr.h" #include "trx0sys.h" -#include "page0types.h" -#include "trx0xa.h" /** The LSB of the "is insert" flag in DB_ROLL_PTR */ #define ROLL_PTR_INSERT_FLAG_POS 55 diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc index db079abf10f..213b370ee36 100644 --- a/storage/innobase/lock/lock0lock.cc +++ b/storage/innobase/lock/lock0lock.cc @@ -44,6 +44,7 @@ Created 5/7/1996 Heikki Tuuri #include "ut0new.h" #include "row0sel.h" #include "row0mysql.h" +#include "row0vers.h" #include "pars0pars.h" #include <set> @@ -1506,128 +1507,210 @@ wsrep_print_wait_locks( } #endif /* WITH_WSREP */ -/** -Check of the lock is on m_rec_id. -@param[in] lock Lock to compare with -@return true if the record lock is on m_rec_id*/ -/** -@param[in] rhs Lock to compare with -@return true if the record lock equals rhs */ -bool -RecLock::is_on_row(const lock_t* lock) const +/** Create a new record lock and inserts it to the lock queue, +without checking for deadlocks or conflicts. +@param[in] type_mode lock mode and wait flag; type will be replaced + with LOCK_REC +@param[in] space tablespace id +@param[in] page_no index page number +@param[in] page R-tree index page, or NULL +@param[in] heap_no record heap number in the index page +@param[in] index the index tree +@param[in,out] trx transaction +@param[in] holds_trx_mutex whether the caller holds trx->mutex +@return created lock */ +lock_t* +lock_rec_create_low( +#ifdef WITH_WSREP + lock_t* c_lock, /*!< conflicting lock */ + que_thr_t* thr, /*!< thread owning trx */ +#endif + ulint type_mode, + ulint space, + ulint page_no, + const page_t* page, + ulint heap_no, + dict_index_t* index, + trx_t* trx, + bool holds_trx_mutex) { - ut_ad(lock_get_type_low(lock) == LOCK_REC); - - const lock_rec_t& other = lock->un_member.rec_lock; - - return(other.space == m_rec_id.m_space_id - && other.page_no == m_rec_id.m_page_no - && lock_rec_get_nth_bit(lock, m_rec_id.m_heap_no)); -} + lock_t* lock; + ulint n_bits; + ulint n_bytes; -/** -Do some checks and prepare for creating a new record lock */ -void -RecLock::prepare() const -{ ut_ad(lock_mutex_own()); - ut_ad(m_trx == thr_get_trx(m_thr)); - - /* Test if there already is some other reason to suspend thread: - we do not enqueue a lock request if the query thread should be - stopped anyway */ + ut_ad(holds_trx_mutex == trx_mutex_own(trx)); + ut_ad(dict_index_is_clust(index) || !dict_index_is_online_ddl(index)); - if (que_thr_stop(m_thr)) { - ut_error; +#ifdef UNIV_DEBUG + /* Non-locking autocommit read-only transactions should not set + any locks. See comment in trx_set_rw_mode explaining why this + conditional check is required in debug code. */ + if (holds_trx_mutex) { + check_trx_state(trx); } +#endif /* UNIV_DEBUG */ - switch (trx_get_dict_operation(m_trx)) { - case TRX_DICT_OP_NONE: - break; - case TRX_DICT_OP_TABLE: - case TRX_DICT_OP_INDEX: - ib::error() << "A record lock wait happens in a dictionary" - " operation. index " << m_index->name - << " of table " << m_index->table->name - << ". " << BUG_REPORT_MSG; - ut_ad(0); - } + /* If rec is the supremum record, then we reset the gap and + LOCK_REC_NOT_GAP bits, as all locks on the supremum are + automatically of the gap type */ - ut_ad(m_index->table->n_ref_count > 0 - || !m_index->table->can_be_evicted); -} + if (UNIV_UNLIKELY(heap_no == PAGE_HEAP_NO_SUPREMUM)) { + ut_ad(!(type_mode & LOCK_REC_NOT_GAP)); + type_mode = type_mode & ~(LOCK_GAP | LOCK_REC_NOT_GAP); + } -/** -Create the lock instance -@param[in, out] trx The transaction requesting the lock -@param[in, out] index Index on which record lock is required -@param[in] mode The lock mode desired -@param[in] rec_id The record id -@param[in] size Size of the lock + bitmap requested -@return a record lock instance */ -lock_t* -RecLock::lock_alloc( - trx_t* trx, - dict_index_t* index, - ulint mode, - const RecID& rec_id, - ulint size) -{ - ut_ad(lock_mutex_own()); + if (UNIV_LIKELY(!(type_mode & (LOCK_PREDICATE | LOCK_PRDT_PAGE)))) { + /* Make lock bitmap bigger by a safety margin */ + n_bits = page_dir_get_n_heap(page) + LOCK_PAGE_BITMAP_MARGIN; + n_bytes = 1 + n_bits / 8; + } else { + ut_ad(heap_no == PRDT_HEAPNO); - lock_t* lock; + /* The lock is always on PAGE_HEAP_NO_INFIMUM (0), so + we only need 1 bit (which round up to 1 byte) for + lock bit setting */ + n_bytes = 1; - if (trx->lock.rec_cached >= trx->lock.rec_pool.size() - || sizeof(*lock) + size > REC_LOCK_SIZE) { + if (type_mode & LOCK_PREDICATE) { + ulint tmp = UNIV_WORD_SIZE - 1; - ulint n_bytes = size + sizeof(*lock); - mem_heap_t* heap = trx->lock.lock_heap; + /* We will attach predicate structure after lock. + Make sure the memory is aligned on 8 bytes, + the mem_heap_alloc will align it with + MEM_SPACE_NEEDED anyway. */ + n_bytes = (n_bytes + sizeof(lock_prdt_t) + tmp) & ~tmp; + ut_ad(n_bytes == sizeof(lock_prdt_t) + UNIV_WORD_SIZE); + } + } - lock = reinterpret_cast<lock_t*>(mem_heap_alloc(heap, n_bytes)); + if (trx->lock.rec_cached >= trx->lock.rec_pool.size() + || sizeof *lock + n_bytes > REC_LOCK_SIZE) { + lock = static_cast<lock_t*>( + mem_heap_alloc(trx->lock.lock_heap, + sizeof *lock + n_bytes)); } else { - - lock = trx->lock.rec_pool[trx->lock.rec_cached]; - ++trx->lock.rec_cached; + lock = trx->lock.rec_pool[trx->lock.rec_cached++]; } lock->trx = trx; - + lock->type_mode = (type_mode & ~LOCK_TYPE_MASK) | LOCK_REC; lock->index = index; + lock->un_member.rec_lock.space = uint32_t(space); + lock->un_member.rec_lock.page_no = uint32_t(page_no); - /* Setup the lock attributes */ - - lock->type_mode = uint32_t(LOCK_REC | (mode & ~LOCK_TYPE_MASK)); + if (UNIV_LIKELY(!(type_mode & (LOCK_PREDICATE | LOCK_PRDT_PAGE)))) { + lock->un_member.rec_lock.n_bits = uint32_t(n_bytes * 8); + } else { + /* Predicate lock always on INFIMUM (0) */ + lock->un_member.rec_lock.n_bits = 8; + } + lock_rec_bitmap_reset(lock); + lock_rec_set_nth_bit(lock, heap_no); + index->table->n_rec_locks++; + ut_ad(index->table->n_ref_count > 0 || !index->table->can_be_evicted); - lock_rec_t& rec_lock = lock->un_member.rec_lock; +#ifdef WITH_WSREP + if (c_lock && wsrep_on_trx(trx) + && wsrep_thd_is_BF(trx->mysql_thd, FALSE)) { + lock_t *hash = (lock_t *)c_lock->hash; + lock_t *prev = NULL; - /* Predicate lock always on INFIMUM (0) */ + while (hash && wsrep_thd_is_BF(hash->trx->mysql_thd, TRUE) + && wsrep_trx_order_before(hash->trx->mysql_thd, + trx->mysql_thd)) { + prev = hash; + hash = (lock_t *)hash->hash; + } + lock->hash = hash; + if (prev) { + prev->hash = lock; + } else { + c_lock->hash = lock; + } + /* + * delayed conflict resolution '...kill_one_trx' was not called, + * if victim was waiting for some other lock + */ + trx_mutex_enter(c_lock->trx); + if (c_lock->trx->lock.que_state == TRX_QUE_LOCK_WAIT) { - if (is_predicate_lock(mode)) { + c_lock->trx->lock.was_chosen_as_deadlock_victim = TRUE; - rec_lock.n_bits = 8; + if (wsrep_debug) { + wsrep_print_wait_locks(c_lock); + } - memset(&lock[1], 0x0, 1); + trx->lock.que_state = TRX_QUE_LOCK_WAIT; + lock_set_lock_and_trx_wait(lock, trx); + UT_LIST_ADD_LAST(trx->lock.trx_locks, lock); - } else { - ut_ad(8 * size < UINT32_MAX); - rec_lock.n_bits = static_cast<uint32_t>(8 * size); + trx->lock.wait_thr = thr; + thr->state = QUE_THR_LOCK_WAIT; - memset(&lock[1], 0x0, size); - } + /* have to release trx mutex for the duration of + victim lock release. This will eventually call + lock_grant, which wants to grant trx mutex again + */ + if (holds_trx_mutex) { + trx_mutex_exit(trx); + } + lock_cancel_waiting_and_release( + c_lock->trx->lock.wait_lock); - rec_lock.space = rec_id.m_space_id; + if (holds_trx_mutex) { + trx_mutex_enter(trx); + } - rec_lock.page_no = rec_id.m_page_no; + /* trx might not wait for c_lock, but some other lock + does not matter if wait_lock was released above + */ + if (c_lock->trx->lock.wait_lock == c_lock) { + lock_reset_lock_and_trx_wait(lock); + } - /* Set the bit corresponding to rec */ + trx_mutex_exit(c_lock->trx); - lock_rec_set_nth_bit(lock, rec_id.m_heap_no); + if (wsrep_debug) { + ib::info() << "WSREP: c_lock canceled " + << ib::hex(c_lock->trx->id) + << " SQL: " + << wsrep_thd_query( + c_lock->trx->mysql_thd); + } - MONITOR_INC(MONITOR_NUM_RECLOCK); + /* have to bail out here to avoid lock_set_lock... */ + return(lock); + } + trx_mutex_exit(c_lock->trx); + } else +#endif /* WITH_WSREP */ + if (!(type_mode & (LOCK_WAIT | LOCK_PREDICATE | LOCK_PRDT_PAGE)) + && innodb_lock_schedule_algorithm + == INNODB_LOCK_SCHEDULE_ALGORITHM_VATS + && !thd_is_replication_slave_thread(trx->mysql_thd)) { + HASH_PREPEND(lock_t, hash, lock_sys->rec_hash, + lock_rec_fold(space, page_no), lock); + } else { + HASH_INSERT(lock_t, hash, lock_hash_get(type_mode), + lock_rec_fold(space, page_no), lock); + } + if (!holds_trx_mutex) { + trx_mutex_enter(trx); + } + ut_ad(trx_mutex_own(trx)); + if (type_mode & LOCK_WAIT) { + lock_set_lock_and_trx_wait(lock, trx); + } + UT_LIST_ADD_LAST(trx->lock.trx_locks, lock); + if (!holds_trx_mutex) { + trx_mutex_exit(trx); + } MONITOR_INC(MONITOR_RECLOCK_CREATED); + MONITOR_INC(MONITOR_NUM_RECLOCK); - return(lock); + return lock; } /*********************************************************************//** @@ -1638,6 +1721,7 @@ If only one of them is a wait lock, it has lower priority. If either is a high priority transaction, the lock has higher priority. Otherwise, the one with an older transaction has higher priority. @returns true if lock1 has higher priority, false otherwise. */ +static bool has_higher_priority( lock_t *lock1, @@ -1654,9 +1738,6 @@ has_higher_priority( } else if (!lock_get_wait(lock2)) { return false; } - if (trx_is_high_priority(lock1->trx)) { - return false; - } return lock1->trx->start_time_micro <= lock2->trx->start_time_micro; } @@ -1781,389 +1862,118 @@ lock_rec_insert_to_head( } } -/** -Add the lock to the record lock hash and the transaction's lock list -@param[in,out] lock Newly created record lock to add to the rec hash -@param[in] add_to_hash If the lock should be added to the hash table */ -void -RecLock::lock_add(lock_t* lock, bool add_to_hash) -{ - ut_ad(lock_mutex_own()); - ut_ad(trx_mutex_own(lock->trx)); - - bool wait_lock = m_mode & LOCK_WAIT; - - if (add_to_hash) { - ulint key = m_rec_id.fold(); - hash_table_t *lock_hash = lock_hash_get(m_mode); - - ++lock->index->table->n_rec_locks; - - if (innodb_lock_schedule_algorithm == INNODB_LOCK_SCHEDULE_ALGORITHM_VATS - && !thd_is_replication_slave_thread(lock->trx->mysql_thd)) { - if (wait_lock) { - HASH_INSERT(lock_t, hash, lock_hash, key, lock); - } else { - lock_rec_insert_to_head(lock, m_rec_id.fold()); - } - } else { - HASH_INSERT(lock_t, hash, lock_hash, key, lock); - } - } - - if (wait_lock) { - lock_set_lock_and_trx_wait(lock, lock->trx); - } - - UT_LIST_ADD_LAST(lock->trx->lock.trx_locks, lock); -} - -/** -Create a new lock. -@param[in,out] trx Transaction requesting the lock -@param[in] owns_trx_mutex true if caller owns the trx_t::mutex -@param[in] add_to_hash add the lock to hash table -@param[in] prdt Predicate lock (optional) -@param[in,out] c_lock Conflicting lock request or NULL - in Galera conflicting lock is selected - as deadlock victim if requester - is BF transaction. -@return a new lock instance */ -lock_t* -RecLock::create( - trx_t* trx, - bool owns_trx_mutex, - bool add_to_hash, - const lock_prdt_t* prdt +/** Enqueue a waiting request for a lock which cannot be granted immediately. +Check for deadlocks. +@param[in] type_mode the requested lock mode (LOCK_S or LOCK_X) + possibly ORed with LOCK_GAP or + LOCK_REC_NOT_GAP, ORed with + LOCK_INSERT_INTENTION if this + waiting lock request is set + when performing an insert of + an index record +@param[in] block leaf page in the index +@param[in] heap_no record heap number in the block +@param[in] index index tree +@param[in,out] thr query thread +@param[in] prdt minimum bounding box (spatial index) +@retval DB_LOCK_WAIT if the waiting lock was enqueued +@retval DB_DEADLOCK if this transaction was chosen as the victim +@retval DB_SUCCESS_LOCKED_REC if the other transaction was chosen as a victim + (or it happened to commit) */ +dberr_t +lock_rec_enqueue_waiting( #ifdef WITH_WSREP - ,lock_t* c_lock -#endif /* WITH_WSREP */ -) + lock_t* c_lock, /*!< conflicting lock */ +#endif + ulint type_mode, + const buf_block_t* block, + ulint heap_no, + dict_index_t* index, + que_thr_t* thr, + lock_prdt_t* prdt) { ut_ad(lock_mutex_own()); - ut_ad(owns_trx_mutex == trx_mutex_own(trx)); - - /* Create the explicit lock instance and initialise it. */ + ut_ad(!srv_read_only_mode); + ut_ad(dict_index_is_clust(index) || !dict_index_is_online_ddl(index)); - lock_t* lock = lock_alloc(trx, m_index, m_mode, m_rec_id, m_size); + trx_t* trx = thr_get_trx(thr); - if (prdt != NULL && (m_mode & LOCK_PREDICATE)) { + ut_ad(trx_mutex_own(trx)); + ut_a(!que_thr_stop(thr)); - lock_prdt_set_prdt(lock, prdt); + switch (trx_get_dict_operation(trx)) { + case TRX_DICT_OP_NONE: + break; + case TRX_DICT_OP_TABLE: + case TRX_DICT_OP_INDEX: + ib::error() << "A record lock wait happens in a dictionary" + " operation. index " + << index->name + << " of table " + << index->table->name + << ". " << BUG_REPORT_MSG; + ut_ad(0); } + /* Enqueue the lock request that will wait to be granted, note that + we already own the trx mutex. */ + lock_t* lock = lock_rec_create( #ifdef WITH_WSREP - if (c_lock && wsrep_on_trx(trx) && - wsrep_thd_is_BF(trx->mysql_thd, FALSE)) { - lock_t *hash = (lock_t *)c_lock->hash; - lock_t *prev = NULL; - - while (hash && - wsrep_thd_is_BF(((lock_t *)hash)->trx->mysql_thd, TRUE) && - wsrep_trx_order_before( - ((lock_t *)hash)->trx->mysql_thd, - trx->mysql_thd)) { - prev = hash; - hash = (lock_t *)hash->hash; - } - - lock->hash = hash; - - if (prev) { - prev->hash = lock; - } else { - c_lock->hash = lock; - } - /* - * delayed conflict resolution '...kill_one_trx' was not called, - * if victim was waiting for some other lock - */ - trx_mutex_enter(c_lock->trx); - if (c_lock->trx->lock.que_state == TRX_QUE_LOCK_WAIT) { - - c_lock->trx->lock.was_chosen_as_deadlock_victim = TRUE; - - if (wsrep_debug) { - wsrep_print_wait_locks(c_lock); - } - - trx->lock.que_state = TRX_QUE_LOCK_WAIT; - lock_set_lock_and_trx_wait(lock, trx); - UT_LIST_ADD_LAST(trx->lock.trx_locks, lock); - - ut_ad(m_thr != NULL); - trx->lock.wait_thr = m_thr; - m_thr->state = QUE_THR_LOCK_WAIT; - - /* have to release trx mutex for the duration of - victim lock release. This will eventually call - lock_grant, which wants to grant trx mutex again - */ - if (owns_trx_mutex) { - trx_mutex_exit(trx); - } - - lock_cancel_waiting_and_release( - c_lock->trx->lock.wait_lock); - - if (owns_trx_mutex) { - trx_mutex_enter(trx); - } - - /* trx might not wait for c_lock, but some other lock - does not matter if wait_lock was released above - */ - if (c_lock->trx->lock.wait_lock == c_lock) { - if (wsrep_debug) { - ib::info() << - "victim trx waits for some other lock than c_lock"; - } - lock_reset_lock_and_trx_wait(lock); - } - - trx_mutex_exit(c_lock->trx); - - if (wsrep_debug) { - ib::info() << "WSREP: c_lock canceled " << ib::hex(c_lock->trx->id); - ib::info() << " SQL1: " - << wsrep_thd_query(c_lock->trx->mysql_thd); - ib::info() << " SQL2: " - << wsrep_thd_query(trx->mysql_thd); - } - - ++lock->index->table->n_rec_locks; - /* have to bail out here to avoid lock_set_lock... */ - return(lock); - } - trx_mutex_exit(c_lock->trx); - /* we don't want to add to hash anymore, but need other updates from lock_add */ - ++lock->index->table->n_rec_locks; - lock_add(lock, false); - } else { -#endif /* WITH_WSREP */ - - /* Ensure that another transaction doesn't access the trx - lock state and lock data structures while we are adding the - lock and changing the transaction state to LOCK_WAIT */ - - if (!owns_trx_mutex) { - trx_mutex_enter(trx); - } - - lock_add(lock, add_to_hash); + c_lock, thr, +#endif + type_mode | LOCK_WAIT, block, heap_no, index, trx, TRUE); - if (!owns_trx_mutex) { - trx_mutex_exit(trx); - } -#ifdef WITH_WSREP + if (prdt && type_mode & LOCK_PREDICATE) { + lock_prdt_set_prdt(lock, prdt); } -#endif /* WITH_WSREP */ - - return(lock); -} - -/** -Check the outcome of the deadlock check -@param[in,out] victim_trx Transaction selected for rollback -@param[in,out] lock Lock being requested -@return DB_LOCK_WAIT, DB_DEADLOCK or DB_SUCCESS_LOCKED_REC */ -dberr_t -RecLock::check_deadlock_result(const trx_t* victim_trx, lock_t* lock) -{ - ut_ad(lock_mutex_own()); - ut_ad(m_trx == lock->trx); - ut_ad(trx_mutex_own(m_trx)); - - if (victim_trx != NULL) { - - ut_ad(victim_trx == m_trx); + if (const trx_t* victim = + DeadlockChecker::check_and_resolve(lock, trx)) { + ut_ad(victim == trx); lock_reset_lock_and_trx_wait(lock); + lock_rec_reset_nth_bit(lock, heap_no); + return DB_DEADLOCK; + } - lock_rec_reset_nth_bit(lock, m_rec_id.m_heap_no); - - return(DB_DEADLOCK); - - } else if (m_trx->lock.wait_lock == NULL) { - + if (!trx->lock.wait_lock) { /* If there was a deadlock but we chose another transaction as a victim, it is possible that we already have the lock now granted! */ - - return(DB_SUCCESS_LOCKED_REC); - } - - return(DB_LOCK_WAIT); -} - -/** -Check and resolve any deadlocks -@param[in, out] lock The lock being acquired -@return DB_LOCK_WAIT, DB_DEADLOCK, or - DB_SUCCESS_LOCKED_REC; DB_SUCCESS_LOCKED_REC means that - there was a deadlock, but another transaction was chosen - as a victim, and we got the lock immediately: no need to - wait then */ -dberr_t -RecLock::deadlock_check(lock_t* lock) -{ - ut_ad(lock_mutex_own()); - ut_ad(lock->trx == m_trx); - ut_ad(trx_mutex_own(m_trx)); - - const trx_t* victim_trx = - DeadlockChecker::check_and_resolve(lock, m_trx); - - /* Check the outcome of the deadlock test. It is possible that - the transaction that blocked our lock was rolled back and we - were granted our lock. */ - - dberr_t err = check_deadlock_result(victim_trx, lock); - - if (err == DB_LOCK_WAIT) { - - set_wait_state(lock); - - MONITOR_INC(MONITOR_LOCKREC_WAIT); - } - - return(err); -} - -/** -Collect the transactions that will need to be rolled back asynchronously -@param[in, out] trx Transaction to be rolled back */ -void -RecLock::mark_trx_for_rollback(trx_t* trx) -{ - trx->abort = true; - - ut_ad(!trx->read_only); - ut_ad(trx_mutex_own(m_trx)); - ut_ad(!(trx->in_innodb & TRX_FORCE_ROLLBACK)); - ut_ad(!(trx->in_innodb & TRX_FORCE_ROLLBACK_ASYNC)); - ut_ad(!(trx->in_innodb & TRX_FORCE_ROLLBACK_DISABLE)); - - /* Note that we will attempt an async rollback. The _ASYNC - flag will be cleared if the transaction is rolled back - synchronously before we get a chance to do it. */ - - trx->in_innodb |= TRX_FORCE_ROLLBACK | TRX_FORCE_ROLLBACK_ASYNC; - - ut_a(!trx->killed_by); - my_atomic_storelong(&trx->killed_by, (long) os_thread_get_curr_id()); - - m_trx->hit_list.push_back(hit_list_t::value_type(trx)); - -#ifdef UNIV_DEBUG - THD* thd = trx->mysql_thd; - - if (thd != NULL) { - - char buffer[1024]; - ib::info() << "Blocking transaction: ID: " << ib::hex(trx->id) << " - " - << " Blocked transaction ID: "<< ib::hex(m_trx->id) << " - " - << thd_get_error_context_description(thd, buffer, sizeof(buffer), - 512); - } -#endif /* UNIV_DEBUG */ -} - -/** -Setup the requesting transaction state for lock grant -@param[in,out] lock Lock for which to change state */ -void -RecLock::set_wait_state(lock_t* lock) -{ - ut_ad(lock_mutex_own()); - ut_ad(m_trx == lock->trx); - ut_ad(trx_mutex_own(m_trx)); - ut_ad(lock_get_wait(lock)); - - m_trx->lock.wait_started = ut_time(); - - m_trx->lock.que_state = TRX_QUE_LOCK_WAIT; - - m_trx->lock.was_chosen_as_deadlock_victim = false; - - bool stopped = que_thr_stop(m_thr); - ut_a(stopped); -} - -/** -Enqueue a lock wait for normal transaction. If it is a high priority transaction -then jump the record lock wait queue and if the transaction at the head of the -queue is itself waiting roll it back, also do a deadlock check and resolve. -@param[in, out] wait_for The lock that the joining transaction is - waiting for -@param[in] prdt Predicate [optional] -@return DB_LOCK_WAIT, DB_DEADLOCK, or - DB_SUCCESS_LOCKED_REC; DB_SUCCESS_LOCKED_REC means that - there was a deadlock, but another transaction was chosen - as a victim, and we got the lock immediately: no need to - wait then */ -dberr_t -RecLock::add_to_waitq(lock_t* wait_for, const lock_prdt_t* prdt) -{ - ut_ad(lock_mutex_own()); - ut_ad(m_trx == thr_get_trx(m_thr)); - ut_ad(trx_mutex_own(m_trx)); - - DEBUG_SYNC_C("rec_lock_add_to_waitq"); - - m_mode |= LOCK_WAIT; - - /* Do the preliminary checks, and set query thread state */ - - prepare(); - - bool high_priority = trx_is_high_priority(m_trx); - - /* Don't queue the lock to hash table, if high priority transaction. */ - lock_t* lock = create( - m_trx, true, !high_priority, prdt #ifdef WITH_WSREP - ,wait_for -#endif /* WITH_WSREP */ - ); + if (wsrep_debug) { + ib::info() << "WSREP: BF thread got lock granted early, ID " << ib::hex(trx->id) + << " query: " << wsrep_thd_query(trx->mysql_thd); + } +#endif + return DB_SUCCESS_LOCKED_REC; + } - /* Attempt to jump over the low priority waiting locks. */ - if (high_priority && jump_queue(lock, wait_for)) { + trx->lock.que_state = TRX_QUE_LOCK_WAIT; - /* Lock is granted */ - return(DB_SUCCESS); - } + trx->lock.was_chosen_as_deadlock_victim = false; + trx->lock.wait_started = ut_time(); -#ifdef WITH_WSREP - if (!lock_get_wait(lock) && wsrep_thd_is_BF(m_trx->mysql_thd, FALSE)) { - if (wsrep_debug) { - ib::info() << "WSREP: BF thread got lock granted early, ID " << ib::hex(lock->trx->id) - << " query: " << wsrep_thd_query(m_trx->mysql_thd); - } - return(DB_SUCCESS); - } -#endif /* WITH_WSREP */ - ut_ad(lock_get_wait(lock)); + ut_a(que_thr_stop(thr)); - dberr_t err = deadlock_check(lock); - ut_ad(trx_mutex_own(m_trx)); + DBUG_LOG("ib_lock", "trx " << ib::hex(trx->id) + << " waits for lock in index " << index->name + << " of table " << index->table->name); - // Move it only when it does not cause a deadlock. - if (err != DB_DEADLOCK - && innodb_lock_schedule_algorithm - == INNODB_LOCK_SCHEDULE_ALGORITHM_VATS - && !thd_is_replication_slave_thread(lock->trx->mysql_thd) - && !trx_is_high_priority(lock->trx)) { + MONITOR_INC(MONITOR_LOCKREC_WAIT); - HASH_DELETE(lock_t, hash, lock_hash_get(lock->type_mode), - m_rec_id.fold(), lock); + if (innodb_lock_schedule_algorithm + == INNODB_LOCK_SCHEDULE_ALGORITHM_VATS + && !prdt + && !thd_is_replication_slave_thread(lock->trx->mysql_thd)) { + HASH_DELETE(lock_t, hash, lock_sys->rec_hash, + lock_rec_lock_fold(lock), lock); dberr_t res = lock_rec_insert_by_trx_age(lock); if (res != DB_SUCCESS) { return res; } } - return(err); + return DB_LOCK_WAIT; } /*********************************************************************//** @@ -2288,9 +2098,11 @@ lock_rec_add_to_queue( } } - RecLock rec_lock(index, block, heap_no, type_mode); - - rec_lock.create(trx, caller_owns_trx_mutex, true); + lock_rec_create( +#ifdef WITH_WSREP + NULL, NULL, +#endif + type_mode, block, heap_no, index, trx, caller_owns_trx_mutex); } /*********************************************************************//** @@ -2341,12 +2153,13 @@ lock_rec_lock_fast( lock_rec_req_status status = LOCK_REC_SUCCESS; if (lock == NULL) { - if (!impl) { - RecLock rec_lock(index, block, heap_no, mode); - /* Note that we don't own the trx mutex. */ - rec_lock.create(trx, false, true); + lock = lock_rec_create( +#ifdef WITH_WSREP + NULL, NULL, +#endif + mode, block, heap_no, index, trx, false); } status = LOCK_REC_SUCCESS_CREATED; @@ -2419,40 +2232,32 @@ lock_rec_lock_slow( trx_mutex_enter(trx); if (lock_rec_has_expl(mode, block, heap_no, trx)) { - - /* The trx already has a strong enough lock on rec: do - nothing */ - + /* The trx already has a strong enough lock: do nothing */ err = DB_SUCCESS; - + } else if ( +#ifdef WITH_WSREP + lock_t* c_lock = +#endif /* WITH_WSREP */ + lock_rec_other_has_conflicting( + static_cast<enum lock_mode>(mode), + block, heap_no, trx)) { + /* If another transaction has a non-gap conflicting + request in the queue, as this transaction does not + have a lock strong enough already granted on the + record, we have to wait. */ + err = lock_rec_enqueue_waiting( +#ifdef WITH_WSREP + c_lock, +#endif /* WITH_WSREP */ + mode, block, heap_no, index, thr, NULL); + } else if (!impl) { + /* Set the requested lock on the record, note that + we already own the transaction mutex. */ + lock_rec_add_to_queue( + LOCK_REC | mode, block, heap_no, index, trx, TRUE); + err = DB_SUCCESS_LOCKED_REC; } else { - lock_t* wait_for = lock_rec_other_has_conflicting( - mode, block, heap_no, trx); - - if (wait_for != NULL) { - - /* If another transaction has a non-gap conflicting - request in the queue, as this transaction does not - have a lock strong enough already granted on the - record, we may have to wait. */ - - RecLock rec_lock(thr, index, block, heap_no, mode); - - err = rec_lock.add_to_waitq(wait_for); - - } else if (!impl) { - - /* Set the requested lock on the record, note that - we already own the transaction mutex. */ - - lock_rec_add_to_queue( - LOCK_REC | mode, block, heap_no, index, trx, - true); - - err = DB_SUCCESS_LOCKED_REC; - } else { - err = DB_SUCCESS; - } + err = DB_SUCCESS; } trx_mutex_exit(trx); @@ -2630,234 +2435,6 @@ lock_grant( } } -/** -Jump the queue for the record over all low priority transactions and -add the lock. If all current granted locks are compatible, grant the -lock. Otherwise, mark all granted transaction for asynchronous -rollback and add to hit list. -@param[in, out] lock Lock being requested -@param[in] conflict_lock First conflicting lock from the head -@return true if the lock is granted */ -bool -RecLock::jump_queue( - lock_t* lock, - const lock_t* conflict_lock) -{ - ut_ad(m_trx == lock->trx); - ut_ad(trx_mutex_own(m_trx)); - ut_ad(conflict_lock->trx != m_trx); - ut_ad(trx_is_high_priority(m_trx)); - ut_ad(m_rec_id.m_heap_no != ULINT32_UNDEFINED); - - bool high_priority = false; - - /* Find out the position to add the lock. If there are other high - priority transactions in waiting state then we should add it after - the last high priority transaction. Otherwise, we can add it after - the last granted lock jumping over the wait queue. */ - bool grant_lock = lock_add_priority(lock, conflict_lock, - &high_priority); - - if (grant_lock) { - - ut_ad(conflict_lock->trx->lock.que_state == TRX_QUE_LOCK_WAIT); - ut_ad(conflict_lock->trx->lock.wait_lock == conflict_lock); - - DBUG_LOG("trx", - "Granting High Priority Transaction " - << ib::hex(lock->trx->id) << " a lock jumping over" - << " waiting Transaction " << ib::hex(conflict_lock->trx->id)); - - lock_reset_lock_and_trx_wait(lock); - return(true); - } - - /* If another high priority transaction is found waiting - victim transactions are already marked for rollback. */ - if (high_priority) { - - return(false); - } - - /* The lock is placed after the last granted lock in the queue. Check and add - low priority transactinos to hit list for ASYNC rollback. */ - make_trx_hit_list(lock, conflict_lock); - - return(false); -} - -/** Find position in lock queue and add the high priority transaction -lock. Intention and GAP only locks can be granted even if there are -waiting locks in front of the queue. To add the High priority -transaction in a safe position we keep the following rule. - -1. If the lock can be granted, add it before the first waiting lock -in the queue so that all currently waiting locks need to do conflict -check before getting granted. - -2. If the lock has to wait, add it after the last granted lock or the -last waiting high priority transaction in the queue whichever is later. -This ensures that the transaction is granted only after doing conflict -check with all granted transactions. -@param[in] lock Lock being requested -@param[in] conflict_lock First conflicting lock from the head -@param[out] high_priority high priority transaction ahead in queue -@return true if the lock can be granted */ -bool -RecLock::lock_add_priority( - lock_t* lock, - const lock_t* conflict_lock, - bool* high_priority) -{ - ut_ad(high_priority); - - *high_priority = false; - - /* If the first conflicting lock is waiting for the current row, - then all other granted locks are compatible and the lock can be - directly granted if no other high priority transactions are - waiting. We need to recheck with all granted transaction as there - could be granted GAP or Intention locks down the queue. */ - bool grant_lock = (conflict_lock->is_waiting()); - lock_t* lock_head = NULL; - lock_t* grant_position = NULL; - lock_t* add_position = NULL; - - /* Different lock (such as predicate lock) are on different hash */ - hash_table_t* lock_hash = lock_hash_get(m_mode); - - HASH_SEARCH(hash, lock_hash, m_rec_id.fold(), lock_t*, - lock_head, ut_ad(lock_head->is_record_lock()), true); - - ut_ad(lock_head); - - for (lock_t* next = lock_head; next != NULL; next = next->hash) { - - /* check only for locks on the current row */ - if (!is_on_row(next)) { - continue; - } - - if (next->is_waiting()) { - /* grant lock position is the granted lock just before - the first wait lock in the queue. */ - if (grant_position == NULL) { - grant_position = add_position; - } - - if (trx_is_high_priority(next->trx)) { - - *high_priority = true; - grant_lock = false; - add_position = next; - } - } else { - - add_position = next; - /* Cannot grant lock if there is any conflicting - granted lock. */ - if (grant_lock && lock_has_to_wait(lock, next)) { - grant_lock = false; - } - } - } - - /* If the lock is to be granted it is safe to add before the first - waiting lock in the queue. */ - if (grant_lock) { - - ut_ad(!lock_has_to_wait(lock, grant_position)); - add_position = grant_position; - } - - ut_ad(add_position != NULL); - - /* Add the lock to lock hash table. */ - lock->hash = add_position->hash; - add_position->hash = lock; - ++lock->index->table->n_rec_locks; - - return(grant_lock); -} - -/** Iterate over the granted locks and prepare the hit list for ASYNC Rollback. -If the transaction is waiting for some other lock then wake up with deadlock error. -Currently we don't mark following transactions for ASYNC Rollback. -1. Read only transactions -2. Background transactions -3. Other High priority transactions -@param[in] lock Lock being requested -@param[in] conflict_lock First conflicting lock from the head */ -void -RecLock::make_trx_hit_list( - lock_t* lock, - const lock_t* conflict_lock) -{ - const lock_t* next; - - for (next = conflict_lock; next != NULL; next = next->hash) { - - /* All locks ahead in the queue are checked. */ - if (next == lock) { - - ut_ad(next->is_waiting()); - break; - } - - trx_t* trx = next->trx; - /* Check only for conflicting, granted locks on the current row. - Currently, we don't rollback read only transactions, transactions - owned by background threads. */ - if (trx == lock->trx - || !is_on_row(next) - || next->is_waiting() - || trx->read_only - || trx->mysql_thd == NULL - || !lock_has_to_wait(lock, next)) { - - continue; - } - - trx_mutex_enter(trx); - - /* Skip high priority transactions, if already marked for abort - by some other transaction or if ASYNC rollback is disabled. A - transaction must complete kill/abort of a victim transaction once - marked and added to hit list. */ - if (trx_is_high_priority(trx) - || (trx->in_innodb & TRX_FORCE_ROLLBACK_DISABLE) != 0 - || trx->abort) { - - trx_mutex_exit(trx); - continue; - } - - /* If the transaction is waiting on some other resource then - wake it up with DEAD_LOCK error so that it can rollback. */ - if (trx->lock.que_state == TRX_QUE_LOCK_WAIT) { - - /* Assert that it is not waiting for current record. */ - ut_ad(trx->lock.wait_lock != next); - - DBUG_LOG("trx", "High Priority Transaction " - << ib::hex(lock->trx->id) - << " waking up blocking transaction " - << ib::hex(trx->id)); - - trx->lock.was_chosen_as_deadlock_victim = true; - lock_cancel_waiting_and_release(trx->lock.wait_lock); - trx_mutex_exit(trx); - continue; - } - - /* Mark for ASYNC Rollback and add to hit list. */ - mark_trx_for_rollback(trx); - trx_mutex_exit(trx); - } - - ut_ad(next == lock); -} - /*************************************************************//** Cancels a waiting record lock request and releases the waiting transaction that requested it. NOTE: does NOT check if waiting lock requests behind this @@ -2981,7 +2558,6 @@ lock_rec_dequeue_from_page( space = in_lock->un_member.rec_lock.space; page_no = in_lock->un_member.rec_lock.page_no; - ut_ad(in_lock->index->table->n_rec_locks > 0); in_lock->index->table->n_rec_locks--; lock_hash = lock_hash_get(in_lock->type_mode); @@ -3042,7 +2618,6 @@ lock_rec_discard( space = in_lock->un_member.rec_lock.space; page_no = in_lock->un_member.rec_lock.page_no; - ut_ad(in_lock->index->table->n_rec_locks > 0); in_lock->index->table->n_rec_locks--; HASH_DELETE(lock_t, hash, lock_hash_get(in_lock->type_mode), @@ -3826,10 +3401,10 @@ lock_update_merge_right( #ifdef UNIV_DEBUG /* there should exist no page lock on the left page, otherwise, it will be blocked from merge */ - ulint space = left_block->page.id.space(); - ulint page_no = left_block->page.id.page_no(); + ulint space = left_block->page.id.space(); + ulint page_no = left_block->page.id.page_no(); ut_ad(lock_rec_get_first_on_page_addr( - lock_sys->prdt_page_hash, space, page_no) == NULL); + lock_sys->prdt_page_hash, space, page_no) == NULL); #endif /* UNIV_DEBUG */ lock_rec_free_all_from_discard_page(left_block); @@ -3954,7 +3529,7 @@ lock_update_merge_left( ulint space = right_block->page.id.space(); ulint page_no = right_block->page.id.page_no(); lock_t* lock_test = lock_rec_get_first_on_page_addr( - lock_sys->prdt_page_hash, space, page_no); + lock_sys->prdt_page_hash, space, page_no); ut_ad(!lock_test); #endif /* UNIV_DEBUG */ @@ -4001,9 +3576,9 @@ lock_update_discard( const buf_block_t* block) /*!< in: index page which will be discarded */ { + const page_t* page = block->frame; const rec_t* rec; ulint heap_no; - const page_t* page = block->frame; lock_mutex_enter(); @@ -4444,10 +4019,9 @@ lock_table_remove_low( /*********************************************************************//** Enqueues a waiting request for a table lock which cannot be granted immediately. Checks for deadlocks. -@return DB_LOCK_WAIT, DB_DEADLOCK, or -DB_SUCCESS; DB_SUCCESS means that there was a deadlock, but another -transaction was chosen as a victim, and we got the lock immediately: -no need to wait then */ +@retval DB_LOCK_WAIT if the waiting lock was enqueued +@retval DB_DEADLOCK if this transaction was chosen as the victim +@retval DB_SUCCESS if the other transaction committed or aborted */ static dberr_t lock_table_enqueue_waiting( @@ -5036,7 +4610,7 @@ lock_release( } if (count == LOCK_RELEASE_INTERVAL) { - /* Release the mutex for a while, so that we + /* Release the mutex for a while, so that we do not monopolize it */ lock_mutex_exit(); @@ -6548,19 +6122,21 @@ lock_rec_insert_check_and_lock( const ulint type_mode = LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION; - lock_t* wait_for = lock_rec_other_has_conflicting( - type_mode, block, heap_no, trx); - - if (wait_for != NULL) { - - RecLock rec_lock(thr, index, block, heap_no, type_mode); - + if ( +#ifdef WITH_WSREP + lock_t* c_lock = +#endif /* WITH_WSREP */ + lock_rec_other_has_conflicting(type_mode, block, heap_no, trx)) { + /* Note that we may get DB_SUCCESS also here! */ trx_mutex_enter(trx); - err = rec_lock.add_to_waitq(wait_for); + err = lock_rec_enqueue_waiting( +#ifdef WITH_WSREP + c_lock, +#endif /* WITH_WSREP */ + type_mode, block, heap_no, index, thr, NULL); trx_mutex_exit(trx); - } else { err = DB_SUCCESS; } @@ -7369,7 +6945,6 @@ lock_unlock_table_autoinc( but not COMMITTED transactions. */ ut_ad(trx_state_eq(trx, TRX_STATE_NOT_STARTED) - || trx_state_eq(trx, TRX_STATE_FORCED_ROLLBACK) || !trx_state_eq(trx, TRX_STATE_COMMITTED_IN_MEMORY)); /* This function is invoked for a running transaction by the @@ -7992,24 +7567,9 @@ DeadlockChecker::select_victim() const ut_ad(m_start->lock.wait_lock != 0); ut_ad(m_wait_lock->trx != m_start); - if (thd_trx_priority(m_start->mysql_thd) > 0 - || thd_trx_priority(m_wait_lock->trx->mysql_thd) > 0) { - - const trx_t* victim; - - victim = trx_arbitrate(m_start, m_wait_lock->trx); - - if (victim != NULL) { - - return(victim); - } - } - if (trx_weight_ge(m_wait_lock->trx, m_start)) { - /* The joining transaction is 'smaller', choose it as the victim and roll it back. */ - #ifdef WITH_WSREP if (wsrep_thd_is_BF(m_start->mysql_thd, TRUE)) { return(m_wait_lock->trx); @@ -8208,12 +7768,7 @@ DeadlockChecker::check_and_resolve(const lock_t* lock, trx_t* trx) check_trx_state(trx); ut_ad(!srv_read_only_mode); - /* If transaction is marked for ASYNC rollback then we should - not allow it to wait for another lock causing possible deadlock. - We return current transaction as deadlock victim here. */ - if (trx->in_innodb & TRX_FORCE_ROLLBACK_ASYNC) { - return(trx); - } else if (!innobase_deadlock_detect) { + if (!innobase_deadlock_detect) { return(NULL); } diff --git a/storage/innobase/lock/lock0prdt.cc b/storage/innobase/lock/lock0prdt.cc index f6859b70297..23a46a002be 100644 --- a/storage/innobase/lock/lock0prdt.cc +++ b/storage/innobase/lock/lock0prdt.cc @@ -38,6 +38,7 @@ Created 9/7/2013 Jimmy Yang #include "ut0vec.h" #include "btr0btr.h" #include "dict0boot.h" +#include "que0que.h" #include <set> /*********************************************************************//** @@ -495,9 +496,18 @@ lock_prdt_add_to_queue( } } - RecLock rec_lock(index, block, PRDT_HEAPNO, type_mode); + lock = lock_rec_create( +#ifdef WITH_WSREP + NULL, NULL, /* FIXME: replicate SPATIAL INDEX locks */ +#endif + type_mode, block, PRDT_HEAPNO, index, trx, + caller_owns_trx_mutex); - return(rec_lock.create(trx, caller_owns_trx_mutex, true, prdt)); + if (lock->type_mode & LOCK_PREDICATE) { + lock_prdt_set_prdt(lock, prdt); + } + + return lock; } /*********************************************************************//** @@ -565,7 +575,7 @@ lock_prdt_insert_check_and_lock( const ulint mode = LOCK_X | LOCK_PREDICATE | LOCK_INSERT_INTENTION; - lock_t* wait_for = lock_prdt_other_has_conflicting( + const lock_t* wait_for = lock_prdt_other_has_conflicting( mode, block, prdt, trx); if (wait_for != NULL) { @@ -574,16 +584,17 @@ lock_prdt_insert_check_and_lock( /* Allocate MBR on the lock heap */ lock_init_prdt_from_mbr(prdt, mbr, 0, trx->lock.lock_heap); - RecLock rec_lock(thr, index, block, PRDT_HEAPNO, mode); - /* Note that we may get DB_SUCCESS also here! */ - trx_mutex_enter(trx); - err = rec_lock.add_to_waitq(wait_for, prdt); + err = lock_rec_enqueue_waiting( +#ifdef WITH_WSREP + NULL, /* FIXME: replicate SPATIAL INDEX locks */ +#endif + LOCK_X | LOCK_PREDICATE | LOCK_INSERT_INTENTION, + block, PRDT_HEAPNO, index, thr, prdt); trx_mutex_exit(trx); - } else { err = DB_SUCCESS; } @@ -831,13 +842,14 @@ lock_prdt_lock( lock_t* lock = lock_rec_get_first_on_page(hash, block); if (lock == NULL) { - - RecLock rec_lock(index, block, PRDT_HEAPNO, prdt_mode); - - lock = rec_lock.create(trx, false, true); + lock = lock_rec_create( +#ifdef WITH_WSREP + NULL, NULL, /* FIXME: replicate SPATIAL INDEX locks */ +#endif + mode | type_mode, block, PRDT_HEAPNO, + index, trx, FALSE); status = LOCK_REC_SUCCESS_CREATED; - } else { trx_mutex_enter(trx); @@ -861,12 +873,14 @@ lock_prdt_lock( if (wait_for != NULL) { - RecLock rec_lock( - thr, index, block, PRDT_HEAPNO, - prdt_mode, prdt); - - err = rec_lock.add_to_waitq(wait_for); - + err = lock_rec_enqueue_waiting( +#ifdef WITH_WSREP + NULL, /* FIXME: replicate + SPATIAL INDEX locks */ +#endif + mode | type_mode, + block, PRDT_HEAPNO, + index, thr, prdt); } else { lock_prdt_add_to_queue( @@ -947,10 +961,12 @@ lock_place_prdt_page_lock( } if (lock == NULL) { - RecID rec_id(space, page_no, PRDT_HEAPNO); - RecLock rec_lock(index, rec_id, mode); - - rec_lock.create(trx, false, true); + lock = lock_rec_create_low( +#ifdef WITH_WSREP + NULL, NULL, /* FIXME: replicate SPATIAL INDEX locks */ +#endif + mode, space, page_no, NULL, PRDT_HEAPNO, + index, trx, FALSE); #ifdef PRDT_DIAG printf("GIS_DIAGNOSTIC: page lock %d\n", (int) page_no); diff --git a/storage/innobase/lock/lock0wait.cc b/storage/innobase/lock/lock0wait.cc index c41821412af..d6c812e1af0 100644 --- a/storage/innobase/lock/lock0wait.cc +++ b/storage/innobase/lock/lock0wait.cc @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2014, 2017, MariaDB Corporation. +Copyright (c) 2014, 2018, MariaDB Corporation. 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 the Free Software @@ -419,7 +419,7 @@ lock_wait_suspend_thread( && (!wsrep_on_trx(trx) || (!wsrep_is_BF_lock_timeout(trx, false) && trx->error_state != DB_DEADLOCK)) #endif /* WITH_WSREP */ - && !trx_is_high_priority(trx)) { + ) { trx->error_state = DB_LOCK_WAIT_TIMEOUT; @@ -502,7 +502,7 @@ lock_wait_check_and_cancel( trx_mutex_enter(trx); - if (trx->lock.wait_lock != NULL && !trx_is_high_priority(trx)) { + if (trx->lock.wait_lock != NULL) { ut_a(trx->lock.que_state == TRX_QUE_LOCK_WAIT); diff --git a/storage/innobase/page/page0page.cc b/storage/innobase/page/page0page.cc index fb528843da6..c33623e9398 100644 --- a/storage/innobase/page/page0page.cc +++ b/storage/innobase/page/page0page.cc @@ -2,7 +2,7 @@ Copyright (c) 1994, 2016, Oracle and/or its affiliates. All Rights Reserved. Copyright (c) 2012, Facebook Inc. -Copyright (c) 2017, MariaDB Corporation. +Copyright (c) 2017, 2018, MariaDB Corporation. 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 the Free Software @@ -35,6 +35,7 @@ Created 2/2/1994 Heikki Tuuri #include "lock0lock.h" #include "fut0lst.h" #include "btr0sea.h" +#include "trx0sys.h" /* THE INDEX PAGE ============== diff --git a/storage/innobase/page/page0zip.cc b/storage/innobase/page/page0zip.cc index 271ef4a787e..6b68ee973af 100644 --- a/storage/innobase/page/page0zip.cc +++ b/storage/innobase/page/page0zip.cc @@ -45,6 +45,7 @@ const byte field_ref_zero[FIELD_REF_SIZE] = { #include "btr0cur.h" #include "page0types.h" #include "log0recv.h" +#include "row0row.h" #include "row0trunc.h" #include "zlib.h" #include "buf0buf.h" diff --git a/storage/innobase/row/row0ins.cc b/storage/innobase/row/row0ins.cc index 98e94e06464..76809222f2c 100644 --- a/storage/innobase/row/row0ins.cc +++ b/storage/innobase/row/row0ins.cc @@ -1852,8 +1852,6 @@ do_possible_lock_wait: my_atomic_addlint( &check_table->n_foreign_key_checks_running, 1); - trx_kill_blocking(trx); - lock_wait_suspend_thread(thr); thr->lock_state = QUE_THR_LOCK_NOLOCK; diff --git a/storage/innobase/row/row0merge.cc b/storage/innobase/row/row0merge.cc index c50e8fbd6ae..cbd5aa2f316 100644 --- a/storage/innobase/row/row0merge.cc +++ b/storage/innobase/row/row0merge.cc @@ -36,6 +36,7 @@ Completed by Sunny Bains and Marko Makela #include "row0ext.h" #include "row0log.h" #include "row0ins.h" +#include "row0row.h" #include "row0sel.h" #include "log0crypt.h" #include "dict0crea.h" @@ -45,6 +46,7 @@ Completed by Sunny Bains and Marko Makela #include "ut0sort.h" #include "row0ftsort.h" #include "row0import.h" +#include "row0vers.h" #include "handler0alter.h" #include "btr0bulk.h" #include "fsp0sysspace.h" @@ -3586,8 +3588,6 @@ row_merge_lock_table( trx->op_info = "setting table lock for creating or dropping index"; trx->ddl = true; - /* Trx for DDL should not be forced to rollback for now */ - trx->in_innodb |= TRX_FORCE_ROLLBACK_DISABLE; return(lock_table_for_trx(table, trx, mode)); } diff --git a/storage/innobase/row/row0mysql.cc b/storage/innobase/row/row0mysql.cc index 49a042acc20..cd3e7253298 100644 --- a/storage/innobase/row/row0mysql.cc +++ b/storage/innobase/row/row0mysql.cc @@ -729,9 +729,6 @@ handle_new_error: /* MySQL will roll back the latest SQL statement */ break; case DB_LOCK_WAIT: - - trx_kill_blocking(trx); - lock_wait_suspend_thread(thr); if (trx->error_state != DB_SUCCESS) { diff --git a/storage/innobase/row/row0sel.cc b/storage/innobase/row/row0sel.cc index 623961945b2..c42b08d820b 100644 --- a/storage/innobase/row/row0sel.cc +++ b/storage/innobase/row/row0sel.cc @@ -4437,17 +4437,12 @@ row_search_mvcc( naturally moves upward (in fetch next) in alphabetical order, otherwise downward */ - if (direction == 0) { - - if (mode == PAGE_CUR_GE - || mode == PAGE_CUR_G + if (UNIV_UNLIKELY(direction == 0)) { + if (mode == PAGE_CUR_GE || mode == PAGE_CUR_G || mode >= PAGE_CUR_CONTAIN) { - moves_up = TRUE; } - } else if (direction == ROW_SEL_NEXT) { - moves_up = TRUE; } @@ -5682,15 +5677,6 @@ normal_return: mtr.commit(); - /* Rollback blocking transactions from hit list for high priority - transaction, if any. We should not be holding latches here as - we are going to rollback the blocking transactions. */ - if (!trx->hit_list.empty()) { - - ut_ad(trx_is_high_priority(trx)); - trx_kill_blocking(trx); - } - DEBUG_SYNC_C("row_search_for_mysql_before_return"); if (prebuilt->idx_cond != 0) { diff --git a/storage/innobase/row/row0trunc.cc b/storage/innobase/row/row0trunc.cc index 068b4d96ed2..94be5152596 100644 --- a/storage/innobase/row/row0trunc.cc +++ b/storage/innobase/row/row0trunc.cc @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 2013, 2017, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2017, MariaDB Corporation. +Copyright (c) 2017, 2018, MariaDB Corporation. 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 the Free Software @@ -36,7 +36,8 @@ Created 2013-04-12 Sunny Bains #include "srv0start.h" #include "row0trunc.h" #include "os0file.h" -#include <vector> +#include "que0que.h" +#include "trx0undo.h" /* FIXME: For temporary tables, use a simple approach of btr_free() and btr_create() of each index tree. */ diff --git a/storage/innobase/trx/trx0roll.cc b/storage/innobase/trx/trx0roll.cc index 5c2258f25be..a5be54b7b79 100644 --- a/storage/innobase/trx/trx0roll.cc +++ b/storage/innobase/trx/trx0roll.cc @@ -183,10 +183,7 @@ trx_rollback_for_mysql_low( /** Rollback a transaction used in MySQL @param[in, out] trx transaction @return error code or DB_SUCCESS */ -static -dberr_t -trx_rollback_low( - trx_t* trx) +dberr_t trx_rollback_for_mysql(trx_t* trx) { /* We are reading trx->state without holding trx_sys->mutex here, because the rollback should be invoked for a running @@ -194,7 +191,6 @@ trx_rollback_low( that is associated with the current thread. */ switch (trx->state) { - case TRX_STATE_FORCED_ROLLBACK: case TRX_STATE_NOT_STARTED: trx->will_lock = 0; ut_ad(trx->in_mysql_trx_list); @@ -262,28 +258,6 @@ trx_rollback_low( } /*******************************************************************//** -Rollback a transaction used in MySQL. -@return error code or DB_SUCCESS */ -dberr_t -trx_rollback_for_mysql( -/*===================*/ - trx_t* trx) /*!< in/out: transaction */ -{ - /* Avoid the tracking of async rollback killer - thread to enter into InnoDB. */ - if (TrxInInnoDB::is_async_rollback(trx)) { - - return(trx_rollback_low(trx)); - - } else { - - TrxInInnoDB trx_in_innodb(trx, true); - - return(trx_rollback_low(trx)); - } -} - -/*******************************************************************//** Rollback the latest SQL statement for MySQL. @return error code or DB_SUCCESS */ dberr_t @@ -300,7 +274,6 @@ trx_rollback_last_sql_stat_for_mysql( ut_ad(trx->in_mysql_trx_list); switch (trx->state) { - case TRX_STATE_FORCED_ROLLBACK: case TRX_STATE_NOT_STARTED: return(DB_SUCCESS); @@ -487,12 +460,9 @@ trx_rollback_to_savepoint_for_mysql( switch (trx->state) { case TRX_STATE_NOT_STARTED: - case TRX_STATE_FORCED_ROLLBACK: - ib::error() << "Transaction has a savepoint " << savep->name << " though it is not started"; - return(DB_ERROR); case TRX_STATE_ACTIVE: @@ -780,7 +750,6 @@ fake_prepared: case TRX_STATE_PREPARED: goto func_exit; case TRX_STATE_NOT_STARTED: - case TRX_STATE_FORCED_ROLLBACK: break; } diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc index c6e2bd88d62..1c058a3ede5 100644 --- a/storage/innobase/trx/trx0trx.cc +++ b/storage/innobase/trx/trx0trx.cc @@ -43,7 +43,6 @@ Created 3/26/1996 Heikki Tuuri #include "srv0mon.h" #include "srv0srv.h" #include "fsp0sysspace.h" -#include "row0mysql.h" #include "srv0start.h" #include "trx0purge.h" #include "trx0rec.h" @@ -69,15 +68,6 @@ typedef std::set< std::less<table_id_t>, ut_allocator<table_id_t> > table_id_set; -/** Constructor */ -TrxVersion::TrxVersion(trx_t* trx) - : - m_trx(trx), - m_version(trx->version) -{ - /* No op */ -} - /** Set flush observer for the transaction @param[in/out] trx transaction struct @param[in] observer flush observer */ @@ -121,14 +111,12 @@ trx_init( /*=====*/ trx_t* trx) { - /* This is called at the end of commit, do not reset the - trx_t::state here to NOT_STARTED. The FORCED_ROLLBACK - status is required for asynchronous handling. */ - trx->id = 0; trx->no = TRX_ID_MAX; + trx->state = TRX_STATE_NOT_STARTED; + trx->is_recovered = false; trx->op_info = ""; @@ -183,29 +171,7 @@ trx_init( trx->lock.table_cached = 0; - /* During asynchronous rollback, we should reset forced rollback flag - only after rollback is complete to avoid race with the thread owning - the transaction. */ - - if (!TrxInInnoDB::is_async_rollback(trx)) { - - my_atomic_storelong(&trx->killed_by, 0); - - /* Note: Do not set to 0, the ref count is decremented inside - the TrxInInnoDB() destructor. We only need to clear the flags. */ - - trx->in_innodb &= TRX_FORCE_ROLLBACK_MASK; - } - - /* Note: It's possible that this list is not empty if a transaction - was interrupted after it collected the victim transactions and before - it got a chance to roll them back asynchronously. */ - - trx->hit_list.clear(); - trx->flush_observer = NULL; - - ++trx->version; } /** For managing the life-cycle of the trx_t instance that we get @@ -220,7 +186,7 @@ struct TrxFactory { { /* Explicitly call the constructor of the already allocated object. trx_t objects are allocated by - ut_zalloc() in Pool::Pool() which would not call + ut_zalloc_nokey() in Pool::Pool() which would not call the constructors of the trx_t members. */ new(&trx->mod_tables) trx_mod_tables_t(); @@ -230,13 +196,8 @@ struct TrxFactory { new(&trx->lock.table_locks) lock_pool_t(); - new(&trx->hit_list) hit_list_t(); - trx_init(trx); - DBUG_LOG("trx", "Init: " << trx); - trx->state = TRX_STATE_NOT_STARTED; - trx->dict_operation_lock_mode = 0; trx->xid = UT_NEW_NOKEY(xid_t()); @@ -309,8 +270,6 @@ struct TrxFactory { trx->lock.table_pool.~lock_pool_t(); trx->lock.table_locks.~lock_pool_t(); - - trx->hit_list.~hit_list_t(); } /** Enforce any invariants here, this is called before the transaction @@ -324,8 +283,7 @@ struct TrxFactory { ut_ad(!trx->read_only); - ut_ad(trx->state == TRX_STATE_NOT_STARTED - || trx->state == TRX_STATE_FORCED_ROLLBACK); + ut_ad(trx->state == TRX_STATE_NOT_STARTED); ut_ad(trx->dict_operation == TRX_DICT_OP_NONE); @@ -344,12 +302,6 @@ struct TrxFactory { ut_ad(trx->lock.table_locks.empty()); - ut_ad(!trx->abort); - - ut_ad(trx->hit_list.empty()); - - ut_ad(trx->killed_by == 0); - return(true); } }; @@ -442,15 +394,9 @@ trx_create_low() /* We just got trx from pool, it should be non locking */ ut_ad(trx->will_lock == 0); + ut_ad(trx->state == TRX_STATE_NOT_STARTED); - /* Background trx should not be forced to rollback, - we will unset the flag for user trx. */ - trx->in_innodb |= TRX_FORCE_ROLLBACK_DISABLE; - - /* Trx state can be TRX_STATE_FORCED_ROLLBACK if - the trx was forced to rollback before it's reused.*/ DBUG_LOG("trx", "Create: " << trx); - trx->state = TRX_STATE_NOT_STARTED; heap = mem_heap_create(sizeof(ib_vector_t) + sizeof(void*) * 8); @@ -1204,7 +1150,6 @@ trx_start_low( { ut_ad(!trx->in_rollback); ut_ad(!trx->is_recovered); - ut_ad(trx->hit_list.empty()); ut_ad(trx->start_line != 0); ut_ad(trx->start_file != 0); ut_ad(trx->roll_limit == 0); @@ -1213,10 +1158,6 @@ trx_start_low( ut_ad(trx->rsegs.m_noredo.rseg == NULL); ut_ad(trx_state_eq(trx, TRX_STATE_NOT_STARTED)); ut_ad(UT_LIST_GET_LEN(trx->lock.trx_locks) == 0); - ut_ad(!(trx->in_innodb & TRX_FORCE_ROLLBACK)); - ut_ad(!(trx->in_innodb & TRX_FORCE_ROLLBACK_ASYNC)); - - ++trx->version; /* Check whether it is an AUTOCOMMIT SELECT */ trx->auto_commit = thd_trx_is_auto_commit(trx->mysql_thd); @@ -1731,16 +1672,9 @@ trx_commit_in_memory( MONITOR_INC(MONITOR_TRX_NL_RO_COMMIT); - /* AC-NL-RO transactions can't be rolled back asynchronously. */ - ut_ad(!trx->abort); - ut_ad(!(trx->in_innodb - & (TRX_FORCE_ROLLBACK | TRX_FORCE_ROLLBACK_ASYNC))); - DBUG_LOG("trx", "Autocommit in memory: " << trx); trx->state = TRX_STATE_NOT_STARTED; - } else { - if (trx->id > 0) { /* For consistent snapshot, we need to remove current transaction from running transaction id list for mvcc @@ -1864,20 +1798,8 @@ trx_commit_in_memory( } #endif - /* Because we can rollback transactions asynchronously, we change - the state at the last step. trx_t::abort cannot change once commit - or rollback has started because we will have released the locks by - the time we get here. */ - - if (trx->abort) { - - trx->abort = false; - DBUG_LOG("trx", "Abort: " << trx); - trx->state = TRX_STATE_FORCED_ROLLBACK; - } else { - DBUG_LOG("trx", "Commit in memory: " << trx); - trx->state = TRX_STATE_NOT_STARTED; - } + DBUG_LOG("trx", "Commit in memory: " << trx); + trx->state = TRX_STATE_NOT_STARTED; /* trx->in_mysql_trx_list would hold between trx_allocate_for_mysql() and trx_free_for_mysql(). It does not @@ -2089,8 +2011,6 @@ trx_commit_or_rollback_prepare( switch (trx->state) { case TRX_STATE_NOT_STARTED: - case TRX_STATE_FORCED_ROLLBACK: - trx_start_low(trx, true); /* fall through */ @@ -2194,22 +2114,12 @@ trx_commit_for_mysql( /*=================*/ trx_t* trx) /*!< in/out: transaction */ { - TrxInInnoDB trx_in_innodb(trx, true); - - if (trx_in_innodb.is_aborted() - && trx->killed_by != os_thread_get_curr_id()) { - - return(DB_FORCED_ABORT); - } - /* Because we do not do the commit by sending an Innobase sig to the transaction, we must here make sure that trx has been started. */ switch (trx->state) { case TRX_STATE_NOT_STARTED: - case TRX_STATE_FORCED_ROLLBACK: - ut_d(trx->start_file = __FILE__); ut_d(trx->start_line = __LINE__); @@ -2270,7 +2180,6 @@ trx_mark_sql_stat_end( case TRX_STATE_COMMITTED_IN_MEMORY: break; case TRX_STATE_NOT_STARTED: - case TRX_STATE_FORCED_ROLLBACK: trx->undo_no = 0; trx->undo_rseg_space = 0; /* fall through */ @@ -2321,9 +2230,6 @@ trx_print_low( case TRX_STATE_NOT_STARTED: fputs(", not started", f); goto state_ok; - case TRX_STATE_FORCED_ROLLBACK: - fputs(", forced rollback", f); - goto state_ok; case TRX_STATE_ACTIVE: fprintf(f, ", ACTIVE %lu sec", (ulong) difftime(time(NULL), trx->start_time)); @@ -2465,10 +2371,6 @@ wsrep_trx_print_locking( fprintf(f, ", ACTIVE %lu sec", (ulong) difftime(time(NULL), trx->start_time)); goto state_ok; - case TRX_STATE_FORCED_ROLLBACK: - fprintf(f, ", FORCED ROLLBACK, %lu sec", - (ulong) difftime(time(NULL), trx->start_time)); - goto state_ok; case TRX_STATE_PREPARED: fprintf(f, ", ACTIVE (PREPARED) %lu sec", (ulong) difftime(time(NULL), trx->start_time)); @@ -2599,7 +2501,6 @@ trx_assert_started( return(TRUE); case TRX_STATE_NOT_STARTED: - case TRX_STATE_FORCED_ROLLBACK: break; } @@ -2714,10 +2615,6 @@ trx_prepare( /*========*/ trx_t* trx) /*!< in/out: transaction */ { - /* This transaction has crossed the point of no return and cannot - be rolled back asynchronously now. It must commit or rollback - synhronously. */ - /* Only fresh user transactions can be prepared. Recovered transactions cannot. */ ut_a(!trx->is_recovered); @@ -2755,29 +2652,17 @@ trx_prepare( } } -/** -Does the transaction prepare for MySQL. -@param[in, out] trx Transaction instance to prepare */ -dberr_t -trx_prepare_for_mysql(trx_t* trx) +/** XA PREPARE a transaction. +@param[in,out] trx transaction to prepare */ +void trx_prepare_for_mysql(trx_t* trx) { trx_start_if_not_started_xa(trx, false); - TrxInInnoDB trx_in_innodb(trx, true); - - if (trx_in_innodb.is_aborted() - && trx->killed_by != os_thread_get_curr_id()) { - - return(DB_FORCED_ABORT); - } - trx->op_info = "preparing"; trx_prepare(trx); trx->op_info = ""; - - return(DB_SUCCESS); } /**********************************************************************//** @@ -2925,7 +2810,6 @@ trx_start_if_not_started_xa_low( { switch (trx->state) { case TRX_STATE_NOT_STARTED: - case TRX_STATE_FORCED_ROLLBACK: trx_start_low(trx, read_write); return; @@ -2958,13 +2842,10 @@ trx_start_if_not_started_low( { switch (trx->state) { case TRX_STATE_NOT_STARTED: - case TRX_STATE_FORCED_ROLLBACK: - trx_start_low(trx, read_write); return; case TRX_STATE_ACTIVE: - if (read_write && trx->id == 0 && !trx->read_only) { trx_set_rw_mode(trx); } @@ -3021,8 +2902,6 @@ trx_start_for_ddl_low( { switch (trx->state) { case TRX_STATE_NOT_STARTED: - case TRX_STATE_FORCED_ROLLBACK: - /* Flag this transaction as a dictionary operation, so that the data dictionary will be locked in crash recovery. */ @@ -3114,146 +2993,3 @@ trx_set_rw_mode( mutex_exit(&trx_sys->mutex); } - -/** -Kill all transactions that are blocking this transaction from acquiring locks. -@param[in,out] trx High priority transaction */ - -void -trx_kill_blocking(trx_t* trx) -{ - if (trx->hit_list.empty()) { - return; - } - - DEBUG_SYNC_C("trx_kill_blocking_enter"); - - ulint had_dict_lock = trx->dict_operation_lock_mode; - - switch (had_dict_lock) { - case 0: - break; - - case RW_S_LATCH: - /* Release foreign key check latch */ - row_mysql_unfreeze_data_dictionary(trx); - break; - - default: - /* There should never be a lock wait when the - dictionary latch is reserved in X mode. Dictionary - transactions should only acquire locks on dictionary - tables, not other tables. All access to dictionary - tables should be covered by dictionary - transactions. */ - ut_error; - } - - ut_a(trx->dict_operation_lock_mode == 0); - - /** Kill the transactions in the lock acquisition order old -> new. */ - hit_list_t::reverse_iterator end = trx->hit_list.rend(); - - for (hit_list_t::reverse_iterator it = trx->hit_list.rbegin(); - it != end; - ++it) { - - trx_t* victim_trx = it->m_trx; - ulint version = it->m_version; - - /* Shouldn't commit suicide. */ - ut_ad(victim_trx != trx); - ut_ad(victim_trx->mysql_thd != trx->mysql_thd); - - /* Check that the transaction isn't active inside - InnoDB code. We have to wait while it is executing - in the InnoDB context. This can potentially take a - long time */ - - trx_mutex_enter(victim_trx); - ut_ad(version <= victim_trx->version); - - ulint loop_count = 0; - /* start with optimistic sleep time of 20 micro seconds. */ - ulint sleep_time = 20; - - while ((victim_trx->in_innodb & TRX_FORCE_ROLLBACK_MASK) > 0 - && victim_trx->version == version) { - - trx_mutex_exit(victim_trx); - - loop_count++; - /* If the wait is long, don't hog the cpu. */ - if (loop_count < 100) { - /* 20 microseconds */ - sleep_time = 20; - } else if (loop_count < 1000) { - /* 1 millisecond */ - sleep_time = 1000; - } else { - /* 100 milliseconds */ - sleep_time = 100000; - } - - os_thread_sleep(sleep_time); - - trx_mutex_enter(victim_trx); - } - - /* Compare the version to check if the transaction has - already finished */ - if (victim_trx->version != version) { - trx_mutex_exit(victim_trx); - continue; - } - - /* We should never kill background transactions. */ - ut_ad(victim_trx->mysql_thd != NULL); - - ut_ad(!(trx->in_innodb & TRX_FORCE_ROLLBACK_DISABLE)); - ut_ad(victim_trx->in_innodb & TRX_FORCE_ROLLBACK); - ut_ad(victim_trx->in_innodb & TRX_FORCE_ROLLBACK_ASYNC); - ut_ad(victim_trx->killed_by == os_thread_get_curr_id()); - ut_ad(victim_trx->version == it->m_version); - - /* We don't kill Read Only, Background or high priority - transactions. */ - ut_a(!victim_trx->read_only); - ut_a(victim_trx->mysql_thd != NULL); - - trx_mutex_exit(victim_trx); - -#ifndef DBUG_OFF - char buffer[1024]; -#endif /* !DBUG_OFF */ - - DBUG_LOG("trx", - "High Priority Transaction " - << trx->id << " killed transaction " - << victim_trx->id << " in hit list" - << " - " - << thd_get_error_context_description( - victim_trx->mysql_thd, - buffer, sizeof(buffer), 512)); - - trx_rollback_for_mysql(victim_trx); - trx_mutex_enter(victim_trx); - - version++; - ut_ad(victim_trx->version == version); - - my_atomic_storelong(&victim_trx->killed_by, 0); - - victim_trx->in_innodb &= TRX_FORCE_ROLLBACK_MASK; - - trx_mutex_exit(victim_trx); - } - - trx->hit_list.clear(); - - if (had_dict_lock) { - - row_mysql_freeze_data_dictionary(trx); - } - -} |