summaryrefslogtreecommitdiff
path: root/storage
diff options
context:
space:
mode:
authorMarko Mäkelä <marko.makela@mariadb.com>2023-03-16 16:00:45 +0200
committerMarko Mäkelä <marko.makela@mariadb.com>2023-03-16 16:00:45 +0200
commit4105017a5832c3486002c4ec0c67005ceb5ab88b (patch)
treecdec9ff0fc2d0812e6aed5a1500e5d6722327b41 /storage
parentf2096478d5750b983f9a9cc4691d20e152dafd4a (diff)
downloadmariadb-git-4105017a5832c3486002c4ec0c67005ceb5ab88b.tar.gz
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 b6e41e38720d1e8d33b2abec0d1109615133bc2b 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
Diffstat (limited to 'storage')
-rw-r--r--storage/innobase/include/trx0sys.h11
-rw-r--r--storage/innobase/include/trx0trx.h4
-rw-r--r--storage/innobase/lock/lock0lock.cc68
-rw-r--r--storage/innobase/trx/trx0trx.cc1
4 files changed, 48 insertions, 36 deletions
diff --git a/storage/innobase/include/trx0sys.h b/storage/innobase/include/trx0sys.h
index 4d231077b12..245b981974b 100644
--- a/storage/innobase/include/trx0sys.h
+++ b/storage/innobase/include/trx0sys.h
@@ -924,14 +924,19 @@ public:
/**
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;
}
diff --git a/storage/innobase/include/trx0trx.h b/storage/innobase/include/trx0trx.h
index 21e4516f35a..5b2b2264a46 100644
--- a/storage/innobase/include/trx0trx.h
+++ b/storage/innobase/include/trx0trx.h
@@ -603,6 +603,10 @@ public:
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
diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc
index 2b30b9b1a03..3c7c3d348af 100644
--- a/storage/innobase/lock/lock0lock.cc
+++ b/storage/innobase/lock/lock0lock.cc
@@ -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;
diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc
index a0c781f6287..d7ab02844bf 100644
--- a/storage/innobase/trx/trx0trx.cc
+++ b/storage/innobase/trx/trx0trx.cc
@@ -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