/***************************************************************************** Copyright (c) 1996, 2017, Oracle and/or its affiliates. All Rights Reserved. Copyright (c) 2017, 2022, 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, Fifth Floor, 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; #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(); } /** Initialize the transaction system when creating the database. */ dberr_t trx_sys_create_sys_pages(mtr_t *mtr) { mtr->start(); mtr->x_lock_space(fil_system.sys_space); static_assert(TRX_SYS_SPACE == 0, "compatibility"); /* Create the trx sys file block in a new allocated file segment */ dberr_t err; buf_block_t *block= fseg_create(fil_system.sys_space, TRX_SYS + TRX_SYS_FSEG_HEADER, mtr, &err); if (UNIV_UNLIKELY(!block)) { error: mtr->commit(); return err; } ut_a(block->page.id() == page_id_t(0, TRX_SYS_PAGE_NO)); mtr->write<2>(*block, FIL_PAGE_TYPE + block->page.frame, FIL_PAGE_TYPE_TRX_SYS); /* 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. */ static_assert(256 >= TRX_SYS_N_RSEGS, ""); static_assert(TRX_SYS + TRX_SYS_RSEGS + 256 * TRX_SYS_RSEG_SLOT_SIZE <= UNIV_PAGE_SIZE_MIN - FIL_PAGE_DATA_END, ""); mtr->write<4>(*block, TRX_SYS + TRX_SYS_RSEGS + TRX_SYS_RSEG_PAGE_NO + block->page.frame, FSP_FIRST_RSEG_PAGE_NO); mtr->memset(block, TRX_SYS + TRX_SYS_RSEGS + TRX_SYS_RSEG_SLOT_SIZE, 255 * TRX_SYS_RSEG_SLOT_SIZE, 0xff); buf_block_t *r= trx_rseg_header_create(fil_system.sys_space, 0, 0, mtr, &err); if (UNIV_UNLIKELY(!r)) goto error; ut_a(r->page.id() == page_id_t(0, FSP_FIRST_RSEG_PAGE_NO)); mtr->commit(); return trx_lists_init_at_db_start(); } void trx_sys_t::create() { ut_ad(this == &trx_sys); ut_ad(!is_initialised()); m_initialised= true; trx_list.create(); rw_trx_hash.init(); } size_t trx_sys_t::history_size() { ut_ad(is_initialised()); size_t size= 0; for (auto &rseg : rseg_array) { rseg.latch.rd_lock(SRW_LOCK_CALL); size+= rseg.history_size; } for (auto &rseg : rseg_array) rseg.latch.rd_unlock(); return size; } bool trx_sys_t::history_exceeds(size_t threshold) { ut_ad(is_initialised()); size_t size= 0; bool exceeds= false; size_t i; for (i= 0; i < array_elements(rseg_array); i++) { rseg_array[i].latch.rd_lock(SRW_LOCK_CALL); size+= rseg_array[i].history_size; if (size > threshold) { exceeds= true; i++; break; } } while (i) rseg_array[--i].latch.rd_unlock(); return exceeds; } TPOOL_SUPPRESS_TSAN bool trx_sys_t::history_exists() { ut_ad(is_initialised()); for (auto &rseg : rseg_array) if (rseg.history_size) return true; return false; } TPOOL_SUPPRESS_TSAN size_t trx_sys_t::history_size_approx() const { ut_ad(is_initialised()); size_t size= 0; for (auto &rseg : rseg_array) size+= rseg.history_size; return size; } /** Create a persistent rollback segment. @param space_id system or undo tablespace id @return pointer to new rollback segment @retval nullptr on failure */ static trx_rseg_t *trx_rseg_create(uint32_t space_id) { trx_rseg_t *rseg= nullptr; mtr_t mtr; mtr.start(); if (fil_space_t *space= mtr.x_lock_space(space_id)) { ut_ad(space->purpose == FIL_TYPE_TABLESPACE); if (buf_block_t *sys_header= trx_sysf_get(&mtr)) { ulint rseg_id= trx_sys_rseg_find_free(sys_header); dberr_t err; if (buf_block_t *rblock= rseg_id == ULINT_UNDEFINED ? nullptr : trx_rseg_header_create(space, rseg_id, 0, &mtr, &err)) { rseg= &trx_sys.rseg_array[rseg_id]; rseg->init(space, rblock->page.id().page_no()); ut_ad(rseg->is_persistent()); mtr.write<4,mtr_t::MAYBE_NOP> (*sys_header, TRX_SYS + TRX_SYS_RSEGS + TRX_SYS_RSEG_SPACE + rseg_id * TRX_SYS_RSEG_SLOT_SIZE + sys_header->page.frame, space_id); mtr.write<4,mtr_t::MAYBE_NOP> (*sys_header, TRX_SYS + TRX_SYS_RSEGS + TRX_SYS_RSEG_PAGE_NO + rseg_id * TRX_SYS_RSEG_SLOT_SIZE + sys_header->page.frame, rseg->page_no); } } } mtr.commit(); return rseg; } /** 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. */ ut_ad(srv_undo_tablespaces <= TRX_SYS_MAX_UNDO_SPACES); if (high_level_read_only) { srv_available_undo_logs = 0; 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); for (uint32_t i = 0; srv_available_undo_logs < TRX_SYS_N_RSEGS; i++, srv_available_undo_logs++) { /* Tablespace 0 is the system tablespace. Dedicated undo log tablespaces start from 1. */ uint32_t 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_space_id_start + srv_undo_tablespaces_active - 1)) { srv_undo_tablespaces_active++; } } ut_ad(srv_available_undo_logs == TRX_SYS_N_RSEGS); ib::info info; info << 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 < array_elements(temp_rsegs); ++i) { temp_rsegs[i].destroy(); } for (ulint i = 0; i < array_elements(rseg_array); ++i) { rseg_array[i].destroy(); } ut_a(trx_list.empty()); trx_list.close(); m_initialised = false; } /** @return total number of active (non-prepared) transactions */ size_t trx_sys_t::any_active_transactions(size_t *prepared) { size_t total_trx= 0, prepared_trx= 0; trx_sys.trx_list.for_each([&](const trx_t &trx) { switch (trx.state) { case TRX_STATE_NOT_STARTED: break; case TRX_STATE_ACTIVE: if (!trx.id) break; /* fall through */ case TRX_STATE_COMMITTED_IN_MEMORY: total_trx++; break; case TRX_STATE_PREPARED: case TRX_STATE_PREPARED_RECOVERED: prepared_trx++; } }); if (prepared) *prepared= prepared_trx; return total_trx; }