/** Build a purge 'query' graph. The actual purge is performed by executing thisquerygraph.
@return own: the query graph */ static
que_t*
purge_graph_build()
{
ut_a(srv_n_purge_threads > 0);
/** Determine if the history of a transaction is purgeable. @paramtrx_idtransactionidentifier
@return whether the history is purgeable */ bool purge_sys_t::is_purgeable(trx_id_t trx_id) const noexcept
{
latch.rd_lock(SRW_LOCK_CALL); bool purgeable= view.changes_visible(trx_id);
latch.rd_unlock(); return purgeable;
}
/*================ UNDO LOG HISTORY LIST =============================*/
/** Prepend the history list with an undo log. Removetheundologsegmentfromthersegslotifitistoobigforreuse. @param[in]trxtransaction @param[in,out]undoundolog @param[in,out]mtrmini-transaction
@param[in] end transaction serialisation number */ void
trx_purge_add_undo_to_history(const trx_t* trx, trx_undo_t*& undo, mtr_t* mtr,
trx_id_t end)
{
DBUG_PRINT("trx", ("commit(" TRX_ID_FMT "," TRX_ID_FMT ")",
trx->id, end));
ut_ad(undo->id < TRX_RSEG_N_SLOTS);
ut_ad(undo == trx->rsegs.m_redo.undo);
trx_rseg_t *rseg= trx->rsegs.m_redo.rseg;
ut_ad(undo->rseg == rseg);
buf_block_t *rseg_header= rseg->get(mtr, nullptr); /* We are in transaction commit; we cannot return an error. If the databaseiscorrupted,itisbettertocrashitthanto intentionallyviolateACIDbycommittingsomethingthatisknownto
be corrupted. */
ut_ad(rseg_header);
buf_block_t *undo_page=
buf_page_get(page_id_t(rseg->space->id, undo->hdr_page_no), 0,
RW_X_LATCH, mtr); /* This function is invoked during transaction commit, which is not
allowed to fail. If we get a corrupted undo header, we will crash here. */
ut_a(undo_page); const uint16_t undo_header_offset= undo->hdr_offset;
trx_ulogf_t *undo_header= undo_page->page.frame + undo_header_offset;
if (UNIV_UNLIKELY(mach_read_from_4(TRX_RSEG + TRX_RSEG_FORMAT +
rseg_header->page.frame))) /* This database must have been upgraded from before MariaDB 10.3.5. */
trx_rseg_format_upgrade(rseg_header, mtr);
#ifdef WITH_WSREP if (wsrep_is_wsrep_xid(&trx->xid))
trx_rseg_update_wsrep_checkpoint(rseg_header, &trx->xid, mtr); #endif
if (trx->mysql_log_file_name && *trx->mysql_log_file_name) /* Update the latest binlog name and offset if log_bin=ON or this
is a slave. */
trx_rseg_update_binlog_offset(rseg_header, trx->mysql_log_file_name,
trx->mysql_log_offset, mtr);
/* Add the log as the first in the history list */
/* We are in transaction commit; we cannot return an error whendetectingcorruption.Itisbettertocrashtheserver thantointentionallyviolateACIDbycommittingsomething
that is known to be corrupted. */
ut_a(flst_add_first(rseg_header, TRX_RSEG + TRX_RSEG_HISTORY, undo_page,
uint16_t(undo_header_offset + TRX_UNDO_HISTORY_NODE),
rseg->space->free_limit, mtr) == DB_SUCCESS);
while (!fseg_free_step_not_header(block,
TRX_UNDO_SEG_HDR + TRX_UNDO_FSEG_HEADER,
&mtr))
{
rseg_hdr->fix();
block->fix();
ut_d(const page_id_t rseg_hdr_id{rseg_hdr->page.id()});
ut_d(const page_id_t id{block->page.id()});
mtr.commit(); /* NOTE: If the server is killed after the log that was produced uptothispointwaswritten,andbeforethelogfromthemtr.commit() inourcalleriswritten,thenthepagesbelongingtothe undologwillbecomeunaccessiblegarbage.
Thisdoesnotmatterwhenusingmultipleinnodb_undo_tablespaces;
innodb_undo_log_truncate=ON will be able to reclaim the space. */
mtr.start();
rseg_hdr->page.lock.x_lock();
ut_ad(rseg_hdr->page.id() == rseg_hdr_id);
block->page.lock.x_lock();
ut_ad(block->page.id() == id);
mtr.memo_push(rseg_hdr, MTR_MEMO_PAGE_X_FIX);
mtr.memo_push(block, MTR_MEMO_PAGE_X_FIX);
}
while (!fseg_free_step(block, TRX_UNDO_SEG_HDR + TRX_UNDO_FSEG_HEADER,
&mtr));
}
void purge_sys_t::rseg_enable(trx_rseg_t &rseg)
{
ut_ad(this == &purge_sys);
ut_ad(rseg.latch.have_wr());
uint8_t skipped= skipped_rseg;
ut_ad(skipped < TRX_SYS_N_RSEGS); if (&rseg == &trx_sys.rseg_array[skipped])
{ /* If this rollback segment is subject to innodb_undo_log_truncate=ON, wemustnotcleartheflag.Butwewilladvancepurge_sys.skipped_rseg tobeabletochooseanothercandidateforthissofttruncation,and topreventthefollowingscenario:
(1)purge_sys_t::iterator::free_history_rseg()hadinvoked rseg.set_skip_allocation() (2)undologtruncationhadcompletedonthisrollbacksegment (3)SETGLOBALinnodb_undo_log_truncate=OFF (4)purge_sys_t::iterator::free_history_rseg()wouldnotbeableto invokerseg.set_skip_allocation()onanyotherrollbacksegment
before this rseg has grown enough */ if (truncate_undo_space.current != rseg.space)
rseg.clear_skip_allocation();
skipped++; /* If innodb_undo_tablespaces>0, the rollback segment 0 (whichalwaysresidesinthesystemtablespace)will
never be used; @see trx_assign_rseg_low() */ if (!(skipped&= (TRX_SYS_N_RSEGS - 1)) && srv_undo_tablespaces)
skipped++;
skipped_rseg= skipped;
}
}
/** Remove unnecessary history data from a rollback segment. @paramrsegrollbacksegment @paramlimittruncateanythingbeforethis
@return error code */ inline dberr_t purge_sys_t::iterator::free_history_rseg(trx_rseg_t &rseg) const
{
fil_addr_t hdr_addr;
mtr_t mtr{nullptr}; bool freed= false;
uint32_t rseg_ref= 0; constauto last_boffset= srv_page_size - TRX_UNDO_LOG_OLD_HDR_SIZE; /* Technically, rseg.space->free_limit is not protected by rseg.latch,whichweareholding,butrseg.space->latch.Thevalue thatwearereadingmaybecomestale(toosmall)ifotherpagesare beingallocatedinthistablespace,forotherrollback segments.Nothingcanbeaddedtothisrsegwithoutholding rseg.latch,andhencewecanvalidatetheentirefile-basedlist againstthelimitthatwearereadinghere.
Note:Thereadheremaylooklikeadatarace.Onnoneofourtarget architecturesthisshouldbeanactualproblem,becausetheuint32_t
value should always fit in a register and be correctly aligned. */ constauto last_page= rseg.space->free_limit;
void purge_sys_t::cleanse_purge_queue(const fil_space_t &space)
{
mysql_mutex_lock(&pq_mutex); auto purge_elem_list= clone_queue_container();
purge_queue.clear(); for (auto elem : purge_elem_list) if (purge_queue::rseg(elem)->space != &space)
purge_queue.push_trx_no_rseg(elem);
mysql_mutex_unlock(&pq_mutex);
}
dberr_t purge_sys_t::iterator::free_history() const
{ for (auto &rseg : trx_sys.rseg_array) if (rseg.space)
{
ut_ad(rseg.is_persistent());
log_free_check();
rseg.latch.wr_lock(SRW_LOCK_CALL);
dberr_t err= free_history_rseg(rseg);
rseg.latch.wr_unlock(); if (err) return err;
} return DB_SUCCESS;
}
inlinevoid trx_sys_t::undo_truncate_start(fil_space_t &space)
{
ut_ad(this == &trx_sys); /* Undo tablespace always are a single file. */
ut_a(UT_LIST_GET_LEN(space.chain) == 1);
fil_node_t *file= UT_LIST_GET_FIRST(space.chain); /* The undo tablespace files are never closed. */
ut_ad(file->is_open());
sql_print_information("InnoDB: Starting to truncate %s", file->name);
for (auto &rseg : rseg_array) if (rseg.space == &space)
{ /* Prevent a race with purge_sys_t::iterator::free_history_rseg() */
rseg.latch.rd_lock(SRW_LOCK_CALL); /* Once set, this rseg will not be allocated to subsequent transactions,butwewillwaitforexistingactive
transactions to finish. */
rseg.set_skip_allocation();
rseg.latch.rd_unlock();
}
}
const uint32_t size=
uint32_t(std::min(ulonglong{std::numeric_limits<uint32_t>::max()},
srv_max_undo_log_size >> srv_page_size_shift)); for (uint32_t i= truncate_undo_space.last, j= i;; )
{ if (fil_space_t *s= undo_truncate_try(srv_undo_space_id_start + i, size)) return s;
++i;
i%= srv_undo_tablespaces_active; if (i == j) return nullptr;
}
}
#ifdefined __GNUC__ && __GNUC__ == 4 && !defined __clang__ # ifdefined __arm__ || defined __aarch64__ /* Work around an internal compiler error in GCC 4.8.5 */
__attribute__((optimize(0))) # endif #endif /** Removeunnecessaryhistorydatafromrollbacksegments.NOTEthatwhenthis functioniscalled,thecaller (purge_coordinator_callbackorpurge_truncation_callback) mustnothaveanylatchesonundologpages!
*/ void trx_purge_truncate_history()
{
ut_ad(purge_sys.head <= purge_sys.tail);
purge_sys_t::iterator &head= purge_sys.head.trx_no
? purge_sys.head : purge_sys.tail;
if (head.trx_no >= purge_sys.low_limit_no())
{ /* This is sometimes necessary. TODO: find out why. */
head.trx_no= purge_sys.low_limit_no();
head.undo_no= 0;
}
if (head.free_history() != DB_SUCCESS) return;
while (fil_space_t *space= purge_sys.truncating_tablespace())
{ for (auto &rseg : trx_sys.rseg_array)
{ if (rseg.space != space) continue;
if (UNIV_UNLIKELY(srv_shutdown_state != SRV_SHUTDOWN_NONE) &&
srv_fast_shutdown) return;
/* Adjust the tablespace metadata. */
mysql_mutex_lock(&fil_system.mutex); if (space->crypt_data)
{
space->reacquire();
mysql_mutex_unlock(&fil_system.mutex);
fil_space_crypt_close_tablespace(space);
space->release();
} else
mysql_mutex_unlock(&fil_system.mutex);
/* Re-initialize tablespace, in a single mini-transaction. */ const uint32_t size= SRV_UNDO_TABLESPACE_SIZE_IN_PAGES;
log_free_check();
mtr_t mtr{nullptr};
mtr.start();
mtr.x_lock_space(space); /* Associate the undo tablespace with mtr. Duringmtr::commit_shrink(),InnoDBcanusetheundo
tablespace object to clear all freed ranges */
mtr.set_named_space(space);
mtr.trim_pages(page_id_t(space->id, size));
ut_a(fsp_header_init(space, size, &mtr) == DB_SUCCESS);
for (auto &rseg : trx_sys.rseg_array)
{ if (rseg.space != space) continue;
ut_ad(!rseg.is_referenced()); /* We may actually have rseg.needs_purge > head.trx_no here iftrx_t::commit_empty()hadbeenexecutedinthepast,
possibly before this server had been started up. */
dberr_t err;
buf_block_t *rblock= trx_rseg_header_create(space,
&rseg - trx_sys.rseg_array,
trx_sys.get_max_trx_id(),
&mtr, &err);
ut_a(rblock); /* These were written by trx_rseg_header_create(). */
ut_ad(!mach_read_from_4(TRX_RSEG + TRX_RSEG_FORMAT +
rblock->page.frame));
ut_ad(!mach_read_from_4(TRX_RSEG + TRX_RSEG_HISTORY_SIZE +
rblock->page.frame));
rseg.reinit(rblock->page.id().page_no());
}
mtr.commit_shrink(*space, size);
/* No mutex; this is only updated by the purge coordinator. */
export_vars.innodb_undo_truncations++;
if (purge_sys.rseg && purge_sys.rseg->last_page_no == FIL_NULL)
{ /* If purge_sys.rseg is pointing to rseg that was recently truncatedthenmovetonextrsegelement.
Note:Ideallypurge_sys.rsegshouldbeNULLbecausepurgeshould completeprocessingofalltherecordsbutsrv_purge_batch_size
can force the purge loop to exit before all the records are purged. */
purge_sys.rseg= nullptr;
purge_sys.next_stored= false;
}
if (UNIV_LIKELY(trx_no != 0))
{
rseg->last_page_no= prev_log_addr.page;
rseg->set_last_commit(prev_log_addr.boffset, trx_no);
/* Purge can also produce events, however these are already orderedintherollbacksegmentandanyusergeneratedevent willbegreaterthantheeventsthatPurgeproduces.ie.Purge
can never produce events from an empty rollback segment. */
/** Position the purge sys "iterator" on the undo record to use for purging. @paramtrxtransactionattachedtocurrent_thd @retvalfalsewhennothingistobepurged
@retval true when purge_sys.rseg->latch was locked */ bool purge_sys_t::choose_next_log(trx_t *trx) noexcept
{
ut_ad(!next_stored);
/* We assume in purge of externally stored fields that space
id is in the range of UNDO tablespace space ids */
ut_ad(rseg->space == fil_system.sys_space ||
srv_is_undo_tablespace(rseg->space->id));
/* Only the purge_coordinator_task will access this any of purge_sys.hdr_page_no,purge_sys.tail.Thefieldpurge_sys.headand purge_sys.viewaremodifiedbyclone_end_view()inthe purge_coordinator_taskwhileholdingexclusivepurge_sys.latch.The purge_sys.viewmayalsobemodifiedbywake_if_not_active()whileholding exclusivepurge_sys.latch.Thepurge_sys.headmaybereadby
purge_truncation_callback(). */
ut_a(hdr_page_no != FIL_NULL);
ut_a(tail.trx_no <= last_trx_no);
tail.trx_no = last_trx_no;
/** Getthenextrecordtopurgeandupdatetheinfointhepurgesystem. @paramtrxtransactionattachedtocurrent_thd @paramroll_ptrundologpointertotherecord @returnbuffer-fixedreferencetoundologrecord @retval{nullptr,1}ifthewholeundologcanskippedinpurge
@retval {nullptr,0} if nothing is left, or on corruption */ inline
trx_purge_rec_t purge_sys_t::get_next_rec(trx_t *trx, roll_ptr_t roll_ptr)
noexcept
{
ut_ad(next_stored);
ut_ad(tail.trx_no < low_limit_no());
ut_ad(rseg->latch.have_wr());
if (!offset)
{ /* It is the dummy undo log record, which means that there is no need to
purge this undo log. Look for the next undo log and record to purge */ if (rseg_get_next_history_log(trx))
rseg->latch.wr_unlock(); return {nullptr, 1};
}
ut_ad(offset == uint16_t(roll_ptr));
page_id_t page_id{rseg->space->id, page_no}; bool locked= true;
buf_block_t *b= get_page(page_id, trx); if (UNIV_UNLIKELY(!b))
{ if (locked)
rseg->latch.wr_unlock(); return {nullptr, 0};
}
buf_block_t *rec2_page= b; if (const trx_undo_rec_t *rec2=
trx_undo_page_get_next_rec(b, offset, hdr_page_no, hdr_offset))
{
got_rec:
ut_ad(page_no == page_id.page_no());
ut_ad(page_offset(rec2) == rec2 - rec2_page->page.frame);
offset= uint16_t(rec2 - rec2_page->page.frame);
tail.undo_no= trx_undo_rec_get_undo_no(rec2);
} elseif (hdr_page_no != page_no ||
!mach_read_from_2(b->page.frame + hdr_offset + TRX_UNDO_NEXT_LOG))
{
uint32_t next= mach_read_from_4(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE +
FLST_NEXT + FIL_ADDR_PAGE + b->page.frame); if (next != FIL_NULL)
{
page_id.set_page_no(next); if (buf_block_t *next_page= get_page(page_id, trx))
{
rec2= trx_undo_page_get_first_rec(next_page, hdr_page_no, hdr_offset); if (rec2)
{
rec2_page= next_page;
page_no= next; goto got_rec;
}
}
} goto got_no_rec;
} else
{
got_no_rec: /* Look for the next undo log and record to purge */
locked= rseg_get_next_history_log(trx);
}
/** Close a single purge table entry. ClearsthecachedTABLE*pointerfromvc_templ,closesthe dict_table_t,andcollectstheMDL_ticketforlaterrelease. @paramptpurge_tableentrytoclose
@param mdl_tickets vector to collect standalone MDL tickets */ staticvoid trx_purge_close_table(purge_table &pt,
std::vector<MDL_ticket *> &mdl_tickets)
{ if (pt.table && !pt.must_wait())
{ if (TABLE *maria_table= pt.get_maria_table())
purge_sys.reset_in_use(maria_table);
pt.table->release();
pt.table= nullptr;
}
MDL_ticket *ticket= pt.get_ticket(); if (ticket)
{
mdl_tickets.push_back(ticket);
pt.set_ticket(nullptr);
}
}
/** Close all tables that were opened in a purge batch for a worker. @paramthdpurgecoordinatorthreadhandle @paramlast_entryadditionaltabletoclose(notinnode->tables), typicallythetablethattriggered close_and_reopen();nullptrifnoadditional tabletoclose @parambatch_cleanupiftrue,clearsthelistofopenedtables
in the purge node. */ staticvoid trx_purge_close_tables(THD *thd, purge_table *last_entry, bool batch_cleanup=false) noexcept
{
MDL_context *mdl_context = static_cast<MDL_context*>(thd_mdl_context(thd));
std::vector<MDL_ticket *> mdl_tickets; if (last_entry)
trx_purge_close_table(*last_entry, mdl_tickets);
for (que_thr_t *thr= UT_LIST_GET_FIRST(purge_sys.query->thrs); thr;
thr= UT_LIST_GET_NEXT(thrs, thr))
{
purge_node_t* node = static_cast<purge_node_t*>(thr->child); for (auto &t : node->tables)
trx_purge_close_table(t.second, mdl_tickets); if (batch_cleanup)
node->tables.clear();
}
close_thread_tables(thd);
for (auto mdl : mdl_tickets)
{ if (mdl && mdl_context)
mdl_context->release_lock(mdl);
}
}
__attribute__((nonnull)) /** Acquire a metadata lock on a table. @paramtabletablehandle @parammdl_contextmetadatalockacquisitioncontext @parammdlmetadatalock @returntablehandle @retvalnullptrifthetableisnotfoundoraccessible
@retval -1 if the purge of history must be suspended due to DDL */ static dict_table_t *trx_purge_table_acquire(dict_table_t *table,
MDL_context *mdl_context,
MDL_ticket **mdl) noexcept
{
ut_ad(dict_sys.frozen_not_locked());
*mdl= nullptr;
if (!table->is_readable() || table->corrupted) return nullptr;
size_t db_len= dict_get_db_name_len(table->name.m_name); if (db_len == 0)
{ /* InnoDB system tables are not covered by MDL */
got_table:
table->acquire(); return table;
}
if (purge_sys.must_wait_FTS())
must_wait: returnreinterpret_cast<dict_table_t*>(-1);
/** Open a table handle for the purge of committed transaction history @paramtable_idInnoDBtableidentifier @parammdl_contextmetadatalockacquisitioncontext @returnpurge_tablewithdict_table_t*andTABLE*(or)MDL_ticket,
possibly with must_wait() || table == nullptr */ static purge_table trx_purge_table_open(table_id_t table_id,
MDL_context *mdl_context) noexcept
{
purge_table result;
MDL_ticket *mdl= nullptr;
for (;;)
{
dict_sys.freeze(SRW_LOCK_CALL);
result.table= dict_sys.find_table(table_id); if (result.table) break;
dict_sys.unfreeze();
dict_sys.lock(SRW_LOCK_CALL);
result.table= dict_load_table_on_id(table_id, DICT_ERR_IGNORE_FK_NOKEY);
dict_sys.unlock(); if (!result.table) return result; /* At this point, the freshly loaded table may already have been evicted. Wemustlookitupagainwhileholdingashareddict_sys.latch.Wekeep tryingthisuntilthetableisfoundinthecacheoritcannotbefound
in the dictionary (because the table has been dropped or rebuilt). */
}
for (THD *const thd{trx->mysql_thd};
UNIV_LIKELY(srv_undo_sources) || !srv_fast_shutdown; )
{ /* Track the max {trx_id, undo_no} for truncating the
UNDO logs once we have purged the records. */
if (head <= purge_sys.tail)
head= purge_sys.tail;
/* Fetch the next record, and advance the purge_sys.tail. */
trx_purge_rec_t purge_rec= purge_sys.fetch_next_rec(trx);
if (!purge_rec.undo_rec)
{ if (!purge_rec.roll_ptr) break;
ut_ad(purge_rec.roll_ptr == 1); continue;
}
table_id_t table_id= trx_undo_rec_get_table_id(purge_rec.undo_rec);
purge_node_t *&table_node= table_id_map[table_id]; if (table_node)
{
ut_ad(!table_node->in_progress); if (table_node->tables[table_id].table)
{
enqueue:
table_node->undo_recs.push(purge_rec);
ut_ad(!table_node->in_progress);
}
} else
{
purge_table pt= trx_purge_table_open(table_id, &thd->mdl_context); if (pt.must_wait())
pt= purge_sys.close_and_reopen(table_id, pt, thd);
/** Wait for pending purge jobs to complete. */ staticvoid trx_purge_wait_for_workers_to_complete()
{ constbool notify_wait{purge_worker_task.is_running()};
if (notify_wait)
tpool::tpool_wait_begin();
purge_worker_task.wait();
if (notify_wait)
tpool::tpool_wait_end();
/* There should be no outstanding tasks as long
as the worker threads are active. */
ut_ad(srv_get_task_queue_length() == 0);
}
/* Release the undo pages. */ for (auto p : pages)
p.second->unfix();
pages.clear();
pages.reserve(srv_purge_batch_size);
/* This is only invoked only by the purge coordinator, whichistheonlythreadthatcanmodifyourinputshead,tail,view.
Therefore, we only need to protect end_view from concurrent reads. */
/* Limit the end_view similar to what trx_purge_truncate_history() does. */ const trx_id_t trx_no= head.trx_no ? head.trx_no : tail.trx_no;
end_latch.wr_lock(); this->head= head;
end_view= view;
end_view.clamp_low_limit_id(trx_no);
end_latch.wr_unlock();
coordinator_thd= nullptr;
}
/** Runapurgebatch. @paramtrxdummytransactionassociatedwiththepurgecoordinator @paramn_tasksnumberofpurgetaskstosubmittothequeue @paramhistory_sizetrx_sys.history_size()
@return number of undo log pages handled in the batch */
ulint trx_purge(trx_t *trx, ulint n_tasks, ulint history_size) noexcept
{
ut_ad(n_tasks > 0);
#ifdef UNIV_DEBUG if (srv_purge_view_update_only_debug) return purge_sys.reset_coordinator(); #endif/* UNIV_DEBUG */
/* Fetch the UNDO recs that need to be purged. */
ulint n_work= 0; const purge_sys_t::iterator head= trx_purge_attach_undo_recs(trx, &n_work); const size_t n_pages= purge_sys.n_pages_handled();
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.31Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-10-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.