summaryrefslogtreecommitdiff
path: root/storage/innobase/lock/lock0lock.cc
diff options
context:
space:
mode:
Diffstat (limited to 'storage/innobase/lock/lock0lock.cc')
-rw-r--r--storage/innobase/lock/lock0lock.cc48
1 files changed, 45 insertions, 3 deletions
diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc
index 9577dfc62aa..2b30b9b1a03 100644
--- a/storage/innobase/lock/lock0lock.cc
+++ b/storage/innobase/lock/lock0lock.cc
@@ -46,12 +46,12 @@ Created 5/7/1996 Heikki Tuuri
#include "srv0mon.h"
#include "que0que.h"
#include "scope.h"
+#include <debug_sync.h>
#include <set>
#ifdef WITH_WSREP
#include <mysql/service_wsrep.h>
-#include <debug_sync.h>
#endif /* WITH_WSREP */
/** The value of innodb_deadlock_detect */
@@ -1882,6 +1882,7 @@ check_trx_error:
if (row_lock_wait)
lock_sys.wait_resume(trx->mysql_thd, suspend_time, my_hrtime_coarse());
+ /* Cache trx->lock.wait_lock to avoid unnecessary atomic variable load */
if (lock_t *lock= trx->lock.wait_lock)
{
lock_sys_t::cancel<false>(trx, lock);
@@ -1905,6 +1906,12 @@ void lock_wait_end(trx_t *trx)
ut_d(const auto state= trx->state);
ut_ad(state == TRX_STATE_COMMITTED_IN_MEMORY || state == TRX_STATE_ACTIVE ||
state == TRX_STATE_PREPARED);
+ /* lock_wait() checks trx->lock.was_chosen_as_deadlock_victim flag before
+ requesting lock_sys.wait_mutex, and if the flag is set, it returns error,
+ what causes transaction rollback, which can reset trx->lock.wait_thr before
+ deadlock resolution starts cancelling victim's waiting lock. That's why we
+ don't check trx->lock.wait_thr here if the function was called from deadlock
+ resolution function. */
ut_ad(from_deadlock || trx->lock.wait_thr);
if (trx->lock.was_chosen_as_deadlock_victim)
@@ -3193,6 +3200,8 @@ lock_rec_store_on_page_infimum(
ut_ad(block->page.frame == page_align(rec));
const page_id_t id{block->page.id()};
+ ut_d(SCOPE_EXIT(
+ []() { DEBUG_SYNC_C("lock_rec_store_on_page_infimum_end"); }));
LockGuard g{lock_sys.rec_hash, id};
lock_rec_move(g.cell(), *block, id, g.cell(), id,
@@ -5770,17 +5779,30 @@ void lock_sys_t::cancel_lock_wait_for_trx(trx_t *trx)
/** Cancel a waiting lock request.
@tparam check_victim whether to check for DB_DEADLOCK
-@param lock waiting lock request
@param trx active transaction
+@param lock waiting lock request
@retval DB_SUCCESS if no lock existed
@retval DB_DEADLOCK if trx->lock.was_chosen_as_deadlock_victim was set
@retval DB_LOCK_WAIT if the lock was canceled */
template<bool check_victim>
dberr_t lock_sys_t::cancel(trx_t *trx, lock_t *lock)
{
+ DEBUG_SYNC_C("lock_sys_t_cancel_enter");
mysql_mutex_assert_owner(&lock_sys.wait_mutex);
- ut_ad(trx->lock.wait_lock == lock);
ut_ad(trx->state == TRX_STATE_ACTIVE);
+ /* trx->lock.wait_lock may be changed by other threads as long as
+ we are not holding lock_sys.latch.
+
+ So, trx->lock.wait_lock==lock does not necessarily hold, but both
+ pointers should be valid, because other threads cannot assign
+ trx->lock.wait_lock=nullptr (or invalidate *lock) while we are
+ holding lock_sys.wait_mutex. Also, the type of trx->lock.wait_lock
+ (record or table lock) cannot be changed by other threads. So, it is
+ safe to call lock->is_table() while not holding lock_sys.latch. If
+ we have to release and reacquire lock_sys.wait_mutex, we must reread
+ trx->lock.wait_lock. We must also reread trx->lock.wait_lock after
+ lock_sys.latch acquiring, as it can be changed to not-null in lock moving
+ functions even if we hold lock_sys.wait_mutex. */
dberr_t err= DB_SUCCESS;
/* This would be too large for a memory transaction, except in the
DB_DEADLOCK case, which was already tested in lock_trx_handle_wait(). */
@@ -5802,6 +5824,15 @@ dberr_t lock_sys_t::cancel(trx_t *trx, lock_t *lock)
}
else
{
+ /* This function is invoked from the thread which executes the
+ transaction. Table locks are requested before record locks. Some other
+ transaction can't change trx->lock.wait_lock from table to record for the
+ current transaction at this point, because the current transaction has not
+ requested record locks yet. There is no need to move any table locks by
+ other threads. And trx->lock.wait_lock can't be set to null while we are
+ holding lock_sys.wait_mutex. That's why there is no need to reload
+ trx->lock.wait_lock here. */
+ ut_ad(lock == trx->lock.wait_lock);
resolve_table_lock:
dict_table_t *table= lock->un_member.tab_lock.table;
if (!table->lock_mutex_trylock())
@@ -5812,6 +5843,7 @@ resolve_table_lock:
mysql_mutex_unlock(&lock_sys.wait_mutex);
table->lock_mutex_lock();
mysql_mutex_lock(&lock_sys.wait_mutex);
+ /* Cache trx->lock.wait_lock under the corresponding latches. */
lock= trx->lock.wait_lock;
if (!lock)
goto retreat;
@@ -5821,6 +5853,10 @@ resolve_table_lock:
goto retreat;
}
}
+ else
+ /* Cache trx->lock.wait_lock under the corresponding latches if
+ it was not cached yet */
+ lock= trx->lock.wait_lock;
if (lock->is_waiting())
lock_cancel_waiting_and_release(lock);
/* Even if lock->is_waiting() did not hold above, we must return
@@ -5844,6 +5880,7 @@ retreat:
mysql_mutex_unlock(&lock_sys.wait_mutex);
lock_sys.wr_lock(SRW_LOCK_CALL);
mysql_mutex_lock(&lock_sys.wait_mutex);
+ /* Cache trx->lock.wait_lock under the corresponding latches. */
lock= trx->lock.wait_lock;
/* Even if waiting lock was cancelled while lock_sys.wait_mutex was
unlocked, we need to return deadlock error if transaction was chosen
@@ -5855,6 +5892,9 @@ retreat:
}
else
{
+ /* Cache trx->lock.wait_lock under the corresponding latches if
+ it was not cached yet */
+ lock= trx->lock.wait_lock;
resolve_record_lock:
if (lock->is_waiting())
lock_cancel_waiting_and_release(lock);
@@ -5876,6 +5916,7 @@ resolve_record_lock:
void lock_sys_t::cancel(trx_t *trx)
{
mysql_mutex_lock(&lock_sys.wait_mutex);
+ /* Cache trx->lock.wait_lock to avoid unnecessary atomic variable load */
if (lock_t *lock= trx->lock.wait_lock)
{
/* Dictionary transactions must be immune to KILL, because they
@@ -5943,6 +5984,7 @@ dberr_t lock_trx_handle_wait(trx_t *trx)
mysql_mutex_lock(&lock_sys.wait_mutex);
if (trx->lock.was_chosen_as_deadlock_victim)
err= DB_DEADLOCK;
+ /* Cache trx->lock.wait_lock to avoid unnecessary atomic variable load */
else if (lock_t *wait_lock= trx->lock.wait_lock)
err= lock_sys_t::cancel<true>(trx, wait_lock);
lock_sys.deadlock_check();