Commit b7b9f3ce authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-34515: Contention between purge and workload

In a Sysbench oltp_update_index workload that involves 1 table,
a serious contention between the workload and the purge of history
was observed. This was the worst when the table contained only 1 record.

This turned out to be fixed by setting innodb_purge_batch_size=128,
which corresponds to the number of usable persistent rollback segments.
When we go above that, there would be contention between row_purge_poss_sec()
and the workload, typically on the clustered index page latch, sometimes
also on a secondary index page latch. It might be that with smaller
batches, trx_sys.history_size() will end up pausing all concurrent
transaction start/commit frequently enough so that purge will be able
to make some progress, so that there would be less contention on the
index page latches between purge and SQL execution.

In commit aa719b50 (part of MDEV-32050)
the interpretation of the parameter innodb_purge_batch_size was slightly
changed. It would correspond to the maximum desired size of the
purge_sys.pages cache. Before that change, the parameter was referring to
a number of undo log pages, but the accounting might have been inaccurate.

To avoid a regression, we will reduce the default value to
innodb_purge_batch_size=127, which will also be compatible with
innodb_undo_tablespaces>1 (which will disable rollback segment 0).

Additionally, some logic in the purge and MVCC checks is simplified.
The purge tasks will make use of purge_sys.pages when accessing undo
log pages to find out if a secondary index record can be removed.
If an undo page needs to be looked up in buf_pool.page_hash, we will
merely buffer-fix it. This is correct, because the undo pages are
append-only in nature. Holding purge_sys.latch or purge_sys.end_latch
or the fact that the current thread is executing as a part of an
in-progress purge batch will prevent the contents of the undo page from
being freed and subsequently reused. The buffer-fix will prevent the
page from being evicted form the buffer pool. Thanks to this logic,
we can refer to the undo log record directly in the buffer pool page
and avoid copying the record.

buf_pool_t::page_fix(): Look up and buffer-fix a page. This is useful
for accessing undo log pages, which are append-only by nature.
There will be no need to deal with change buffer or ROW_FORMAT=COMPRESSED
in that case.

purge_sys_t::view_guard::view_guard(): Allow the type of guard to be
acquired: end_latch, latch, or no latch (in case we are a purge thread).

purge_sys_t::view_guard::get(): Read-only accessor to purge_sys.pages.

purge_sys_t::get_page(): Invoke buf_pool_t::page_fix().

row_vers_old_has_index_entry(): Replaced with row_purge_is_unsafe()
and row_undo_mod_sec_unsafe().

trx_undo_get_undo_rec(): Merged to trx_undo_prev_version_build().

row_purge_poss_sec(): Add the parameter mtr and remove redundant
or unused parameters sec_pcur, sec_mtr, is_tree. We will use the
caller's mtr object but release any acquired page latches before
returning.

btr_cur_get_page(), page_cur_get_page(): Do not invoke page_align().

row_purge_remove_sec_if_poss_leaf(): Return the value of PAGE_MAX_TRX_ID
to be checked against the page in row_purge_remove_sec_if_poss_tree().
If the secondary index page was not changed meanwhile, it will be
unnecessary to invoke row_purge_poss_sec() again.

trx_undo_prev_version_build(): Access any undo log pages using
the caller's mini-transaction object.

row_purge_vc_matches_cluster(): Moved to the only compilation unit that
needs it.

Reviewed by: Debarun Banerjee
parent d58734d7
SET @global_start_value = @@global.innodb_purge_batch_size;
SELECT @global_start_value;
@global_start_value
1000
127
'#--------------------FN_DYNVARS_046_01------------------------#'
SET @@global.innodb_purge_batch_size = 1;
SET @@global.innodb_purge_batch_size = DEFAULT;
SELECT @@global.innodb_purge_batch_size;
@@global.innodb_purge_batch_size
1000
127
'#---------------------FN_DYNVARS_046_02-------------------------#'
SET innodb_purge_batch_size = 1;
ERROR HY000: Variable 'innodb_purge_batch_size' is a GLOBAL variable and should be set with SET GLOBAL
SELECT @@innodb_purge_batch_size;
@@innodb_purge_batch_size
1000
127
SELECT local.innodb_purge_batch_size;
ERROR 42S02: Unknown table 'local' in field list
SET global innodb_purge_batch_size = 1;
......@@ -112,4 +112,4 @@ SELECT @@global.innodb_purge_batch_size;
SET @@global.innodb_purge_batch_size = @global_start_value;
SELECT @@global.innodb_purge_batch_size;
@@global.innodb_purge_batch_size
1000
127
......@@ -1293,7 +1293,7 @@ READ_ONLY NO
COMMAND_LINE_ARGUMENT OPTIONAL
VARIABLE_NAME INNODB_PURGE_BATCH_SIZE
SESSION_VALUE NULL
DEFAULT_VALUE 1000
DEFAULT_VALUE 127
VARIABLE_SCOPE GLOBAL
VARIABLE_TYPE BIGINT UNSIGNED
VARIABLE_COMMENT Number of UNDO log pages to purge in one batch from the history list.
......
......@@ -1277,7 +1277,7 @@ dberr_t btr_cur_t::search_leaf(const dtuple_t *tuple, page_cur_mode_t mode,
ut_ad(buf_mode == BUF_GET_IF_IN_POOL_OR_WATCH);
auto& chain = buf_pool.page_hash.cell_get(page_id.fold());
if (!row_purge_poss_sec(purge_node, index(), tuple))
if (!row_purge_poss_sec(purge_node, index(), tuple, mtr))
/* The record cannot be purged yet. */
flag= BTR_CUR_DELETE_REF;
else if (ibuf_insert(IBUF_OP_DELETE, tuple, index(),
......
......@@ -2476,6 +2476,51 @@ static bool buf_page_ibuf_merge_try(buf_block_t *block, ulint rw_latch,
return false;
}
buf_block_t* buf_pool_t::page_fix(const page_id_t id)
{
ha_handler_stats *const stats= mariadb_stats;
buf_inc_get(stats);
auto& chain= page_hash.cell_get(id.fold());
page_hash_latch &hash_lock= page_hash.lock_get(chain);
for (;;)
{
hash_lock.lock_shared();
buf_page_t *b= page_hash.get(id, chain);
if (b)
{
uint32_t state= b->fix();
hash_lock.unlock_shared();
ut_ad(!b->in_zip_hash);
ut_ad(b->frame);
ut_ad(state >= buf_page_t::FREED);
if (state >= buf_page_t::READ_FIX && state < buf_page_t::WRITE_FIX)
{
b->lock.s_lock();
state= b->state();
ut_ad(state < buf_page_t::READ_FIX || state >= buf_page_t::WRITE_FIX);
b->lock.s_unlock();
}
if (UNIV_UNLIKELY(state < buf_page_t::UNFIXED))
{
/* The page was marked as freed or corrupted. */
b->unfix();
b= nullptr;
}
return reinterpret_cast<buf_block_t*>(b);
}
hash_lock.unlock_shared();
switch (buf_read_page(id, 0)) {
default:
return nullptr;
case DB_SUCCESS:
case DB_SUCCESS_LOCKED_REC:
mariadb_increment_pages_read(stats);
buf_read_ahead_random(id, 0, false);
}
}
}
/** Low level function used to get access to a database page.
@param[in] page_id page id
@param[in] zip_size ROW_FORMAT=COMPRESSED page size, or 0
......
......@@ -18946,7 +18946,7 @@ static MYSQL_SYSVAR_ULONG(purge_batch_size, srv_purge_batch_size,
PLUGIN_VAR_OPCMDARG,
"Number of UNDO log pages to purge in one batch from the history list.",
NULL, NULL,
1000, /* Default setting */
127, /* Default setting */
1, /* Minimum value */
innodb_purge_batch_size_MAX, 0);
......
......@@ -78,14 +78,10 @@ page_zip_des_t*
btr_cur_get_page_zip(
/*=================*/
btr_cur_t* cursor);/*!< in: tree cursor */
/*********************************************************//**
Returns the page of a tree cursor.
/** Returns the page of a tree cursor.
@return pointer to page */
UNIV_INLINE
page_t*
btr_cur_get_page(
/*=============*/
btr_cur_t* cursor);/*!< in: tree cursor */
#define btr_cur_get_page(cursor) (cursor)->block()->page.frame
/*********************************************************//**
Returns the index of a cursor.
@param cursor b-tree cursor
......
......@@ -48,18 +48,6 @@ btr_cur_get_page_zip(
return(buf_block_get_page_zip(btr_cur_get_block(cursor)));
}
/*********************************************************//**
Returns the page of a tree cursor.
@return pointer to page */
UNIV_INLINE
page_t*
btr_cur_get_page(
/*=============*/
btr_cur_t* cursor) /*!< in: tree cursor */
{
return(page_align(page_cur_get_rec(&(cursor->page_cur))));
}
/*********************************************************//**
Positions a tree cursor at a given record. */
UNIV_INLINE
......
......@@ -1416,6 +1416,12 @@ class buf_pool_t
}
public:
/** Look up and buffer-fix a page.
@param id page identifier
@return undo log page, buffer-fixed
@retval nullptr if the undo page was corrupted or freed */
buf_block_t *page_fix(const page_id_t id);
/** @return whether the buffer pool contains a page
@tparam allow_watch whether to allow watch_is_sentinel()
@param page_id page identifier
......
......@@ -31,14 +31,6 @@ Created 10/4/1994 Heikki Tuuri
#ifdef UNIV_DEBUG
/*********************************************************//**
Gets pointer to the page frame where the cursor is positioned.
@return page */
UNIV_INLINE
page_t*
page_cur_get_page(
/*==============*/
page_cur_t* cur); /*!< in: page cursor */
/*********************************************************//**
Gets pointer to the buffer block where the cursor is positioned.
@return page */
UNIV_INLINE
......@@ -60,12 +52,12 @@ page_cur_get_page_zip(
UNIV_INLINE
rec_t *page_cur_get_rec(const page_cur_t *cur);
#else /* UNIV_DEBUG */
# define page_cur_get_page(cur) page_align((cur)->rec)
# define page_cur_get_block(cur) (cur)->block
# define page_cur_get_page_zip(cur) buf_block_get_page_zip((cur)->block)
# define page_cur_get_rec(cur) (cur)->rec
#endif /* UNIV_DEBUG */
# define is_page_cur_get_page_zip(cur) is_buf_block_get_page_zip((cur)->block)
#define page_cur_get_page(cur) page_cur_get_block(cur)->page.frame
#define is_page_cur_get_page_zip(cur) is_buf_block_get_page_zip((cur)->block)
/*********************************************************//**
Sets the cursor object to point before the first user record
on the page. */
......
......@@ -25,18 +25,6 @@ Created 10/4/1994 Heikki Tuuri
*************************************************************************/
#ifdef UNIV_DEBUG
/*********************************************************//**
Gets pointer to the page frame where the cursor is positioned.
@return page */
UNIV_INLINE
page_t*
page_cur_get_page(
/*==============*/
page_cur_t* cur) /*!< in: page cursor */
{
return page_align(page_cur_get_rec(cur));
}
/*********************************************************//**
Gets pointer to the buffer block where the cursor is positioned.
@return page */
......
......@@ -50,26 +50,13 @@ inserts a record that the secondary index entry would refer to.
However, in that case, the user transaction would also re-insert the
secondary index entry after purge has removed it and released the leaf
page latch.
@param[in,out] node row purge node
@param[in] index secondary index
@param[in] entry secondary index entry
@param[in,out] sec_pcur secondary index cursor or NULL
if it is called for purge buffering
operation.
@param[in,out] sec_mtr mini-transaction which holds
secondary index entry or NULL if it is
called for purge buffering operation.
@param[in] is_tree true=pessimistic purge,
false=optimistic (leaf-page only)
@return true if the secondary index record can be purged */
bool
row_purge_poss_sec(
purge_node_t* node,
dict_index_t* index,
const dtuple_t* entry,
btr_pcur_t* sec_pcur=NULL,
mtr_t* sec_mtr=NULL,
bool is_tree=false);
@param node row purge node
@param index secondary index
@param entry secondary index entry
@param mtr mini-transaction for looking up clustered index
@return whether the secondary index record can be purged */
bool row_purge_poss_sec(purge_node_t *node, dict_index_t *index,
const dtuple_t *entry, mtr_t *mtr);
/***************************************************************
Does the purge operation.
......
......@@ -54,32 +54,47 @@ row_vers_impl_x_locked(
dict_index_t* index,
const rec_offs* offsets);
/** Finds out if a version of the record, where the version >= the current
purge_sys.view, should have ientry as its secondary index entry. We check
if there is any not delete marked version of the record where the trx
id >= purge view, and the secondary index entry == ientry; exactly in
this case we return TRUE.
@param[in] also_curr TRUE if also rec is included in the versions
to search; otherwise only versions prior
to it are searched
@param[in] rec record in the clustered index; the caller
must have a latch on the page
@param[in] mtr mtr holding the latch on rec; it will
also hold the latch on purge_view
/** Find out whether data tuple has missing data type
for indexed virtual column.
@param tuple data tuple
@param index virtual index
@return true if tuple has missing column type */
bool dtuple_vcol_data_missing(const dtuple_t &tuple,
const dict_index_t &index);
/** build virtual column value from current cluster index record data
@param[in,out] row the cluster index row in dtuple form
@param[in] clust_index clustered index
@param[in] index the secondary index
@param[in] heap heap used to build virtual dtuple. */
bool
row_vers_build_clust_v_col(
dtuple_t* row,
dict_index_t* clust_index,
dict_index_t* index,
mem_heap_t* heap);
/** Build a dtuple contains virtual column data for current cluster index
@param[in] rec cluster index rec
@param[in] clust_index cluster index
@param[in] clust_offsets cluster rec offset
@param[in] index secondary index
@param[in] ientry secondary index entry
@param[in] trx_id transaction ID on the purging record,
or 0 if called outside purge
@param[in] roll_ptr roll_ptr for the purge record
@param[in] trx_id transaction ID on the purging record
@return TRUE if earlier version should have */
bool
row_vers_old_has_index_entry(
bool also_curr,
@param[in,out] heap heap memory
@param[in,out] v_heap heap memory to keep virtual column tuple
@param[in,out] mtr mini-transaction
@return dtuple contains virtual column data */
dtuple_t*
row_vers_build_cur_vrow(
const rec_t* rec,
mtr_t* mtr,
dict_index_t* clust_index,
rec_offs** clust_offsets,
dict_index_t* index,
const dtuple_t* ientry,
trx_id_t trx_id,
roll_ptr_t roll_ptr,
trx_id_t trx_id);
mem_heap_t* heap,
mem_heap_t* v_heap,
mtr_t* mtr);
/*****************************************************************//**
Constructs the version of a clustered index record which a consistent
......
......@@ -438,10 +438,17 @@ class purge_sys_t
struct view_guard
{
inline view_guard();
enum guard { END_VIEW= -1, PURGE= 0, VIEW= 1};
guard latch;
inline view_guard(guard latch);
inline ~view_guard();
/** Fetch an undo log page.
@param id page identifier
@param mtr mini-transaction
@return reference to buffer page, possibly buffer-fixed in mtr */
inline const buf_block_t *get(const page_id_t id, mtr_t *mtr);
/** @return purge_sys.view */
/** @return purge_sys.view or purge_sys.end_view */
inline const ReadViewBase &view() const;
};
......@@ -470,14 +477,39 @@ class purge_sys_t
/** The global data structure coordinating a purge */
extern purge_sys_t purge_sys;
purge_sys_t::view_guard::view_guard()
{ purge_sys.latch.rd_lock(SRW_LOCK_CALL); }
purge_sys_t::view_guard::view_guard(purge_sys_t::view_guard::guard latch) :
latch(latch)
{
switch (latch) {
case VIEW:
purge_sys.latch.rd_lock(SRW_LOCK_CALL);
break;
case END_VIEW:
purge_sys.end_latch.rd_lock();
break;
case PURGE:
/* the access is within a purge batch; purge_coordinator_task
will wait for all workers to complete before updating the views */
break;
}
}
purge_sys_t::view_guard::~view_guard()
{ purge_sys.latch.rd_unlock(); }
{
switch (latch) {
case VIEW:
purge_sys.latch.rd_unlock();
break;
case END_VIEW:
purge_sys.end_latch.rd_unlock();
break;
case PURGE:
break;
}
}
const ReadViewBase &purge_sys_t::view_guard::view() const
{ return purge_sys.view; }
{ return latch == END_VIEW ? purge_sys.end_view : purge_sys.view; }
purge_sys_t::end_view_guard::end_view_guard()
{ purge_sys.end_latch.rd_lock(); }
......
......@@ -157,50 +157,44 @@ trx_undo_report_row_operation(
/** TRX_UNDO_PREV_IN_PURGE tells trx_undo_prev_version_build() that it
is being called purge view and we would like to get the purge record
even it is in the purge view (in normal case, it will return without
fetching the purge record */
fetching the purge record) */
static constexpr ulint TRX_UNDO_PREV_IN_PURGE = 1;
/** This tells trx_undo_prev_version_build() to fetch the old value in
the undo log (which is the after image for an update) */
static constexpr ulint TRX_UNDO_GET_OLD_V_VALUE = 2;
/** indicate a call from row_vers_old_has_index_entry() */
/** indicate a call from row_undo_mod_sec_is_unsafe() */
static constexpr ulint TRX_UNDO_CHECK_PURGEABILITY = 4;
/** indicate a call from row_purge_is_unsafe() */
static constexpr ulint TRX_UNDO_CHECK_PURGE_PAGES = 8;
/** Build a previous version of a clustered index record. The caller
must hold a latch on the index page of the clustered index record.
@param rec version of a clustered index record
@param index clustered index
@param offsets rec_get_offsets(rec, index)
@param heap memory heap from which the memory needed is
allocated
@param old_vers previous version or NULL if rec is the
first inserted version, or if history data
has been deleted (an error), or if the purge
could have removed the version
though it has not yet done so
@param v_heap memory heap used to create vrow
dtuple if it is not yet created. This heap
diffs from "heap" above in that it could be
prebuilt->old_vers_heap for selection
@param vrow virtual column info, if any
@param v_status status determine if it is going into this
function by purge thread or not.
And if we read "after image" of undo log
@param rec version of a clustered index record
@param index clustered index
@param offsets rec_get_offsets(rec, index)
@param heap memory heap from which the memory needed is allocated
@param old_vers previous version, or NULL if rec is the first inserted
version, or if history data has been deleted (an error),
or if the purge could have removed the version though
it has not yet done so
@param mtr mini-transaction
@param v_status TRX_UNDO_PREV_IN_PURGE, ...
@param v_heap memory heap used to create vrow dtuple if it is not yet
created. This heap diffs from "heap" above in that it could be
prebuilt->old_vers_heap for selection
@param vrow virtual column info, if any
@return error code
@retval DB_SUCCESS if previous version was successfully built,
or if it was an insert or the undo record refers to the table before rebuild
@retval DB_MISSING_HISTORY if the history is missing */
dberr_t
trx_undo_prev_version_build(
const rec_t *rec,
dict_index_t *index,
rec_offs *offsets,
mem_heap_t *heap,
rec_t **old_vers,
mem_heap_t *v_heap,
dtuple_t **vrow,
ulint v_status);
dberr_t trx_undo_prev_version_build(const rec_t *rec, dict_index_t *index,
rec_offs *offsets, mem_heap_t *heap,
rec_t **old_vers, mtr_t *mtr,
ulint v_status,
mem_heap_t *v_heap, dtuple_t **vrow);
/** Read from an undo log record a non-virtual column value.
@param ptr pointer to remaining part of the undo record
......
......@@ -160,7 +160,7 @@ may be pointing to garbage (an undo log record discarded by purge),
but it will never be dereferenced, because the purge view is older
than any active transaction.
For details see: row_vers_old_has_index_entry() and row_purge_poss_sec()
For details see: row_undo_mod_sec_is_unsafe() and row_purge_poss_sec()
*/
......
......@@ -3857,7 +3857,7 @@ UndorecApplier::get_old_rec(const dtuple_t &tuple, dict_index_t *index,
if (is_same(roll_ptr))
return version;
trx_undo_prev_version_build(version, index, *offsets, heap, &prev_version,
nullptr, nullptr, 0);
&mtr, 0, nullptr, nullptr);
version= prev_version;
}
while (version);
......@@ -4026,7 +4026,7 @@ void UndorecApplier::log_update(const dtuple_t &tuple,
copy_rec= rec_copy(mem_heap_alloc(
heap, rec_offs_size(offsets)), match_rec, offsets);
trx_undo_prev_version_build(match_rec, clust_index, offsets, heap,
&prev_version, nullptr, nullptr, 0);
&prev_version, &mtr, 0, nullptr, nullptr);
prev_offsets= rec_get_offsets(prev_version, clust_index, prev_offsets,
clust_index->n_core_fields,
......
This diff is collapsed.
......@@ -6614,7 +6614,7 @@ dberr_t row_check_index(row_prebuilt_t *prebuilt, ulint *n_rows)
err= trx_undo_prev_version_build(clust_rec,
clust_index, clust_offsets,
vers_heap, &old_vers,
nullptr, nullptr, 0);
&mtr, 0, nullptr, nullptr);
if (prev_heap)
mem_heap_free(prev_heap);
if (err != DB_SUCCESS)
......
......@@ -470,6 +470,144 @@ row_undo_mod_clust(
return(err);
}
/** Find out if an accessible version of a clustered index record
corresponds to a secondary index entry.
@param rec record in a latched clustered index page
@param index secondary index
@param ientry secondary index entry
@param mtr mini-transaction
@return whether an accessible non-dete-marked version of rec
corresponds to ientry */
static bool row_undo_mod_sec_is_unsafe(const rec_t *rec, dict_index_t *index,
const dtuple_t *ientry, mtr_t *mtr)
{
const rec_t* version;
rec_t* prev_version;
dict_index_t* clust_index;
rec_offs* clust_offsets;
mem_heap_t* heap;
mem_heap_t* heap2;
dtuple_t* row;
const dtuple_t* entry;
ulint comp;
dtuple_t* vrow = NULL;
mem_heap_t* v_heap = NULL;
dtuple_t* cur_vrow = NULL;
clust_index = dict_table_get_first_index(index->table);
comp = page_rec_is_comp(rec);
ut_ad(!dict_table_is_comp(index->table) == !comp);
heap = mem_heap_create(1024);
clust_offsets = rec_get_offsets(rec, clust_index, NULL,
clust_index->n_core_fields,
ULINT_UNDEFINED, &heap);
if (dict_index_has_virtual(index)) {
v_heap = mem_heap_create(100);
/* The current cluster index record could be
deleted, but the previous version of it might not. We will
need to get the virtual column data from undo record
associated with current cluster index */
cur_vrow = row_vers_build_cur_vrow(
rec, clust_index, &clust_offsets,
index, 0, 0, heap, v_heap, mtr);
}
version = rec;
for (;;) {
heap2 = heap;
heap = mem_heap_create(1024);
vrow = NULL;
trx_undo_prev_version_build(version,
clust_index, clust_offsets,
heap, &prev_version,
mtr, TRX_UNDO_CHECK_PURGEABILITY,
nullptr,
dict_index_has_virtual(index)
? &vrow : nullptr);
mem_heap_free(heap2); /* free version and clust_offsets */
if (!prev_version) {
break;
}
clust_offsets = rec_get_offsets(prev_version, clust_index,
NULL,
clust_index->n_core_fields,
ULINT_UNDEFINED, &heap);
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, it is safe to fetch the prefixes for
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);
}
entry = row_build_index_entry(row, ext, index, heap);
/* If entry == NULL, the record contains unset
BLOB pointers. This must be a freshly
inserted record that we can safely ignore.
For the justification, see the comments after
the previous row_build_index_entry() call. */
/* NOTE that we cannot do the comparison as binary
fields because maybe the secondary index record has
already been updated to a different binary value in
a char field, but the collation identifies the old
and new value anyway! */
if (entry && !dtuple_coll_cmp(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 */
......@@ -488,7 +626,6 @@ row_undo_mod_del_mark_or_remove_sec_low(
btr_cur_t* btr_cur;
dberr_t err = DB_SUCCESS;
mtr_t mtr;
mtr_t mtr_vers;
const bool modify_leaf = mode == BTR_MODIFY_LEAF;
row_mtr_start(&mtr, index, !modify_leaf);
......@@ -555,17 +692,14 @@ row_undo_mod_del_mark_or_remove_sec_low(
which cannot be purged yet, requires its existence. If some requires,
we should delete mark the record. */
mtr_vers.start();
ut_a(node->pcur.restore_position(BTR_SEARCH_LEAF, &mtr_vers) ==
btr_pcur_t::SAME_ALL);
ut_a(node->pcur.restore_position(BTR_SEARCH_LEAF, &mtr) ==
btr_pcur_t::SAME_ALL);
/* 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_vers_old_has_index_entry(
false, btr_pcur_get_rec(&node->pcur),
&mtr_vers, index, entry, 0, 0)) {
|| 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 {
......@@ -599,7 +733,9 @@ row_undo_mod_del_mark_or_remove_sec_low(
}
}
btr_pcur_commit_specify_mtr(&(node->pcur), &mtr_vers);
ut_ad(node->pcur.pos_state == BTR_PCUR_IS_POSITIONED);
node->pcur.pos_state = BTR_PCUR_WAS_POSITIONED;
node->pcur.latch_mode = BTR_NO_LATCHES;
func_exit:
btr_pcur_close(&pcur);
......
......@@ -702,7 +702,7 @@ fetch; output: fetched length of the prefix
@param[in,out] heap heap where to allocate
@return BLOB prefix
@retval NULL if the record is incomplete (should only happen
in row_vers_vc_matches_cluster() executed concurrently with another purge) */
in row_purge_vc_matches_cluster() executed concurrently with another purge) */
static
byte*
row_upd_ext_fetch(
......
This diff is collapsed.
......@@ -776,26 +776,18 @@ TRANSACTIONAL_TARGET void trx_purge_truncate_history()
buf_block_t *purge_sys_t::get_page(page_id_t id)
{
buf_block_t*& undo_page= pages[id];
if (undo_page)
return undo_page;
ut_ad(!recv_sys.recovery_on);
mtr_t mtr;
mtr.start();
undo_page=
buf_page_get_gen(id, 0, RW_S_LATCH, nullptr, BUF_GET_POSSIBLY_FREED, &mtr);
buf_block_t*& undo_page= pages[id];
if (UNIV_LIKELY(undo_page != nullptr))
if (!undo_page)
{
undo_page->fix();
mtr.commit();
return undo_page;
undo_page= buf_pool.page_fix(id); // batch_cleanup() will unfix()
if (!undo_page)
pages.erase(id);
}
mtr.commit();
pages.erase(id);
return nullptr;
return undo_page;
}
bool purge_sys_t::rseg_get_next_history_log()
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment