Skip to content

Commit

Permalink
MDEV-30357 Performance regression in locking reads from secondary ind…
Browse files Browse the repository at this point in the history
…exes

lock_sec_rec_some_has_impl(): Remove a harmful condition that caused the
performance regression and should not have been added in
commit b6e41e3 in the first place.
Locking transactions that have not modified any persistent tables
can carry the transaction identifier 0.

trx_t::max_inactive_id: A cache for trx_sys_t::find_same_or_older().
The value is not reset on transaction commit so that previous results
can be reused for subsequent transactions. The smallest active
transaction ID can only increase over time, not decrease.

trx_sys_t::find_same_or_older(): Remember the maximum previous id for which
rw_trx_hash.iterate() returned false, to avoid redundant iterations.

lock_sec_rec_read_check_and_lock(): Add an early return in case we are
already holding a covering table lock.

lock_rec_convert_impl_to_expl(): Add a template parameter to avoid
a redundant run-time check on whether the index is secondary.

lock_rec_convert_impl_to_expl_for_trx(): Move some code from
lock_rec_convert_impl_to_expl(), to reduce code duplication due
to the added template parameter.

Reviewed by: Vladislav Lesin
Tested by: Matthias Leich
  • Loading branch information
dr-m committed Mar 16, 2023
1 parent f209647 commit 4105017
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 36 deletions.
11 changes: 8 additions & 3 deletions storage/innobase/include/trx0sys.h
Original file line number Diff line number Diff line change
Expand Up @@ -924,14 +924,19 @@ class trx_sys_t
/**
Determine if the specified transaction or any older one might be active.
@param caller_trx used to get/set pins
@param trx current transaction
@param id transaction identifier
@return whether any transaction not newer than id might be active
*/

bool find_same_or_older(trx_t *caller_trx, trx_id_t id)
bool find_same_or_older(trx_t *trx, trx_id_t id)
{
return rw_trx_hash.iterate(caller_trx, find_same_or_older_callback, &id);
if (trx->max_inactive_id >= id)
return false;
bool found= rw_trx_hash.iterate(trx, find_same_or_older_callback, &id);
if (!found)
trx->max_inactive_id= id;
return found;
}


Expand Down
4 changes: 4 additions & 0 deletions storage/innobase/include/trx0trx.h
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,10 @@ struct trx_t : ilist_node<>
Cleared in commit_in_memory() after commit_state(),
trx_sys_t::deregister_rw(), release_locks(). */
trx_id_t id;
/** The largest encountered transaction identifier for which no
transaction was observed to be active. This is a cache to speed up
trx_sys_t::find_same_or_older(). */
trx_id_t max_inactive_id;

private:
/** mutex protecting state and some of lock
Expand Down
68 changes: 35 additions & 33 deletions storage/innobase/lock/lock0lock.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1064,13 +1064,16 @@ lock_sec_rec_some_has_impl(

const trx_id_t max_trx_id= page_get_max_trx_id(page_align(rec));

if ((caller_trx->id > max_trx_id &&
!trx_sys.find_same_or_older(caller_trx, max_trx_id)) ||
/* Note: It is possible to have caller_trx->id == 0 in a locking read
if caller_trx has not modified any persistent tables. */
if (!trx_sys.find_same_or_older(caller_trx, max_trx_id) ||
!lock_check_trx_id_sanity(max_trx_id, rec, index, offsets))
return nullptr;

/* In this case it is possible that some transaction has an implicit
x-lock. We have to look in the clustered index. */
/* We checked above that some active (or XA PREPARE) transaction exists
that is older than PAGE_MAX_TRX_ID. That is, some transaction may be
holding an implicit lock on the record. We have to look up the
clustered index record to find if it is (or was) the case. */
return row_vers_impl_x_locked(caller_trx, rec, index, offsets);
}

Expand Down Expand Up @@ -5157,20 +5160,24 @@ has an implicit lock on the record. The transaction instance must have a
reference count > 0 so that it can't be committed and freed before this
function has completed. */
static
void
bool
lock_rec_convert_impl_to_expl_for_trx(
/*==================================*/
trx_t* trx, /*!< in/out: active transaction */
const page_id_t id, /*!< in: page identifier */
const rec_t* rec, /*!< in: user record on page */
dict_index_t* index, /*!< in: index of record */
trx_t* trx, /*!< in/out: active transaction */
ulint heap_no)/*!< in: rec heap number to lock */
dict_index_t* index) /*!< in: index of record */
{
if (!trx)
return false;

ut_ad(trx->is_referenced());
ut_ad(page_rec_is_leaf(rec));
ut_ad(!rec_is_metadata(rec, *index));

DEBUG_SYNC_C("before_lock_rec_convert_impl_to_expl_for_trx");
ulint heap_no= page_rec_get_heap_no(rec);

{
LockGuard g{lock_sys.rec_hash, id};
trx->mutex_lock();
Expand All @@ -5187,6 +5194,7 @@ lock_rec_convert_impl_to_expl_for_trx(
trx->release_reference();

DEBUG_SYNC_C("after_lock_rec_convert_impl_to_expl_for_trx");
return false;
}


Expand Down Expand Up @@ -5260,7 +5268,6 @@ static void lock_rec_other_trx_holds_expl(trx_t *caller_trx, trx_t *trx,
}
#endif /* UNIV_DEBUG */


/** If an implicit x-lock exists on a record, convert it to an explicit one.
Often, this is called by a transaction that is about to enter a lock wait
Expand All @@ -5272,12 +5279,14 @@ This may also be called by the same transaction that is already holding
an implicit exclusive lock on the record. In this case, no explicit lock
should be created.
@tparam is_primary whether the index is the primary key
@param[in,out] caller_trx current transaction
@param[in] id index tree leaf page identifier
@param[in] rec record on the leaf page
@param[in] index the index of the record
@param[in] offsets rec_get_offsets(rec,index)
@return whether caller_trx already holds an exclusive lock on rec */
template<bool is_primary>
static
bool
lock_rec_convert_impl_to_expl(
Expand All @@ -5295,8 +5304,9 @@ lock_rec_convert_impl_to_expl(
ut_ad(!page_rec_is_comp(rec) == !rec_offs_comp(offsets));
ut_ad(page_rec_is_leaf(rec));
ut_ad(!rec_is_metadata(rec, *index));
ut_ad(index->is_primary() == is_primary);

if (dict_index_is_clust(index)) {
if (is_primary) {
trx_id_t trx_id;

trx_id = lock_clust_rec_some_has_impl(rec, index, offsets);
Expand All @@ -5322,20 +5332,7 @@ lock_rec_convert_impl_to_expl(
ut_d(lock_rec_other_trx_holds_expl(caller_trx, trx, rec, id));
}

if (trx) {
ulint heap_no = page_rec_get_heap_no(rec);

ut_ad(trx->is_referenced());

/* If the transaction is still active and has no
explicit x-lock set on the record, set one for it.
trx cannot be committed until the ref count is zero. */

lock_rec_convert_impl_to_expl_for_trx(
id, rec, index, trx, heap_no);
}

return false;
return lock_rec_convert_impl_to_expl_for_trx(trx, id, rec, index);
}

/*********************************************************************//**
Expand Down Expand Up @@ -5374,8 +5371,9 @@ lock_clust_rec_modify_check_and_lock(
/* If a transaction has no explicit x-lock set on the record, set one
for it */

if (lock_rec_convert_impl_to_expl(thr_get_trx(thr), block->page.id(),
rec, index, offsets)) {
if (lock_rec_convert_impl_to_expl<true>(thr_get_trx(thr),
block->page.id(),
rec, index, offsets)) {
/* We already hold an implicit exclusive lock. */
return DB_SUCCESS;
}
Expand Down Expand Up @@ -5532,15 +5530,17 @@ lock_sec_rec_read_check_and_lock(
return(DB_SUCCESS);
}

const page_id_t id{block->page.id()};

ut_ad(!rec_is_metadata(rec, *index));

trx_t *trx = thr_get_trx(thr);

if (lock_table_has(trx, index->table, mode)) {
return DB_SUCCESS;
}

if (!page_rec_is_supremum(rec)
&& !lock_table_has(trx, index->table, LOCK_X)
&& lock_rec_convert_impl_to_expl(thr_get_trx(thr), id, rec,
index, offsets)
&& lock_rec_convert_impl_to_expl<false>(
trx, block->page.id(), rec, index, offsets)
&& gap_mode == LOCK_REC_NOT_GAP) {
/* We already hold an implicit exclusive lock. */
return DB_SUCCESS;
Expand All @@ -5565,7 +5565,8 @@ lock_sec_rec_read_check_and_lock(
if (trx->wsrep == 3) trx->wsrep = 1;
#endif /* WITH_WSREP */

ut_ad(lock_rec_queue_validate(false, id, rec, index, offsets));
ut_ad(lock_rec_queue_validate(false, block->page.id(),
rec, index, offsets));

return(err);
}
Expand Down Expand Up @@ -5622,7 +5623,8 @@ lock_clust_rec_read_check_and_lock(
trx_t *trx = thr_get_trx(thr);
if (!lock_table_has(trx, index->table, LOCK_X)
&& heap_no != PAGE_HEAP_NO_SUPREMUM
&& lock_rec_convert_impl_to_expl(trx, id, rec, index, offsets)
&& lock_rec_convert_impl_to_expl<true>(trx, id,
rec, index, offsets)
&& gap_mode == LOCK_REC_NOT_GAP) {
/* We already hold an implicit exclusive lock. */
return DB_SUCCESS;
Expand Down
1 change: 1 addition & 0 deletions storage/innobase/trx/trx0trx.cc
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ void trx_t::free()
sizeof skip_lock_inheritance_and_n_ref);
/* do not poison mutex */
MEM_NOACCESS(&id, sizeof id);
MEM_NOACCESS(&max_inactive_id, sizeof id);
MEM_NOACCESS(&state, sizeof state);
MEM_NOACCESS(&is_recovered, sizeof is_recovered);
#ifdef WITH_WSREP
Expand Down

0 comments on commit 4105017

Please sign in to comment.