summaryrefslogtreecommitdiff
path: root/innobase/lock/lock0lock.c
diff options
context:
space:
mode:
Diffstat (limited to 'innobase/lock/lock0lock.c')
-rw-r--r--innobase/lock/lock0lock.c311
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;
}