/***************************************************************************** Copyright (c) 1996, 2017, Oracle and/or its affiliates. All Rights Reserved. Copyright (c) 2017, 2019, 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 Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA *****************************************************************************/ /**************************************************//** @file trx/trx0sys.cc Transaction system Created 3/26/1996 Heikki Tuuri *******************************************************/ #include "trx0sys.h" #include "mysqld.h" #include "sql_error.h" #include "fsp0fsp.h" #include "mtr0log.h" #include "mtr0log.h" #include "trx0trx.h" #include "trx0rseg.h" #include "trx0undo.h" #include "srv0srv.h" #include "srv0start.h" #include "trx0purge.h" #include "log0log.h" #include "log0recv.h" #include "os0file.h" /** The transaction system */ trx_sys_t trx_sys; /** Check whether transaction id is valid. @param[in] id transaction id to check @param[in] name table name */ void ReadView::check_trx_id_sanity( trx_id_t id, const table_name_t& name) { if (id >= trx_sys.get_max_trx_id()) { ib::warn() << "A transaction id" << " in a record of table " << name << " is newer than the" << " system-wide maximum."; ut_ad(0); THD *thd = current_thd; if (thd != NULL) { char table_name[MAX_FULL_NAME_LEN + 1]; innobase_format_name( table_name, sizeof(table_name), name.m_name); push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_SIGNAL_WARN, "InnoDB: Transaction id" " in a record of table" " %s is newer than system-wide" " maximum.", table_name); } } } #ifdef UNIV_DEBUG /* Flag to control TRX_RSEG_N_SLOTS behavior debugging. */ uint trx_rseg_n_slots_debug = 0; #endif /** Display the MySQL binlog offset info if it is present in the trx system header. */ void trx_sys_print_mysql_binlog_offset() { if (!*trx_sys.recovered_binlog_filename) { return; } ib::info() << "Last binlog file '" << trx_sys.recovered_binlog_filename << "', position " << trx_sys.recovered_binlog_offset; } /** Find an available rollback segment. @param[in] sys_header @return an unallocated rollback segment slot in the TRX_SYS header @retval ULINT_UNDEFINED if not found */ ulint trx_sys_rseg_find_free(const buf_block_t* sys_header) { for (ulint rseg_id = 0; rseg_id < TRX_SYS_N_RSEGS; rseg_id++) { if (trx_sysf_rseg_get_page_no(sys_header, rseg_id) == FIL_NULL) { return rseg_id; } } return(ULINT_UNDEFINED); } /** Count the number of initialized persistent rollback segment slots. */ static void trx_sysf_get_n_rseg_slots() { mtr_t mtr; mtr.start(); srv_available_undo_logs = 0; if (const buf_block_t* sys_header = trx_sysf_get(&mtr, false)) { for (ulint rseg_id = 0; rseg_id < TRX_SYS_N_RSEGS; rseg_id++) { srv_available_undo_logs += trx_sysf_rseg_get_page_no(sys_header, rseg_id) != FIL_NULL; } } mtr.commit(); } /*****************************************************************//** Creates the file page for the transaction system. This function is called only at the database creation, before trx_sys_init. */ static void trx_sysf_create( /*============*/ mtr_t* mtr) /*!< in: mtr */ { ulint slot_no; buf_block_t* block; page_t* page; byte* ptr; ut_ad(mtr); /* Note that below we first reserve the file space x-latch, and then enter the kernel: we must do it in this order to conform to the latching order rules. */ mtr_x_lock(&fil_system.sys_space->latch, mtr); compile_time_assert(TRX_SYS_SPACE == 0); /* Create the trx sys file block in a new allocated file segment */ block = fseg_create(fil_system.sys_space, 0, TRX_SYS + TRX_SYS_FSEG_HEADER, mtr); buf_block_dbg_add_level(block, SYNC_TRX_SYS_HEADER); ut_a(block->page.id.page_no() == TRX_SYS_PAGE_NO); page = buf_block_get_frame(block); mlog_write_ulint(page + FIL_PAGE_TYPE, FIL_PAGE_TYPE_TRX_SYS, MLOG_2BYTES, mtr); /* Reset the doublewrite buffer magic number to zero so that we know that the doublewrite buffer has not yet been created (this suppresses a Valgrind warning) */ mlog_write_ulint(page + TRX_SYS_DOUBLEWRITE + TRX_SYS_DOUBLEWRITE_MAGIC, 0, MLOG_4BYTES, mtr); /* Reset the rollback segment slots. Old versions of InnoDB (before MySQL 5.5) define TRX_SYS_N_RSEGS as 256 and expect that the whole array is initialized. */ ptr = TRX_SYS + TRX_SYS_RSEGS + page; compile_time_assert(256 >= TRX_SYS_N_RSEGS); memset(ptr, 0xff, 256 * TRX_SYS_RSEG_SLOT_SIZE); ptr += 256 * TRX_SYS_RSEG_SLOT_SIZE; ut_a(ptr <= page + (srv_page_size - FIL_PAGE_DATA_END)); /* Initialize all of the page. This part used to be uninitialized. */ memset(ptr, 0, srv_page_size - FIL_PAGE_DATA_END + size_t(page - ptr)); mlog_log_string(TRX_SYS + page, srv_page_size - FIL_PAGE_DATA_END - TRX_SYS, mtr); /* Create the first rollback segment in the SYSTEM tablespace */ slot_no = trx_sys_rseg_find_free(block); buf_block_t* rblock = trx_rseg_header_create(fil_system.sys_space, slot_no, block, mtr); ut_a(slot_no == TRX_SYS_SYSTEM_RSEG_ID); ut_a(rblock->page.id.page_no() == FSP_FIRST_RSEG_PAGE_NO); } /** Create the instance */ void trx_sys_t::create() { ut_ad(this == &trx_sys); ut_ad(!is_initialised()); m_initialised = true; mutex_create(LATCH_ID_TRX_SYS, &mutex); UT_LIST_INIT(trx_list, &trx_t::trx_list); my_atomic_store32(&rseg_history_len, 0); rw_trx_hash.init(); } /*****************************************************************//** Creates and initializes the transaction system at the database creation. */ void trx_sys_create_sys_pages(void) /*==========================*/ { mtr_t mtr; mtr_start(&mtr); trx_sysf_create(&mtr); mtr_commit(&mtr); } /** Create the rollback segments. @return whether the creation succeeded */ bool trx_sys_create_rsegs() { /* srv_available_undo_logs reflects the number of persistent rollback segments that have been initialized in the transaction system header page. srv_undo_logs determines how many of the srv_available_undo_logs rollback segments may be used for logging new transactions. */ ut_ad(srv_undo_tablespaces <= TRX_SYS_MAX_UNDO_SPACES); ut_ad(srv_undo_logs <= TRX_SYS_N_RSEGS); if (srv_read_only_mode) { srv_undo_logs = srv_available_undo_logs = ULONG_UNDEFINED; return(true); } /* This is executed in single-threaded mode therefore it is not necessary to use the same mtr in trx_rseg_create(). n_used cannot change while the function is executing. */ trx_sysf_get_n_rseg_slots(); ut_ad(srv_available_undo_logs <= TRX_SYS_N_RSEGS); /* The first persistent rollback segment is always initialized in the system tablespace. */ ut_a(srv_available_undo_logs > 0); if (srv_force_recovery) { /* Do not create additional rollback segments if innodb_force_recovery has been set. */ if (srv_undo_logs > srv_available_undo_logs) { srv_undo_logs = srv_available_undo_logs; } } else { for (ulint i = 0; srv_available_undo_logs < srv_undo_logs; i++, srv_available_undo_logs++) { /* Tablespace 0 is the system tablespace. Dedicated undo log tablespaces start from 1. */ ulint space = srv_undo_tablespaces > 0 ? (i % srv_undo_tablespaces) + srv_undo_space_id_start : TRX_SYS_SPACE; if (!trx_rseg_create(space)) { ib::error() << "Unable to allocate the" " requested innodb_undo_logs"; return(false); } /* Increase the number of active undo tablespace in case new rollback segment assigned to new undo tablespace. */ if (space > srv_undo_tablespaces_active) { srv_undo_tablespaces_active++; ut_ad(srv_undo_tablespaces_active == space); } } } ut_ad(srv_undo_logs <= srv_available_undo_logs); ib::info info; info << srv_undo_logs << " out of " << srv_available_undo_logs; if (srv_undo_tablespaces_active) { info << " rollback segments in " << srv_undo_tablespaces_active << " undo tablespaces are active."; } else { info << " rollback segments are active."; } return(true); } /** Close the transaction system on shutdown */ void trx_sys_t::close() { ut_ad(srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS); if (!is_initialised()) { return; } if (size_t size = view_count()) { ib::error() << "All read views were not closed before" " shutdown: " << size << " read views open"; } rw_trx_hash.destroy(); /* There can't be any active transactions. */ for (ulint i = 0; i < TRX_SYS_N_RSEGS; ++i) { if (trx_rseg_t* rseg = rseg_array[i]) { trx_rseg_mem_free(rseg); } if (trx_rseg_t* rseg = temp_rsegs[i]) { trx_rseg_mem_free(rseg); } } ut_a(UT_LIST_GET_LEN(trx_list) == 0); mutex_free(&mutex); m_initialised = false; } /** @return total number of active (non-prepared) transactions */ ulint trx_sys_t::any_active_transactions() { uint32_t total_trx= 0; mutex_enter(&mutex); for (trx_t* trx= UT_LIST_GET_FIRST(trx_sys.trx_list); trx != NULL; trx= UT_LIST_GET_NEXT(trx_list, trx)) { if (trx->state == TRX_STATE_COMMITTED_IN_MEMORY || (trx->state == TRX_STATE_ACTIVE && trx->id)) total_trx++; } mutex_exit(&mutex); return total_trx; }