Commit 4105017a authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-30357 Performance regression in locking reads from secondary indexes

lock_sec_rec_some_has_impl(): Remove a harmful condition that caused the
performance regression and should not have been added in
commit b6e41e38 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
parent f2096478
......@@ -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;
}
......
......@@ -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
......
......@@ -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);
}
......@@ -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();
......@@ -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;
}
......@@ -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
......@@ -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(
......@@ -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);
......@@ -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);
}
/*********************************************************************//**
......@@ -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;
}
......@@ -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;
......@@ -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);
}
......@@ -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;
......
......@@ -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
......
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