mirror of
https://github.com/MariaDB/server.git
synced 2025-01-30 18:41:56 +01:00
371 lines
9.7 KiB
C++
371 lines
9.7 KiB
C++
/*****************************************************************************
|
|
|
|
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(ulint 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 (ulint 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. */
|
|
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_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;
|
|
}
|