/* Considerations on undoing a modify operation. (1)Undoingadeletemarking:allindexrecordsshouldbefound.Someof themmayhavedeletemarkalreadyFALSE,ifthedeletemarkoperationwas stoppedunderway,oriftheundooperationendedprematurelybecauseofa systemcrash. (2)Undoinganupdateofadeleteunmarkedrecord:thenewerversionof anupdatedsecondaryindexentryshouldberemovedifnopriorversion oftheclusteredindexrecordrequiresitsexistence.Otherwise,itshould bedeletemarked. (3)Undoinganupdateofadeletemarkedrecord.Inthiskindofupdatea deletemarkedclusteredindexrecordwasdeleteunmarkedandpossiblyalso someofitsfieldswerechanged.Now,itispossiblethatthedeletemarked
version has become obsolete at the time the undo is started. */
/************************************************************************* IMPORTANTNOTE:AnyoperationthatgeneratesredoMUSTcheckthatthere isenoughspaceintheredologbeforeforthatoperation.Thisis donebycallinglog_free_check().Thereasonforcheckingthe availabilityoftheredologspacebeforethestartoftheoperationis thatweMUSTnotholdanysynchonizationobjectswhenperformingthe check. Ifyoumakeachangeinthismodulemakesurethatnocodepathis
introduced where a call to log_free_check() is bypassed. */
/***********************************************************//**
Undoes a modify in a clustered index record.
@return DB_SUCCESS, DB_FAIL, or error code: we may run out of file space */ static MY_ATTRIBUTE((nonnull, warn_unused_result))
dberr_t
row_undo_mod_clust_low( /*===================*/
undo_node_t* node, /*!< in: row undo node */
rec_offs** offsets,/*!< out: rec_get_offsets() on the record */
mem_heap_t** offsets_heap, /*!< in/out: memory heap that can be emptied */
mem_heap_t* heap, /*!< in/out: memory heap */
byte* sys, /*!< out: DB_TRX_ID, DB_ROLL_PTR
for row_log_table_delete() */
que_thr_t* thr, /*!< in: query thread */
mtr_t* mtr, /*!< in: mtr; must be committed before
latching any further pages */
btr_latch_mode mode) /*!< in: BTR_MODIFY_LEAF or BTR_MODIFY_TREE */
{
btr_pcur_t* pcur;
btr_cur_t* btr_cur;
dberr_t err;
switch (constauto id = btr_cur_get_index(btr_cur)->table->id) { unsigned c; case DICT_TABLES_ID: if (node->trx != trx_roll_crash_recv_trx) { break;
}
c = DICT_COL__SYS_TABLES__ID; goto evict; case DICT_INDEXES_ID: if (node->trx != trx_roll_crash_recv_trx) { break;
} elseif (node->rec_type == TRX_UNDO_DEL_MARK_REC
&& btr_cur_get_rec(btr_cur)
[8 + 8 + DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN]
== static_cast<byte>(*TEMP_INDEX_PREFIX_STR)) { /* We are rolling back the DELETE of metadata forafailedADDINDEXoperation.Thisdoes notaffectanycachedtabledefinition, becausewearefilteringoutsuchindexesin
dict_load_indexes(). */ break;
} /* fall through */ case DICT_COLUMNS_ID:
static_assert(!DICT_COL__SYS_INDEXES__TABLE_ID, "");
static_assert(!DICT_COL__SYS_COLUMNS__TABLE_ID, "");
c = DICT_COL__SYS_COLUMNS__TABLE_ID; /* This is rolling back an UPDATE or DELETE on SYS_COLUMNS. IfitwaspartofaninstantALTERTABLEoperation,we mustevictthetabledefinition,sothatitcanbe reloadedafterthedictionaryoperationhasbeen completed.Atthispoint,anycorrespondingoperation
to the metadata record will have been rolled back. */
evict: const dfield_t& table_id = *dtuple_get_nth_field(node->row, c);
ut_ad(dfield_get_len(&table_id) == 8);
node->trx->evict_table(mach_read_from_8( static_cast<byte*>(
table_id.data)),
id == DICT_COLUMNS_ID);
}
return DB_SUCCESS;
}
/** Get the byte offset of the DB_TRX_ID column @param[in]recclusteredindexrecord @param[in]indexclusteredindex
@return the byte offset of DB_TRX_ID, from the start of rec */
ulint row_trx_id_offset(const rec_t* rec, const dict_index_t* index)
{
ut_ad(index->n_uniq <= MAX_REF_PARTS);
ulint trx_id_offset = index->trx_id_offset; if (!trx_id_offset) { /* Reserve enough offsets for the PRIMARY KEY and 2 columns
so that we can access DB_TRX_ID, DB_ROLL_PTR. */
rec_offs offsets_[REC_OFFS_HEADER_SIZE + MAX_REF_PARTS + 2];
rec_offs_init(offsets_);
mem_heap_t* heap = NULL; const ulint trx_id_pos = index->n_uniq ? index->n_uniq : 1;
rec_offs* offsets = rec_get_offsets(rec, index, offsets_,
index->n_core_fields,
trx_id_pos + 1, &heap);
ut_ad(!heap);
ulint len;
trx_id_offset = rec_get_nth_field_offs(
offsets, trx_id_pos, &len);
ut_ad(len == DATA_TRX_ID_LEN);
}
return trx_id_offset;
}
/** Determine if rollback must execute a purge-like operation. @paramnoderowundo
@return whether the record should be purged */ staticbool row_undo_mod_must_purge(const undo_node_t &node)
{
ut_ad(node.rec_type == TRX_UNDO_UPD_DEL_REC);
ut_ad(!node.table->is_temporary());
/***********************************************************//**
Undoes a modify in a clustered index record. Sets also the node state for the
next round of undo.
@return DB_SUCCESS or error code: we may run out of file space */ static MY_ATTRIBUTE((nonnull, warn_unused_result))
dberr_t
row_undo_mod_clust( /*===============*/
undo_node_t* node, /*!< in: row undo node */
que_thr_t* thr) /*!< in: query thread */
{
btr_pcur_t* pcur;
mtr_t mtr{node->trx};
dberr_t err;
dict_index_t* index;
/* FIXME: Perform the below operations in the above
mini-transaction when possible. */
if (node->rec_type == TRX_UNDO_UPD_DEL_REC) { /* In delete-marked records, DB_TRX_ID must
always refer to an existing update_undo log record. */
ut_ad(node->new_trx_id);
mtr.start(); if (pcur->restore_position(BTR_MODIFY_LEAF, &mtr) !=
btr_pcur_t::SAME_ALL) { goto mtr_commit_exit;
}
if (index->table->is_temporary()) {
mtr.set_log_mode(MTR_LOG_NO_REDO);
} else { if (!row_undo_mod_must_purge(*node)) { goto mtr_commit_exit;
}
index->set_modified(mtr);
}
/* This operation is analogous to purge, we can free alsoinheritedexternallystoredfields.Wecanalso assumethattherecordwascomplete(includingBLOBs), becauseithadbeendelete-markedafterithadbeen completelyinserted.Therefore,wearepassing
rollback=false, just like purge does. */
btr_cur_pessimistic_delete(&err, FALSE, &pcur->btr_cur, 0, false, &mtr);
ut_ad(err == DB_SUCCESS || err == DB_OUT_OF_FILE_SPACE);
} elseif (!index->table->is_temporary() && node->new_trx_id) { /* We rolled back a record so that it still exists. WemustresettheDB_TRX_IDifthehistoryisno
longer accessible by any active read view. */
if (dict_index_has_virtual(index)) {
v_heap = mem_heap_create(100); /* The current cluster index record could be deleted,butthepreviousversionofitmightnot.Wewill needtogetthevirtualcolumndatafromundorecord
associated with current cluster index */
if (dict_index_has_virtual(index)) { if (vrow) { if (dtuple_vcol_data_missing(*vrow, *index)) { goto nochange_index;
} /* Keep the virtual row info for the next
version, unless it is changed */
mem_heap_empty(v_heap);
cur_vrow = dtuple_copy(vrow, v_heap);
dtuple_dup_v_fld(cur_vrow, v_heap);
}
if (!cur_vrow) { /* Nothing for this index has changed,
continue */
nochange_index:
version = prev_version; continue;
}
}
if (!rec_get_deleted_flag(prev_version, comp)) {
row_ext_t* ext;
/* The stack of versions is locked by mtr. Thus,itissafetofetchtheprefixesfor
externally stored columns. */
row = row_build(ROW_COPY_POINTERS, clust_index,
prev_version, clust_offsets,
NULL, NULL, NULL, &ext, heap);
if (dict_index_has_virtual(index)) {
ut_ad(cur_vrow);
ut_ad(row->n_v_fields == cur_vrow->n_v_fields);
dtuple_copy_v_fields(row, cur_vrow);
}
/* If entry == NULL, the record contains unset BLOBpointers.Thismustbeafreshly insertedrecordthatwecansafelyignore. Forthejustification,seethecommentsafter
the previous row_build_index_entry() call. */
/* NOTE that we cannot do the comparison as binary fieldsbecausemaybethesecondaryindexrecordhas alreadybeenupdatedtoadifferentbinaryvaluein acharfield,butthecollationidentifiestheold
and new value anyway! */
if (entry && dtuple_coll_eq(*ientry, *entry)) { break;
}
}
version = prev_version;
}
mem_heap_free(heap);
if (v_heap) {
mem_heap_free(v_heap);
}
return !!prev_version;
}
/***********************************************************//** Delete marks or removes a secondary index entry if found.
@return DB_SUCCESS, DB_FAIL, or DB_OUT_OF_FILE_SPACE */ static MY_ATTRIBUTE((nonnull, warn_unused_result))
dberr_t
row_undo_mod_del_mark_or_remove_sec_low( /*====================================*/
undo_node_t* node, /*!< in: row undo node */
que_thr_t* thr, /*!< in: query thread */
dict_index_t* index, /*!< in: index */
dtuple_t* entry, /*!< in: index entry */
btr_latch_mode mode) /*!< in: latch mode BTR_MODIFY_LEAF or
BTR_MODIFY_TREE */
{
btr_pcur_t pcur;
btr_cur_t* btr_cur;
dberr_t err = DB_SUCCESS;
mtr_t mtr{thr->graph->trx}; constbool modify_leaf = mode == BTR_MODIFY_LEAF;
if (index->is_spatial()) {
mode = modify_leaf
? btr_latch_mode(BTR_MODIFY_LEAF
| BTR_RTREE_DELETE_MARK
| BTR_RTREE_UNDO_INS)
: btr_latch_mode(BTR_PURGE_TREE | BTR_RTREE_UNDO_INS); if (UNIV_LIKELY(!rtr_search(entry, mode, &pcur, thr, &mtr))) { goto found;
} else { goto func_exit;
}
} elseif (!index->is_committed()) { /* The index->online_status may change if the index is orwasbeingcreatedonline,butnotcommittedyet.It
is protected by index->lock. */ if (modify_leaf) {
mode = BTR_MODIFY_LEAF_ALREADY_LATCHED;
mtr_s_lock_index(index, &mtr);
} else {
ut_ad(mode == BTR_PURGE_TREE);
mode = BTR_PURGE_TREE_ALREADY_LATCHED;
mtr_x_lock_index(index, &mtr);
}
} else { /* For secondary indexes, index->online_status==ONLINE_INDEX_COMPLETEif
index->is_committed(). */
ut_ad(!dict_index_is_online_ddl(index));
}
if (!row_search_index_entry(entry, mode, &pcur, &mtr)) { /* In crash recovery, the secondary index record may bemissingiftheUPDATEdidnothavetimetoinsert thesecondaryindexrecordsbeforethecrash.Whenwe areundoingthatUPDATEincrashrecovery,therecord maybemissing.
Innormalprocessing,ifanupdateendsinadeadlock beforeithasinsertedallupdatedsecondaryindex
records, then the undo will not find those records. */ goto func_exit;
}
found: /* We should remove the index record if no prior version of the row, whichcannotbepurgedyet,requiresitsexistence.Ifsomerequires,
we should delete mark the record. */
/* For temporary table, we can skip to check older version of
clustered index entry, because there is no MVCC or purge. */ if (node->table->is_temporary()
|| row_undo_mod_sec_is_unsafe(
btr_pcur_get_rec(&node->pcur), index, entry, &mtr)) {
btr_rec_set_deleted<true>(btr_cur_get_block(btr_cur),
btr_cur_get_rec(btr_cur), &mtr);
} else { /* Remove the index record */
if (dict_index_is_spatial(index)) {
rec_t* rec = btr_pcur_get_rec(&pcur); if (rec_get_deleted_flag(rec,
dict_table_is_comp(index->table))) {
ib::error() << "Record found in index "
<< index->name << " is deleted marked" " on rollback update.";
ut_ad(0);
}
}
if (modify_leaf) {
err = btr_cur_optimistic_delete(btr_cur, 0, &mtr);
} else { /* Passing rollback=false, becausewearedeletingasecondaryindexrecord: thedistinctiononlymatterswhendeletinga
record that contains externally stored columns. */
ut_ad(!index->is_primary());
btr_cur_pessimistic_delete(&err, FALSE, btr_cur, 0, false, &mtr);
/* The delete operation may fail if we have little filespaceleft:TODO:easiesttocrashthedatabase
and restart with more file space */
}
}
/***********************************************************//** Delete marks or removes a secondary index entry if found.
NOTE that if we updated the fields of a delete-marked secondary index record
so that alphabetically they stayed the same, e.g., 'abc' -> 'aBc', we cannot return to the original values because we donot know them. But this should not cause problems because in row0sel.cc, in queries we always retrieve the
clustered index record or an earlier version of it, if the secondary index
record through which we do the search is delete-marked.
@return DB_SUCCESS or DB_OUT_OF_FILE_SPACE */ static MY_ATTRIBUTE((nonnull, warn_unused_result))
dberr_t
row_undo_mod_del_mark_or_remove_sec( /*================================*/
undo_node_t* node, /*!< in: row undo node */
que_thr_t* thr, /*!< in: query thread */
dict_index_t* index, /*!< in: index */
dtuple_t* entry) /*!< in: index entry */
{
dberr_t err;
/***********************************************************//** Delete unmarks a secondary index entry which must be found. It might not be delete-marked at the moment, but it does not harm to unmark it anyway. We also
need to update the fields of the secondary index record if we updated its
fields but alphabetically they stayed the same, e.g., 'abc' -> 'aBc'.
@retval DB_SUCCESS on success
@retval DB_FAIL if BTR_MODIFY_TREE should be tried
@retval DB_OUT_OF_FILE_SPACE when running out of tablespace
@retval DB_DUPLICATE_KEY if the value was missing and an insert would lead to a duplicate exists */ static MY_ATTRIBUTE((nonnull, warn_unused_result))
dberr_t
row_undo_mod_del_unmark_sec_and_undo_update( /*========================================*/
btr_latch_mode mode, /*!< in: search mode: BTR_MODIFY_LEAF or
BTR_MODIFY_TREE */
que_thr_t* thr, /*!< in: query thread */
dict_index_t* index, /*!< in: index */
dtuple_t* entry) /*!< in: index entry */
{
btr_pcur_t pcur;
btr_cur_t* btr_cur = btr_pcur_get_btr_cur(&pcur);
upd_t* update;
dberr_t err = DB_SUCCESS;
big_rec_t* dummy_big_rec;
trx_t* trx = thr_get_trx(thr);
mtr_t mtr{trx}; const ulint flags
= BTR_KEEP_SYS_FLAG | BTR_NO_LOCKING_FLAG; constauto orig_mode = mode;
if (index->is_spatial()) { /* FIXME: Currently we do a 2-pass search for the undo duetoavoidundel-markawrongrecinrollingbackin partialupdate.Later,wecouldlogsomeinfoin
secondary index updates to avoid this. */
static_assert(BTR_MODIFY_TREE == (8 | BTR_MODIFY_LEAF), "");
ut_ad(!(mode & 8));
mode = btr_latch_mode(mode | BTR_RTREE_DELETE_MARK);
}
if (!row_search_index_entry(entry, mode, &pcur, &mtr)) {
not_found: if (btr_cur->up_match >= dict_index_get_n_unique(index)
|| btr_cur->low_match >= dict_index_get_n_unique(index)) {
ib::warn() << "Record in index " << index->name
<< " of table " << index->table->name
<< " was not found on rollback, and" " a duplicate exists: "
<< *entry
<< " at: " << rec_index_print(
btr_cur_get_rec(btr_cur), index);
err = DB_DUPLICATE_KEY; goto func_exit;
}
ib::warn() << "Record in index " << index->name
<< " of table " << index->table->name
<< " was not found on rollback, trying to insert: "
<< *entry
<< " at: " << rec_index_print(
btr_cur_get_rec(btr_cur), index);
/* Insert the missing record that we were trying to
delete-unmark. */
big_rec_t* big_rec;
rec_t* insert_rec;
if (err == DB_DUPLICATE_KEY) {
index->type |= DICT_CORRUPT;
err = DB_SUCCESS; /* Do not return any error to the caller. The duplicatewillbereportedbyALTERTABLEor CREATEUNIQUEINDEX.Unfortunatelywecannot reporttheduplicatekeyvaluetotheDDL thread,becausethealtered_tableobjectis
private to its call stack. */
} elseif (err != DB_SUCCESS) { break;
}
mem_heap_empty(heap);
} while ((node->index = dict_table_get_next_index(node->index)));
mem_heap_free(heap);
return(err);
}
/***********************************************************//**
Undoes a modify in secondary indexes when undo record type is UPD_EXIST.
@return DB_SUCCESS or DB_OUT_OF_FILE_SPACE */ static MY_ATTRIBUTE((nonnull, warn_unused_result))
dberr_t
row_undo_mod_upd_exist_sec( /*=======================*/
undo_node_t* node, /*!< in: row undo node */
que_thr_t* thr) /*!< in: query thread */
{ if (node->cmpl_info & UPD_NODE_NO_ORD_CHANGE) { return DB_SUCCESS;
}
/* Build the newest version of the index entry */
dtuple_t* entry = row_build_index_entry(
node->row, node->ext, index, heap); if (UNIV_UNLIKELY(!entry)) { /* InnoDB must have run of space or been killed beforetheupdatedexternallystoredcolumns(BLOBs)
of the new clustered index entry were written. */
/* The table must be in DYNAMIC or COMPRESSED format.REDUNDANTandCOMPACTformats storealocal768-byteprefixofeach
externally stored column. */
ut_a(dict_table_has_atomic_blobs(index->table));
} else { /* NOTE that if we updated the fields of a delete-markedsecondaryindexrecordsothat alphabeticallytheystayedthesame,e.g., 'abc'->'aBc',wecannotreturntothe originalvaluesbecausewedonotknowthem. Butthisshouldnotcauseproblemsbecause inrow0sel.cc,inquerieswealwaysretrieve theclusteredindexrecordoranearlier versionofit,ifthesecondaryindexrecord throughwhichwedothesearchis
delete-marked. */
mem_heap_empty(heap); /* We may have to update the delete mark in the secondaryindexrecordofthepreviousversionof therow.Wealsoneedtoupdatethefieldsof thesecondaryindexrecordifweupdateditsfields butalphabeticallytheystayedthesame,e.g.,
'abc' -> 'aBc'. */
entry = row_build_index_entry_low(node->undo_row,
node->undo_ext,
index, heap,
ROW_BUILD_FOR_UNDO);
ut_a(entry);
if (UNIV_UNLIKELY(!node->table->is_accessible())) {
close_table: /* Normally, tables should not disappear or become unaccessibleduringROLLBACK,becausetheyshouldbe protectedbyInnoDBtablelocks.Corruptioncouldbe avalidexception.
FIXME:Whenrunningoutoftemporarytablespace,it wouldprobablybebettertojustdropalltemporary tables(andtemporaryundologrecords)ofthecurrent
connection, instead of doing this rollback. */
node->table->release();
node->table = NULL; returnfalse;
}
if (node->update->info_bits & REC_INFO_MIN_REC_FLAG) { if ((node->update->info_bits & ~REC_INFO_DELETED_FLAG)
!= REC_INFO_MIN_REC_FLAG) {
ut_ad("wrong info_bits in undo log record" == 0); goto close_table;
} /* This must be an undo log record for a subsequent
instant ALTER TABLE, extending the metadata record. */
ut_ad(clust_index->is_instant());
ut_ad(clust_index->table->instant
|| !(node->update->info_bits & REC_INFO_DELETED_FLAG));
node->ref = &trx_undo_metadata;
node->update->info_bits = (node->update->info_bits
& REC_INFO_DELETED_FLAG)
? REC_INFO_METADATA_ALTER
: REC_INFO_METADATA_ADD;
}
if (!row_undo_search_clust_to_pcur(node)) { /* As long as this rolling-back transaction exists, thePRIMARYKEYvaluepointedtobytheundolog recordshouldexist.
if (err == DB_SUCCESS && node->table->stat_initialized()) { switch (node->rec_type) { case TRX_UNDO_UPD_EXIST_REC: break; case TRX_UNDO_DEL_MARK_REC:
dict_table_n_rows_inc(node->table);
update_statistics = update_statistics
|| !srv_stats_include_delete_marked; break; case TRX_UNDO_UPD_DEL_REC:
dict_table_n_rows_dec(node->table);
update_statistics = update_statistics
|| !srv_stats_include_delete_marked; break;
}
/* Do not attempt to update statistics when executingROLLBACKintheInnoDBSQL interpreter,becauseinthatcasewewould alreadybeholdingdict_sys.latch,which
would be acquired when updating statistics. */ if (update_statistics && !dict_locked) {
dict_stats_update_if_needed(node->table,
*node->trx);
} else {
node->table->stat_modified_counter++;
}
}
}
node->table->release();
node->table = NULL;
return(err);
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.21 Sekunden
(vorverarbeitet am 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.