diff options
Diffstat (limited to 'innobase/lock/lock0lock.c')
-rw-r--r-- | innobase/lock/lock0lock.c | 311 |
1 files changed, 247 insertions, 64 deletions
diff --git a/innobase/lock/lock0lock.c b/innobase/lock/lock0lock.c index c180ecb50ce..866fe556af9 100644 --- a/innobase/lock/lock0lock.c +++ b/innobase/lock/lock0lock.c @@ -14,6 +14,8 @@ Created 5/7/1996 Heikki Tuuri #include "usr0sess.h" #include "trx0purge.h" +#include "dict0mem.h" +#include "trx0sys.h" /* Restricts the length of search we will do in the waits-for graph of transactions */ @@ -53,10 +55,9 @@ bump into an x-lock set there. Different transaction can have conflicting locks set on the gap at the same time. The locks on the gap are purely inhibitive: an insert cannot -be made, or a select cursor may have to wait, if a different transaction +be made, or a select cursor may have to wait if a different transaction has a conflicting lock on the gap. An x-lock on the gap does not give -the right to insert into the gap if there are conflicting locks granted -on the gap at the same time. +the right to insert into the gap. An explicit lock can be placed on a user record or the supremum record of a page. The locks on the supremum record are always thought to be of the gap @@ -88,7 +89,7 @@ locks. RULE 4: If a there is a waiting lock request in a queue, no lock request, ------- gap or not, can be inserted ahead of it in the queue. In record deletes -and page splits, new gap type locks can be created by the database manager +and page splits new gap type locks can be created by the database manager for a transaction, and without rule 4, the waits-for graph of transactions might become cyclic without the database noticing it, as the deadlock check is only performed when a transaction itself requests a lock! @@ -96,7 +97,9 @@ is only performed when a transaction itself requests a lock! An insert is allowed to a gap if there are no explicit lock requests by other transactions on the next record. It does not matter if these lock -requests are granted or waiting, gap bit set or not. On the other hand, an +requests are granted or waiting, gap bit set or not, with the exception +that a gap type request set by another transaction to wait for +its turn to do an insert is ignored. On the other hand, an implicit x-lock by another transaction does not prevent an insert, which allows for more concurrency when using an Oracle-style sequence number generator for the primary key with many transactions doing inserts @@ -615,7 +618,7 @@ UNIV_INLINE ulint lock_get_type( /*==========*/ - /* out: LOCK_TABLE or LOCK_RECa */ + /* out: LOCK_TABLE or LOCK_REC */ lock_t* lock) /* in: lock */ { ut_ad(lock); @@ -717,6 +720,46 @@ lock_rec_set_gap( } /************************************************************************* +Gets the waiting insert flag of a record lock. */ +UNIV_INLINE +ibool +lock_rec_get_insert_intention( +/*==========================*/ + /* out: TRUE if gap flag set */ + lock_t* lock) /* in: record lock */ +{ + ut_ad(lock); + ut_ad(lock_get_type(lock) == LOCK_REC); + + if (lock->type_mode & LOCK_INSERT_INTENTION) { + + return(TRUE); + } + + return(FALSE); +} + +/************************************************************************* +Sets the waiting insert flag of a record lock. */ +UNIV_INLINE +void +lock_rec_set_insert_intention( +/*==========================*/ + lock_t* lock, /* in: record lock */ + ibool val) /* in: value to set: TRUE or FALSE */ +{ + ut_ad(lock); + ut_ad((val == TRUE) || (val == FALSE)); + ut_ad(lock_get_type(lock) == LOCK_REC); + + if (val) { + lock->type_mode = lock->type_mode | LOCK_INSERT_INTENTION; + } else { + lock->type_mode = lock->type_mode & ~LOCK_INSERT_INTENTION; + } +} + +/************************************************************************* Calculates if lock mode 1 is stronger or equal to lock mode 2. */ UNIV_INLINE ibool @@ -797,40 +840,93 @@ lock_mode_compatible( } /************************************************************************* -Returns LOCK_X if mode is LOCK_S, and vice versa. */ +Checks if a lock request for a new lock has to wait for request lock2. */ UNIV_INLINE -ulint -lock_get_confl_mode( -/*================*/ - /* out: conflicting basic lock mode */ - ulint mode) /* in: LOCK_S or LOCK_X */ +ibool +lock_rec_has_to_wait( +/*=================*/ + /* out: TRUE if new lock has to wait for lock2 to be + removed */ + trx_t* trx, /* in: trx of new lock */ + ulint mode, /* in: LOCK_S or LOCK_X */ + ulint gap, /* in: LOCK_GAP or 0 */ + ulint insert_intention, + /* in: LOCK_INSERT_INTENTION or 0 */ + lock_t* lock2) /* in: another record lock; NOTE that it is assumed + that this has a lock bit set on the same record as + in lock1 */ { - ut_ad(mode == LOCK_X || mode == LOCK_S); + ut_ad(trx && lock2); + ut_ad(lock_get_type(lock2) == LOCK_REC); + ut_ad(mode == LOCK_S || mode == LOCK_X); + ut_ad(gap == LOCK_GAP || gap == 0); + ut_ad(insert_intention == LOCK_INSERT_INTENTION + || insert_intention == 0); + + if (trx != lock2->trx && !lock_mode_compatible(mode, + lock_get_mode(lock2))) { + + /* We have somewhat complex rules when gap type + record locks cause waits */ - if (mode == LOCK_S) { + if (!gap && lock_rec_get_insert_intention(lock2)) { - return(LOCK_X); + /* Request of a full next-key record does not + need to wait for an insert intention lock to be + removed. This is ok since our rules allow conflicting + locks on gaps. This eliminates a spurious deadlock + caused by a next-key lock waiting for an insert + intention lock; when the insert intention lock was + granted, the insert deadlocked on the waiting + next-key lock. */ + + return(FALSE); + } + + if (insert_intention && lock_rec_get_insert_intention(lock2)) { + + /* An insert intention is not disturbed by another + insert intention; this removes a spurious deadlock + caused by inserts which had to wait for a next-key + lock to be removed */ + + return(FALSE); + } + + return(TRUE); } - return(LOCK_S); + return(FALSE); } /************************************************************************* -Checks if a lock request lock1 has to wait for request lock2. NOTE that we, -for simplicity, ignore the gap bits in locks, and treat gap type lock -requests like non-gap lock requests. */ -UNIV_INLINE +Checks if a lock request lock1 has to wait for request lock2. */ +static ibool lock_has_to_wait( /*=============*/ - /* out: TRUE if lock1 has to wait lock2 to be removed */ - lock_t* lock1, /* in: waiting record lock */ + /* out: TRUE if lock1 has to wait for lock2 to be + removed */ + lock_t* lock1, /* in: waiting lock */ lock_t* lock2) /* in: another lock; NOTE that it is assumed that this - has a lock bit set on the same record as in lock1 */ + has a lock bit set on the same record as in lock1 if + the locks are record locks */ { + ut_ad(lock1 && lock2); + if (lock1->trx != lock2->trx && !lock_mode_compatible(lock_get_mode(lock1), lock_get_mode(lock2))) { + if (lock_get_type(lock1) == LOCK_REC) { + ut_ad(lock_get_type(lock2) == LOCK_REC); + + return(lock_rec_has_to_wait(lock1->trx, + lock_get_mode(lock1), + lock_rec_get_gap(lock1), + lock_rec_get_insert_intention(lock1), + lock2)); + } + return(TRUE); } @@ -979,6 +1075,7 @@ lock_rec_get_next_on_page( ulint page_no; ut_ad(mutex_own(&kernel_mutex)); + ut_ad(lock_get_type(lock) == LOCK_REC); space = lock->un_member.rec_lock.space; page_no = lock->un_member.rec_lock.page_no; @@ -1105,6 +1202,7 @@ lock_rec_get_next( lock_t* lock) /* in: lock */ { ut_ad(mutex_own(&kernel_mutex)); + ut_ad(lock_get_type(lock) == LOCK_REC); for (;;) { lock = lock_rec_get_next_on_page(lock); @@ -1162,6 +1260,8 @@ lock_rec_bitmap_reset( ulint n_bytes; ulint i; + ut_ad(lock_get_type(lock) == LOCK_REC); + /* Reset to zero the bitmap which resides immediately after the lock struct */ @@ -1191,6 +1291,8 @@ lock_rec_copy( lock_t* dupl_lock; ulint size; + ut_ad(lock_get_type(lock) == LOCK_REC); + size = sizeof(lock_t) + lock_rec_get_n_bits(lock) / 8; dupl_lock = mem_heap_alloc(heap, size); @@ -1284,8 +1386,10 @@ lock_table_has( /*============= FUNCTIONS FOR ANALYZING RECORD LOCK QUEUE ================*/ /************************************************************************* -Checks if a transaction has a GRANTED explicit non-gap lock on rec, stronger -or equal to mode. */ +Checks if a transaction has a GRANTED explicit lock on rec, where the gap +flag or the insert intention flag is not set, stronger or equal to mode. +Note that locks on the supremum of a page are a special case here, since +they are always gap type locks, even if the gap flag is not set in them. */ UNIV_INLINE lock_t* lock_rec_has_expl( @@ -1306,8 +1410,9 @@ lock_rec_has_expl( if (lock->trx == trx && lock_mode_stronger_or_eq(lock_get_mode(lock), mode) && !lock_get_wait(lock) - && !(lock_rec_get_gap(lock) - || page_rec_is_supremum(rec))) { + && !lock_rec_get_insert_intention(lock) + && !lock_rec_get_gap(lock)) { + return(lock); } @@ -1318,9 +1423,8 @@ lock_rec_has_expl( } /************************************************************************* -Checks if some other transaction has an explicit lock request stronger or -equal to mode on rec or gap, waiting or granted, in the lock queue. */ -UNIV_INLINE +Checks if some other transaction has a lock request in the queue. */ +static lock_t* lock_rec_other_has_expl_req( /*========================*/ @@ -1331,13 +1435,15 @@ lock_rec_other_has_expl_req( ulint wait, /* in: LOCK_WAIT if also waiting locks are taken into account, or 0 if not */ rec_t* rec, /* in: record to look at */ - trx_t* trx) /* in: transaction, or NULL if requests - by any transaction are wanted */ + trx_t* trx) /* in: transaction, or NULL if requests by all + transactions are taken into account */ { lock_t* lock; ut_ad(mutex_own(&kernel_mutex)); - ut_ad((mode == LOCK_X) || (mode == LOCK_S)); + ut_ad(mode == LOCK_X || mode == LOCK_S); + ut_ad(gap == 0 || gap == LOCK_GAP); + ut_ad(wait == 0 || wait == LOCK_WAIT); lock = lock_rec_get_first(rec); @@ -1358,6 +1464,44 @@ lock_rec_other_has_expl_req( } /************************************************************************* +Checks if some other transaction has a conflicting explicit lock request +in the queue, so that we have to wait. */ +static +lock_t* +lock_rec_other_has_conflicting( +/*===========================*/ + /* out: lock or NULL */ + ulint mode, /* in: lock mode of the lock we are going to reserve */ + ulint gap, /* in: LOCK_GAP if we are going to reserve a gap type + lock, else 0 */ + ulint insert_intention, + /* in: LOCK_INSERT_INTENTION if we are going to + reserve an insert intention lock */ + rec_t* rec, /* in: record to look at */ + trx_t* trx) /* in: our transaction */ +{ + lock_t* lock; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(mode == LOCK_X || mode == LOCK_S); + ut_ad(gap == 0 || gap == LOCK_GAP); + ut_ad(insert_intention == LOCK_INSERT_INTENTION + || insert_intention == 0); + lock = lock_rec_get_first(rec); + + while (lock) { + if (lock_rec_has_to_wait(trx, mode, gap, insert_intention, + lock)) { + return(lock); + } + + lock = lock_rec_get_next(rec, lock); + } + + return(NULL); +} + +/************************************************************************* Looks for a suitable type record lock struct by the same trx on the same page. This can be used to save space when a new record lock should be set on a page: no new struct is needed, if a suitable old is found. */ @@ -1525,7 +1669,10 @@ lock_rec_enqueue_waiting( DB_QUE_THR_SUSPENDED */ ulint type_mode,/* in: lock mode this transaction is requesting: LOCK_S or LOCK_X, ORed with - LOCK_GAP if a gap lock is requested */ + LOCK_GAP if a gap lock is requested, ORed + with LOCK_INSERT_INTENTION if this waiting + lock request is set when performing an + insert of an index record */ rec_t* rec, /* in: record */ dict_index_t* index, /* in: index of record */ que_thr_t* thr) /* in: query thread */ @@ -1612,11 +1759,12 @@ lock_rec_add_to_queue( ut_ad(mutex_own(&kernel_mutex)); ut_ad((type_mode & (LOCK_WAIT | LOCK_GAP)) || ((type_mode & LOCK_MODE_MASK) != LOCK_S) - || !lock_rec_other_has_expl_req(LOCK_X, 0, LOCK_WAIT, rec, trx)); + || !lock_rec_other_has_expl_req(LOCK_X, 0, LOCK_WAIT, + rec, trx)); ut_ad((type_mode & (LOCK_WAIT | LOCK_GAP)) || ((type_mode & LOCK_MODE_MASK) != LOCK_X) - || !lock_rec_other_has_expl_req(LOCK_S, 0, LOCK_WAIT, rec, trx)); - + || !lock_rec_other_has_expl_req(LOCK_S, 0, LOCK_WAIT, + rec, trx)); type_mode = type_mode | LOCK_REC; page = buf_frame_align(rec); @@ -1664,7 +1812,8 @@ This is a fast routine for locking a record in the most common cases: there are no explicit locks on the page, or there is just one lock, owned by this transaction, and of the right type_mode. This is a low-level function which does NOT look at implicit locks! Checks lock compatibility within -explicit locks. */ +explicit locks. This function sets a normal next-key lock, or in the case of +a page supremum record, a gap type lock. */ UNIV_INLINE ibool lock_rec_lock_fast( @@ -1717,7 +1866,8 @@ lock_rec_lock_fast( /************************************************************************* This is the general, and slower, routine for locking a record. This is a low-level function which does NOT look at implicit locks! Checks lock -compatibility within explicit locks. */ +compatibility within explicit locks. This function sets a normal next-key +lock, or in the case of a page supremum record, a gap type lock. */ static ulint lock_rec_lock_slow( @@ -1732,7 +1882,6 @@ lock_rec_lock_slow( dict_index_t* index, /* in: index of record */ que_thr_t* thr) /* in: query thread */ { - ulint confl_mode; trx_t* trx; ulint err; @@ -1740,7 +1889,6 @@ lock_rec_lock_slow( ut_ad((mode == LOCK_X) || (mode == LOCK_S)); trx = thr_get_trx(thr); - confl_mode = lock_get_confl_mode(mode); ut_ad((mode != LOCK_S) || lock_table_has(trx, index->table, LOCK_IS)); @@ -1751,8 +1899,8 @@ lock_rec_lock_slow( nothing */ err = DB_SUCCESS; - } else if (lock_rec_other_has_expl_req(confl_mode, 0, LOCK_WAIT, rec, - trx)) { + } else if (lock_rec_other_has_conflicting(mode, 0, 0, rec, trx)) { + /* If another transaction has a non-gap conflicting request in the queue, as this transaction does not have a lock strong enough already granted on the record, we have to wait. */ @@ -1776,7 +1924,8 @@ lock_rec_lock_slow( Tries to lock the specified record in the mode requested. If not immediately possible, enqueues a waiting lock request. This is a low-level function which does NOT look at implicit locks! Checks lock compatibility within -explicit locks. */ +explicit locks. This function sets a normal next-key lock, or in the case +of a page supremum record, a gap type lock. */ ulint lock_rec_lock( @@ -1813,9 +1962,7 @@ lock_rec_lock( } /************************************************************************* -Checks if a waiting record lock request still has to wait in a queue. -NOTE that we, for simplicity, ignore the gap bits in locks, and treat -gap type lock requests like non-gap lock requests. */ +Checks if a waiting record lock request still has to wait in a queue. */ static ibool lock_rec_has_to_wait_in_queue( @@ -1830,6 +1977,7 @@ lock_rec_has_to_wait_in_queue( ut_ad(mutex_own(&kernel_mutex)); ut_ad(lock_get_wait(wait_lock)); + ut_ad(lock_get_type(wait_lock) == LOCK_REC); space = wait_lock->un_member.rec_lock.space; page_no = wait_lock->un_member.rec_lock.page_no; @@ -1839,8 +1987,8 @@ lock_rec_has_to_wait_in_queue( while (lock != wait_lock) { - if (lock_has_to_wait(wait_lock, lock) - && lock_rec_get_nth_bit(lock, heap_no)) { + if (lock_rec_get_nth_bit(lock, heap_no) + && lock_has_to_wait(wait_lock, lock)) { return(TRUE); } @@ -1896,6 +2044,7 @@ lock_rec_cancel( lock_t* lock) /* in: waiting record lock request */ { ut_ad(mutex_own(&kernel_mutex)); + ut_ad(lock_get_type(lock) == LOCK_REC); /* Reset the bit (there can be only one set bit) in the lock bitmap */ lock_rec_reset_nth_bit(lock, lock_rec_find_set_bit(lock)); @@ -2068,7 +2217,8 @@ lock_rec_inherit_to_gap( lock = lock_rec_get_first(rec); while (lock != NULL) { - lock_rec_add_to_queue((lock->type_mode | LOCK_GAP) & ~LOCK_WAIT, + lock_rec_add_to_queue(((lock->type_mode | LOCK_GAP) + & ~LOCK_WAIT), heir, lock->index, lock->trx); lock = lock_rec_get_next(rec, lock); } @@ -3281,8 +3431,9 @@ lock_release_off_kernel( /*====================*/ trx_t* trx) /* in: transaction */ { - ulint count; - lock_t* lock; + dict_table_t* table; + ulint count; + lock_t* lock; ut_ad(mutex_own(&kernel_mutex)); @@ -3300,6 +3451,19 @@ lock_release_off_kernel( } else { ut_ad(lock_get_type(lock) == LOCK_TABLE); + if (lock_get_mode(lock) != LOCK_IS + && (trx->insert_undo || trx->update_undo)) { + + /* The trx may have modified the table. + We block the use of the MySQL query cache + for all currently active transactions. */ + + table = lock->un_member.tab_lock.table; + + table->query_cache_inv_trx_id = + trx_sys->max_trx_id; + } + lock_table_dequeue(lock); } @@ -3498,6 +3662,10 @@ lock_rec_print( buf += sprintf(buf, " gap type lock"); } + if (lock_rec_get_insert_intention(lock)) { + buf += sprintf(buf, " insert intention"); + } + if (lock_get_wait(lock)) { buf += sprintf(buf, " waiting"); } @@ -3515,6 +3683,15 @@ lock_rec_print( IB__FILE__, __LINE__, &mtr); if (page) { page = buf_page_get_nowait(space, page_no, RW_S_LATCH, &mtr); + + if (!page) { + /* Let us try to get an X-latch. If the current thread + is holding an X-latch on the page, we cannot get an + S-latch. */ + + page = buf_page_get_nowait(space, page_no, RW_X_LATCH, + &mtr); + } } if (page) { @@ -3901,7 +4078,7 @@ lock_rec_queue_validate( impl_trx = lock_clust_rec_some_has_impl(rec, index); if (impl_trx && lock_rec_other_has_expl_req(LOCK_S, 0, - LOCK_WAIT, rec, impl_trx)) { + LOCK_WAIT, rec, impl_trx)) { ut_a(lock_rec_has_expl(LOCK_X, rec, impl_trx)); } @@ -3916,7 +4093,7 @@ lock_rec_queue_validate( impl_trx = lock_sec_rec_some_has_impl_off_kernel(rec, index); if (impl_trx && lock_rec_other_has_expl_req(LOCK_S, 0, - LOCK_WAIT, rec, impl_trx)) { + LOCK_WAIT, rec, impl_trx)) { ut_a(lock_rec_has_expl(LOCK_X, rec, impl_trx)); } @@ -3941,12 +4118,10 @@ lock_rec_queue_validate( if (lock_get_mode(lock) == LOCK_S) { ut_a(!lock_rec_other_has_expl_req(LOCK_X, - 0, 0, rec, - lock->trx)); + 0, 0, rec, lock->trx)); } else { ut_a(!lock_rec_other_has_expl_req(LOCK_S, - 0, 0, rec, - lock->trx)); + 0, 0, rec, lock->trx)); } } else if (lock_get_wait(lock) && !lock_rec_get_gap(lock)) { @@ -4185,12 +4360,20 @@ lock_rec_insert_check_and_lock( *inherit = TRUE; /* If another transaction has an explicit lock request, gap or not, - waiting or granted, on the successor, the insert has to wait */ - - if (lock_rec_other_has_expl_req(LOCK_S, LOCK_GAP, LOCK_WAIT, next_rec, - trx)) { - err = lock_rec_enqueue_waiting(LOCK_X | LOCK_GAP, next_rec, - index, thr); + waiting or granted, on the successor, the insert has to wait. + + An exception is the case where the lock by the another transaction + is a gap type lock which it placed to wait for its turn to insert. We + do not consider that kind of a lock conflicting with our insert. This + eliminates an unnecessary deadlock which resulted when 2 transactions + had to wait for their insert. Both had waiting gap type lock requests + on the successor, which produced an unnecessary deadlock. */ + + if (lock_rec_other_has_conflicting(LOCK_X, LOCK_GAP, + LOCK_INSERT_INTENTION, next_rec, trx)) { + err = lock_rec_enqueue_waiting(LOCK_X | LOCK_GAP + | LOCK_INSERT_INTENTION, + next_rec, index, thr); } else { err = DB_SUCCESS; } |