|
|
|
@ -76,6 +76,9 @@ bitmap */ |
|
|
|
|
|
|
|
#define LOCK_PAGE_BITMAP_MARGIN 64
|
|
|
|
|
|
|
|
/** Lock scheduling algorithm */ |
|
|
|
ulong innodb_lock_schedule_algorithm = INNODB_LOCK_SCHEDULE_ALGORITHM_FCFS; |
|
|
|
|
|
|
|
/* An explicit record lock affects both the record and the gap before it.
|
|
|
|
An implicit x-lock does not affect the gap, it only locks the index |
|
|
|
record from read or update. |
|
|
|
@ -385,6 +388,9 @@ extern "C" int thd_need_wait_for(const MYSQL_THD thd); |
|
|
|
extern "C" |
|
|
|
int thd_need_ordering_with(const MYSQL_THD thd, const MYSQL_THD other_thd); |
|
|
|
|
|
|
|
extern "C" |
|
|
|
int thd_deadlock_victim_preference(const MYSQL_THD thd1, const MYSQL_THD thd2); |
|
|
|
|
|
|
|
/** Stack to use during DFS search. Currently only a single stack is required
|
|
|
|
because there is no parallel deadlock check. This stack is protected by |
|
|
|
the lock_sys_t::mutex. */ |
|
|
|
@ -1982,6 +1988,84 @@ wsrep_print_wait_locks( |
|
|
|
} |
|
|
|
#endif /* WITH_WSREP */
|
|
|
|
|
|
|
|
/*********************************************************************//**
|
|
|
|
Check if lock1 has higher priority than lock2. |
|
|
|
NULL has lowest priority. |
|
|
|
Respect the preference of the upper server layer to reduce conflict |
|
|
|
during in-order parallel replication. |
|
|
|
If neither of them is wait lock, the first one has higher priority. |
|
|
|
If only one of them is a wait lock, it has lower priority. |
|
|
|
Otherwise, the one with an older transaction has higher priority. |
|
|
|
@returns true if lock1 has higher priority, false otherwise. */ |
|
|
|
bool |
|
|
|
has_higher_priority( |
|
|
|
lock_t *lock1, |
|
|
|
lock_t *lock2) |
|
|
|
{ |
|
|
|
if (lock1 == NULL) { |
|
|
|
return false; |
|
|
|
} else if (lock2 == NULL) { |
|
|
|
return true; |
|
|
|
} |
|
|
|
// Ask the upper server layer if any of the two trx should be prefered.
|
|
|
|
int preference = thd_deadlock_victim_preference(lock1->trx->mysql_thd, lock2->trx->mysql_thd); |
|
|
|
if (preference == -1) { |
|
|
|
// lock1 is preferred as a victim, so lock2 has higher priority
|
|
|
|
return false; |
|
|
|
} else if (preference == 1) { |
|
|
|
// lock2 is preferred as a victim, so lock1 has higher priority
|
|
|
|
return true; |
|
|
|
} |
|
|
|
// No preference. Compre them by wait mode and trx age.
|
|
|
|
if (!lock_get_wait(lock1)) { |
|
|
|
return true; |
|
|
|
} else if (!lock_get_wait(lock2)) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
return lock1->trx->start_time < lock2->trx->start_time; |
|
|
|
} |
|
|
|
|
|
|
|
/*********************************************************************//**
|
|
|
|
Insert a lock to the hash list according to the mode (whether it is a wait lock) |
|
|
|
and the age of the transaction the it is associated with. |
|
|
|
If the lock is not a wait lock, insert it to the head of the hash list. |
|
|
|
Otherwise, insert it to the middle of the wait locks according to the age of the |
|
|
|
transaciton. |
|
|
|
*/ |
|
|
|
static |
|
|
|
void |
|
|
|
lock_rec_insert_by_trx_age( |
|
|
|
lock_t *in_lock, /*!< in: lock to be insert */ |
|
|
|
bool wait) /*!< in: whether it's a wait lock */ |
|
|
|
{ |
|
|
|
ulint space; |
|
|
|
ulint page_no; |
|
|
|
ulint rec_fold; |
|
|
|
hash_cell_t* cell; |
|
|
|
lock_t* node; |
|
|
|
lock_t* next; |
|
|
|
|
|
|
|
space = in_lock->un_member.rec_lock.space; |
|
|
|
page_no = in_lock->un_member.rec_lock.page_no; |
|
|
|
rec_fold = lock_rec_fold(space, page_no); |
|
|
|
cell = hash_get_nth_cell(lock_sys->rec_hash, |
|
|
|
hash_calc_hash(rec_fold, lock_sys->rec_hash)); |
|
|
|
|
|
|
|
node = (lock_t *) cell->node; |
|
|
|
// If in_lock is not a wait lock, we insert it to the head of the list.
|
|
|
|
if (node == NULL || !wait || has_higher_priority(in_lock, node)) { |
|
|
|
cell->node = in_lock; |
|
|
|
in_lock->hash = node; |
|
|
|
return; |
|
|
|
} |
|
|
|
while (node != NULL && has_higher_priority((lock_t *) node->hash, in_lock)) { |
|
|
|
node = (lock_t *) node->hash; |
|
|
|
} |
|
|
|
next = (lock_t *) node->hash; |
|
|
|
node->hash = in_lock; |
|
|
|
in_lock->hash = next; |
|
|
|
} |
|
|
|
|
|
|
|
/*********************************************************************//**
|
|
|
|
Creates a new record lock and inserts it to the lock queue. Does NOT check |
|
|
|
for deadlocks or lock compatibility! |
|
|
|
@ -2146,11 +2230,11 @@ lock_rec_create( |
|
|
|
trx_mutex_exit(c_lock->trx); |
|
|
|
} else { |
|
|
|
HASH_INSERT(lock_t, hash, lock_sys->rec_hash, |
|
|
|
lock_rec_fold(space, page_no), lock); |
|
|
|
lock_rec_fold(space, page_no), lock); |
|
|
|
} |
|
|
|
#else
|
|
|
|
HASH_INSERT(lock_t, hash, lock_sys->rec_hash, |
|
|
|
lock_rec_fold(space, page_no), lock); |
|
|
|
lock_rec_fold(space, page_no), lock); |
|
|
|
#endif /* WITH_WSREP */
|
|
|
|
|
|
|
|
if (!caller_owns_trx_mutex) { |
|
|
|
@ -2281,6 +2365,13 @@ lock_rec_enqueue_waiting( |
|
|
|
return(DB_SUCCESS_LOCKED_REC); |
|
|
|
} |
|
|
|
|
|
|
|
// Move it only when it does not cause a deadlock.
|
|
|
|
if (innodb_lock_schedule_algorithm == INNODB_LOCK_SCHEDULE_ALGORITHM_VATS) { |
|
|
|
HASH_DELETE(lock_t, hash, lock_sys->rec_hash, |
|
|
|
lock_rec_fold(buf_block_get_space(block), buf_block_get_page_no(block)), lock); |
|
|
|
lock_rec_insert_by_trx_age(lock, true); |
|
|
|
} |
|
|
|
|
|
|
|
trx->lock.que_state = TRX_QUE_LOCK_WAIT; |
|
|
|
|
|
|
|
trx->lock.was_chosen_as_deadlock_victim = FALSE; |
|
|
|
@ -2822,6 +2913,27 @@ lock_rec_cancel( |
|
|
|
trx_mutex_exit(lock->trx); |
|
|
|
} |
|
|
|
|
|
|
|
/*************************************************************//**
|
|
|
|
Move the lock to the head of the hash list. */ |
|
|
|
static |
|
|
|
void |
|
|
|
lock_rec_move_to_front( |
|
|
|
lock_t *lock_to_move, /*!< in: lock to be moved */ |
|
|
|
ulint rec_fold) /*!< in: rec fold of the lock */ |
|
|
|
{ |
|
|
|
if (lock_to_move != NULL) |
|
|
|
{ |
|
|
|
// Move the target lock to the head of the list
|
|
|
|
hash_cell_t* cell = hash_get_nth_cell(lock_sys->rec_hash, |
|
|
|
hash_calc_hash(rec_fold, lock_sys->rec_hash)); |
|
|
|
if (lock_to_move != cell->node) { |
|
|
|
lock_t *next = (lock_t *) cell->node; |
|
|
|
cell->node = lock_to_move; |
|
|
|
lock_to_move->hash = next; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/*************************************************************//**
|
|
|
|
Removes a record lock request, waiting or granted, from the queue and |
|
|
|
grants locks to other transactions in the queue if they now are entitled |
|
|
|
@ -2839,7 +2951,9 @@ lock_rec_dequeue_from_page( |
|
|
|
{ |
|
|
|
ulint space; |
|
|
|
ulint page_no; |
|
|
|
ulint rec_fold; |
|
|
|
lock_t* lock; |
|
|
|
lock_t* previous = NULL; |
|
|
|
trx_lock_t* trx_lock; |
|
|
|
|
|
|
|
ut_ad(lock_mutex_own()); |
|
|
|
@ -2850,6 +2964,7 @@ lock_rec_dequeue_from_page( |
|
|
|
|
|
|
|
space = in_lock->un_member.rec_lock.space; |
|
|
|
page_no = in_lock->un_member.rec_lock.page_no; |
|
|
|
rec_fold = lock_rec_fold(space, page_no); |
|
|
|
|
|
|
|
in_lock->index->table->n_rec_locks--; |
|
|
|
|
|
|
|
@ -2861,20 +2976,51 @@ lock_rec_dequeue_from_page( |
|
|
|
MONITOR_INC(MONITOR_RECLOCK_REMOVED); |
|
|
|
MONITOR_DEC(MONITOR_NUM_RECLOCK); |
|
|
|
|
|
|
|
/* Check if waiting locks in the queue can now be granted: grant
|
|
|
|
locks if there are no conflicting locks ahead. Stop at the first |
|
|
|
X lock that is waiting or has been granted. */ |
|
|
|
if (innodb_lock_schedule_algorithm == INNODB_LOCK_SCHEDULE_ALGORITHM_FCFS) { |
|
|
|
/* Check if waiting locks in the queue can now be granted: grant
|
|
|
|
locks if there are no conflicting locks ahead. Stop at the first |
|
|
|
X lock that is waiting or has been granted. */ |
|
|
|
|
|
|
|
for (lock = lock_rec_get_first_on_page_addr(space, page_no); |
|
|
|
lock != NULL; |
|
|
|
lock = lock_rec_get_next_on_page(lock)) { |
|
|
|
for (lock = lock_rec_get_first_on_page_addr(space, page_no); |
|
|
|
lock != NULL; |
|
|
|
lock = lock_rec_get_next_on_page(lock)) { |
|
|
|
|
|
|
|
if (lock_get_wait(lock) |
|
|
|
&& !lock_rec_has_to_wait_in_queue(lock)) { |
|
|
|
if (lock_get_wait(lock) |
|
|
|
&& !lock_rec_has_to_wait_in_queue(lock)) { |
|
|
|
|
|
|
|
/* Grant the lock */ |
|
|
|
ut_ad(lock->trx != in_lock->trx); |
|
|
|
lock_grant(lock); |
|
|
|
/* Grant the lock */ |
|
|
|
ut_ad(lock->trx != in_lock->trx); |
|
|
|
lock_grant(lock); |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
/* Grant locks if there are no conflicting locks ahead.
|
|
|
|
Move granted locks to the head of the list. */ |
|
|
|
for (lock = lock_rec_get_first_on_page_addr(space, page_no); |
|
|
|
lock != NULL;) { |
|
|
|
|
|
|
|
/* If the lock is a wait lock on this page, and it does not need to wait. */ |
|
|
|
if ((lock->un_member.rec_lock.space == space) |
|
|
|
&& (lock->un_member.rec_lock.page_no == page_no) |
|
|
|
&& lock_get_wait(lock) |
|
|
|
&& !lock_rec_has_to_wait_in_queue(lock)) { |
|
|
|
|
|
|
|
lock_grant(lock); |
|
|
|
|
|
|
|
if (previous != NULL) { |
|
|
|
/* Move the lock to the head of the list. */ |
|
|
|
HASH_GET_NEXT(hash, previous) = HASH_GET_NEXT(hash, lock); |
|
|
|
lock_rec_move_to_front(lock, rec_fold); |
|
|
|
} else { |
|
|
|
/* Already at the head of the list. */ |
|
|
|
previous = lock; |
|
|
|
} |
|
|
|
/* Move on to the next lock. */ |
|
|
|
lock = static_cast<lock_t *>(HASH_GET_NEXT(hash, previous)); |
|
|
|
} else { |
|
|
|
previous = lock; |
|
|
|
lock = static_cast<lock_t *>(HASH_GET_NEXT(hash, lock)); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -4080,7 +4226,8 @@ lock_get_first_lock( |
|
|
|
} |
|
|
|
|
|
|
|
ut_a(lock != NULL); |
|
|
|
ut_a(lock != ctx->wait_lock); |
|
|
|
ut_a(lock != ctx->wait_lock || |
|
|
|
innodb_lock_schedule_algorithm == INNODB_LOCK_SCHEDULE_ALGORITHM_VATS); |
|
|
|
ut_ad(lock_get_type_low(lock) == lock_get_type_low(ctx->wait_lock)); |
|
|
|
|
|
|
|
return(lock); |
|
|
|
@ -6287,8 +6434,10 @@ lock_rec_queue_validate( |
|
|
|
mode, 0, 0, block, heap_no, lock->trx)); |
|
|
|
#endif /* WITH_WSREP */
|
|
|
|
|
|
|
|
} else if (lock_get_wait(lock) && !lock_rec_get_gap(lock)) { |
|
|
|
|
|
|
|
} else if (lock_get_wait(lock) && !lock_rec_get_gap(lock) |
|
|
|
&& innodb_lock_schedule_algorithm == INNODB_LOCK_SCHEDULE_ALGORITHM_FCFS) { |
|
|
|
// If using VATS, it's possible that a wait lock is inserted to a place in the list
|
|
|
|
// such that it does not need to wait.
|
|
|
|
ut_a(lock_rec_has_to_wait_in_queue(lock)); |
|
|
|
} |
|
|
|
} |
|
|
|
|