Skip to content

Commit be81138

Browse files
committed
MDEV-27025 insert-intention lock conflicts with waiting ORDINARY lock
The code was backported from 10.6 bd03c0e commit. See that commit message for details. Apart from the above commit trx_lock_t::wait_trx was also backported from MDEV-24738. trx_lock_t::wait_trx is protected with lock_sys.wait_mutex in 10.6, but that mutex was implemented only in MDEV-24789. As there is no need to backport MDEV-24789 for MDEV-27025, trx_lock_t::wait_trx is protected with the same mutexes as trx_lock_t::wait_lock. This fix should not break innodb-lock-schedule-algorithm=VATS. This algorithm uses an Eldest-Transaction-First (ETF) heuristic, which prefers older transactions over new ones. In this fix we just insert granted lock just before the last granted lock of the same transaction, what does not change transactions execution order. The changes in lock_rec_create_low() should not break Galera Cluster, there is a big "if" branch for WSREP. This branch is necessary to provide the correct transactions execution order, and should not be changed for the current bug fix.
1 parent e44439a commit be81138

File tree

12 files changed

+408
-199
lines changed

12 files changed

+408
-199
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#
2+
# MDEV-27025 insert-intention lock conflicts with waiting ORDINARY lock
3+
#
4+
CREATE TABLE t (a INT PRIMARY KEY, b INT NOT NULL UNIQUE) ENGINE=InnoDB;
5+
connect prevent_purge,localhost,root,,;
6+
start transaction with consistent snapshot;
7+
connection default;
8+
INSERT INTO t VALUES (20,20);
9+
DELETE FROM t WHERE b = 20;
10+
connect con_ins,localhost,root,,;
11+
SET DEBUG_SYNC = 'row_ins_sec_index_entry_dup_locks_created SIGNAL ins_set_locks WAIT_FOR ins_cont';
12+
INSERT INTO t VALUES(10, 20);
13+
connect con_del,localhost,root,,;
14+
SET DEBUG_SYNC = 'now WAIT_FOR ins_set_locks';
15+
SET DEBUG_SYNC = 'lock_wait_suspend_thread_enter SIGNAL del_locked';
16+
DELETE FROM t WHERE b = 20;
17+
connection default;
18+
SET DEBUG_SYNC = 'now WAIT_FOR del_locked';
19+
SET DEBUG_SYNC = 'now SIGNAL ins_cont';
20+
connection con_ins;
21+
disconnect con_ins;
22+
connection con_del;
23+
disconnect con_del;
24+
disconnect prevent_purge;
25+
connection default;
26+
SET DEBUG_SYNC = 'RESET';
27+
DROP TABLE t;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
--source include/have_innodb.inc
2+
--source include/count_sessions.inc
3+
--source include/have_debug.inc
4+
--source include/have_debug_sync.inc
5+
6+
--echo #
7+
--echo # MDEV-27025 insert-intention lock conflicts with waiting ORDINARY lock
8+
--echo #
9+
10+
# The test checks the ability to acquire exclusive record lock if the acquiring
11+
# transaction already holds a shared lock on the record and another transaction
12+
# is waiting for a lock.
13+
14+
CREATE TABLE t (a INT PRIMARY KEY, b INT NOT NULL UNIQUE) ENGINE=InnoDB;
15+
16+
--connect(prevent_purge,localhost,root,,)
17+
start transaction with consistent snapshot;
18+
19+
--connection default
20+
INSERT INTO t VALUES (20,20);
21+
DELETE FROM t WHERE b = 20;
22+
23+
--connect(con_ins,localhost,root,,)
24+
SET DEBUG_SYNC = 'row_ins_sec_index_entry_dup_locks_created SIGNAL ins_set_locks WAIT_FOR ins_cont';
25+
send
26+
INSERT INTO t VALUES(10, 20);
27+
28+
--connect(con_del,localhost,root,,)
29+
SET DEBUG_SYNC = 'now WAIT_FOR ins_set_locks';
30+
SET DEBUG_SYNC = 'lock_wait_suspend_thread_enter SIGNAL del_locked';
31+
###############################################################################
32+
# This DELETE creates waiting ORDINARY X-lock for heap_no 2 as the record is
33+
# delete-marked, this lock conflicts with ORDINARY S-lock set by the the last
34+
# INSERT. After the last INSERT creates insert-intention lock on
35+
# heap_no 2, this lock will conflict with waiting ORDINARY X-lock of this
36+
# DELETE, what causes DEADLOCK error for this DELETE.
37+
###############################################################################
38+
send
39+
DELETE FROM t WHERE b = 20;
40+
41+
--connection default
42+
SET DEBUG_SYNC = 'now WAIT_FOR del_locked';
43+
SET DEBUG_SYNC = 'now SIGNAL ins_cont';
44+
45+
--connection con_ins
46+
--reap
47+
--disconnect con_ins
48+
49+
--connection con_del
50+
# Without the fix, ER_LOCK_DEADLOCK would be reported here.
51+
--reap
52+
--disconnect con_del
53+
54+
--disconnect prevent_purge
55+
56+
--connection default
57+
58+
SET DEBUG_SYNC = 'RESET';
59+
DROP TABLE t;
60+
--source include/wait_until_count_sessions.inc

mysql-test/suite/versioning/r/update.result

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,6 @@ connection default;
283283
update t1 set b = 'foo';
284284
connection con1;
285285
update t1 set a = 'bar';
286-
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
287286
disconnect con1;
288287
connection default;
289288
drop table t1;

mysql-test/suite/versioning/t/update.test

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,9 @@ send update t1 set b = 'foo';
186186
connection con1;
187187
let $wait_condition= select count(*) from information_schema.innodb_lock_waits;
188188
source include/wait_condition.inc;
189-
error ER_LOCK_DEADLOCK;
189+
# There must no be DEADLOCK here as con1 transaction already holds locks, and
190+
# default's transaction lock is waiting, so the locks of the following "UPDATE"
191+
# must not conflict with waiting lock.
190192
update t1 set a = 'bar';
191193
disconnect con1;
192194
connection default;

storage/innobase/include/hash0hash.h

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*****************************************************************************
22
33
Copyright (c) 1997, 2016, Oracle and/or its affiliates. All Rights Reserved.
4-
Copyright (c) 2018, 2020, MariaDB Corporation.
4+
Copyright (c) 2018, 2022, MariaDB Corporation.
55
66
This program is free software; you can redistribute it and/or modify it under
77
the terms of the GNU General Public License as published by the Free Software
@@ -28,8 +28,29 @@ Created 5/20/1997 Heikki Tuuri
2828
#include "ut0rnd.h"
2929

3030
struct hash_table_t;
31-
struct hash_cell_t{
32-
void* node; /*!< hash chain node, NULL if none */
31+
struct hash_cell_t
32+
{
33+
/** singly-linked, nullptr terminated list of hash buckets */
34+
void *node;
35+
36+
/** Insert an element after another.
37+
@tparam T type of the element
38+
@param after the element after which to insert
39+
@param insert the being-inserted element
40+
@param next the next-element pointer in T */
41+
template<typename T>
42+
void insert_after(T &after, T &insert, T *T::*next)
43+
{
44+
#ifdef UNIV_DEBUG
45+
for (const T *c= static_cast<const T*>(node); c; c= c->*next)
46+
if (c == &after)
47+
goto found;
48+
ut_error;
49+
found:
50+
#endif
51+
insert.*next= after.*next;
52+
after.*next= &insert;
53+
}
3354
};
3455
typedef void* hash_node_t;
3556

storage/innobase/include/lock0lock.h

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*****************************************************************************
22
33
Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved.
4-
Copyright (c) 2017, 2021, MariaDB Corporation.
4+
Copyright (c) 2017, 2022, MariaDB Corporation.
55
66
This program is free software; you can redistribute it and/or modify it under
77
the terms of the GNU General Public License as published by the Free Software
@@ -832,26 +832,29 @@ class lock_sys_t
832832
/*********************************************************************//**
833833
Creates a new record lock and inserts it to the lock queue. Does NOT check
834834
for deadlocks or lock compatibility!
835+
@param[in] c_lock conflicting lock
836+
@param[in] thr thread owning trx
837+
@param[in] type_mode lock mode and wait flag, type is ignored and replaced by
838+
LOCK_REC
839+
@param[in] block buffer block containing the record
840+
@param[in] heap_no heap number of the record
841+
@param[in] index index of record
842+
@param[in,out] trx transaction
843+
@param[in] caller_owns_trx_mutex TRUE if caller owns trx mutex
844+
@param[in] insert_before_waiting if true, inserts new B-tree record lock
845+
just after the last non-waiting lock of the current transaction which is
846+
located before the first waiting for the current transaction lock, otherwise
847+
the lock is inserted at the end of the queue
835848
@return created lock */
836849
UNIV_INLINE
837-
lock_t*
838-
lock_rec_create(
839-
/*============*/
850+
lock_t *lock_rec_create(lock_t *c_lock,
840851
#ifdef WITH_WSREP
841-
lock_t* c_lock, /*!< conflicting lock */
842-
que_thr_t* thr, /*!< thread owning trx */
852+
que_thr_t *thr,
843853
#endif
844-
unsigned type_mode,/*!< in: lock mode and wait
845-
flag, type is ignored and
846-
replaced by LOCK_REC */
847-
const buf_block_t* block, /*!< in: buffer block containing
848-
the record */
849-
ulint heap_no,/*!< in: heap number of the record */
850-
dict_index_t* index, /*!< in: index of record */
851-
trx_t* trx, /*!< in,out: transaction */
852-
bool caller_owns_trx_mutex);
853-
/*!< in: true if caller owns
854-
trx mutex */
854+
unsigned type_mode, const buf_block_t *block,
855+
ulint heap_no, dict_index_t *index, trx_t *trx,
856+
bool caller_owns_trx_mutex,
857+
bool insert_before_waiting= false);
855858

856859
/*************************************************************//**
857860
Removes a record lock request, waiting or granted, from the queue. */
@@ -864,6 +867,7 @@ lock_rec_discard(
864867

865868
/** Create a new record lock and inserts it to the lock queue,
866869
without checking for deadlocks or conflicts.
870+
@param[in] c_lock conflicting lock
867871
@param[in] type_mode lock mode and wait flag; type will be replaced
868872
with LOCK_REC
869873
@param[in] page_id index page number
@@ -872,11 +876,15 @@ without checking for deadlocks or conflicts.
872876
@param[in] index the index tree
873877
@param[in,out] trx transaction
874878
@param[in] holds_trx_mutex whether the caller holds trx->mutex
879+
@param[in] insert_before_waiting if true, inserts new B-tree record lock
880+
just after the last non-waiting lock of the current transaction which is
881+
located before the first waiting for the current transaction lock, otherwise
882+
the lock is inserted at the end of the queue
875883
@return created lock */
876884
lock_t*
877885
lock_rec_create_low(
886+
lock_t* c_lock,
878887
#ifdef WITH_WSREP
879-
lock_t* c_lock, /*!< conflicting lock */
880888
que_thr_t* thr, /*!< thread owning trx */
881889
#endif
882890
unsigned type_mode,
@@ -885,7 +893,9 @@ lock_rec_create_low(
885893
ulint heap_no,
886894
dict_index_t* index,
887895
trx_t* trx,
888-
bool holds_trx_mutex);
896+
bool holds_trx_mutex,
897+
bool insert_before_waiting = false);
898+
889899
/** Enqueue a waiting request for a lock which cannot be granted immediately.
890900
Check for deadlocks.
891901
@param[in] type_mode the requested lock mode (LOCK_S or LOCK_X)
@@ -906,9 +916,7 @@ Check for deadlocks.
906916
(or it happened to commit) */
907917
dberr_t
908918
lock_rec_enqueue_waiting(
909-
#ifdef WITH_WSREP
910919
lock_t* c_lock, /*!< conflicting lock */
911-
#endif
912920
unsigned type_mode,
913921
const buf_block_t* block,
914922
ulint heap_no,

storage/innobase/include/lock0lock.ic

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -72,32 +72,35 @@ lock_hash_get(
7272
/*********************************************************************//**
7373
Creates a new record lock and inserts it to the lock queue. Does NOT check
7474
for deadlocks or lock compatibility!
75+
@param[in] c_lock conflicting lock
76+
@param[in] thr thread owning trx
77+
@param[in] type_mode lock mode and wait flag, type is ignored and replaced by
78+
LOCK_REC
79+
@param[in] block buffer block containing the record
80+
@param[in] heap_no heap number of the record
81+
@param[in] index index of record
82+
@param[in,out] trx transaction
83+
@param[in] caller_owns_trx_mutex TRUE if caller owns trx mutex
84+
@param[in] insert_before_waiting if true, inserts new B-tree record lock
85+
just after the last non-waiting lock of the current transaction which is
86+
located before the first waiting for the current transaction lock, otherwise
87+
the lock is inserted at the end of the queue
7588
@return created lock */
7689
UNIV_INLINE
77-
lock_t*
78-
lock_rec_create(
79-
/*============*/
90+
lock_t *lock_rec_create(lock_t *c_lock,
8091
#ifdef WITH_WSREP
81-
lock_t* c_lock, /*!< conflicting lock */
82-
que_thr_t* thr, /*!< thread owning trx */
92+
que_thr_t *thr,
8393
#endif
84-
unsigned type_mode,/*!< in: lock mode and wait
85-
flag, type is ignored and
86-
replaced by LOCK_REC */
87-
const buf_block_t* block, /*!< in: buffer block containing
88-
the record */
89-
ulint heap_no,/*!< in: heap number of the record */
90-
dict_index_t* index, /*!< in: index of record */
91-
trx_t* trx, /*!< in,out: transaction */
92-
bool caller_owns_trx_mutex)
93-
/*!< in: TRUE if caller owns
94-
trx mutex */
94+
unsigned type_mode, const buf_block_t *block,
95+
ulint heap_no, dict_index_t *index, trx_t *trx,
96+
bool caller_owns_trx_mutex,
97+
bool insert_before_waiting)
9598
{
9699
btr_assert_not_corrupted(block, index);
97-
return lock_rec_create_low(
100+
return lock_rec_create_low(c_lock,
98101
#ifdef WITH_WSREP
99-
c_lock, thr,
102+
thr,
100103
#endif
101104
type_mode, block->page.id(), block->frame, heap_no,
102-
index, trx, caller_owns_trx_mutex);
105+
index, trx, caller_owns_trx_mutex, insert_before_waiting);
103106
}

storage/innobase/include/lock0priv.h

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -562,14 +562,13 @@ lock_rec_get_next_const(
562562

563563
/*********************************************************************//**
564564
Gets the first explicit lock request on a record.
565+
@param[in] hash hash chain the lock on
566+
@param[in] page_id page id
567+
@param[in] heap_no heap number of the record
565568
@return first lock, NULL if none exists */
566569
UNIV_INLINE
567-
lock_t*
568-
lock_rec_get_first(
569-
/*===============*/
570-
hash_table_t* hash, /*!< in: hash chain the lock on */
571-
const buf_block_t* block, /*!< in: block containing the record */
572-
ulint heap_no);/*!< in: heap number of the record */
570+
lock_t *lock_rec_get_first(hash_table_t *hash, page_id_t page_id,
571+
ulint heap_no);
573572

574573
/*********************************************************************//**
575574
Gets the mode of a lock.
@@ -623,15 +622,26 @@ lock_table_has(
623622

624623
/** Set the wait status of a lock.
625624
@param[in,out] lock lock that will be waited for
626-
@param[in,out] trx transaction that will wait for the lock */
627-
inline void lock_set_lock_and_trx_wait(lock_t* lock, trx_t* trx)
625+
@param[in,out] trx transaction that will wait for the lock
626+
@param[in] c_lock conflicting lock */
627+
inline void lock_set_lock_and_trx_wait(lock_t* lock, trx_t* trx,
628+
const lock_t *c_lock)
628629
{
629630
ut_ad(lock);
630631
ut_ad(lock->trx == trx);
631-
ut_ad(trx->lock.wait_lock == NULL);
632632
ut_ad(lock_mutex_own());
633633
ut_ad(trx_mutex_own(trx));
634634

635+
if (trx->lock.wait_trx) {
636+
ut_ad(!c_lock || trx->lock.wait_trx == c_lock->trx);
637+
ut_ad(trx->lock.wait_lock);
638+
ut_ad((*trx->lock.wait_lock).trx == trx);
639+
} else {
640+
ut_ad(c_lock);
641+
trx->lock.wait_trx = c_lock->trx;
642+
ut_ad(!trx->lock.wait_lock);
643+
}
644+
635645
trx->lock.wait_lock = lock;
636646
lock->type_mode |= LOCK_WAIT;
637647
}
@@ -644,6 +654,7 @@ inline void lock_reset_lock_and_trx_wait(lock_t* lock)
644654
ut_ad(lock_mutex_own());
645655
ut_ad(lock->trx->lock.wait_lock == NULL
646656
|| lock->trx->lock.wait_lock == lock);
657+
lock->trx->lock.wait_trx= nullptr;
647658
lock->trx->lock.wait_lock = NULL;
648659
lock->type_mode &= ~LOCK_WAIT;
649660
}

storage/innobase/include/lock0priv.ic

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,16 +156,15 @@ lock_rec_get_next_const(
156156

157157
/*********************************************************************//**
158158
Gets the first explicit lock request on a record.
159-
@return first lock, NULL if none exists */
159+
@param[in] hash hash chain the lock on
160+
@param[in] page_id page id
161+
@param[in] heap_no heap number of the record
162+
@return first lock, NULL if none exists */
160163
UNIV_INLINE
161-
lock_t*
162-
lock_rec_get_first(
163-
/*===============*/
164-
hash_table_t* hash, /*!< in: hash chain the lock on */
165-
const buf_block_t* block, /*!< in: block containing the record */
166-
ulint heap_no)/*!< in: heap number of the record */
164+
lock_t *lock_rec_get_first(hash_table_t *hash, page_id_t page_id,
165+
ulint heap_no)
167166
{
168-
for (lock_t *lock= lock_sys.get_first(*hash, block->page.id());
167+
for (lock_t *lock= lock_sys.get_first(*hash, page_id);
169168
lock; lock= lock_rec_get_next_on_page(lock))
170169
if (lock_rec_get_nth_bit(lock, heap_no))
171170
return lock;

storage/innobase/include/trx0trx.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,9 @@ struct trx_lock_t {
426426
trx_que_t que_state; /*!< valid when trx->state
427427
== TRX_STATE_ACTIVE: TRX_QUE_RUNNING,
428428
TRX_QUE_LOCK_WAIT, ... */
429-
429+
/** Transaction being waited for; protected by the same mutexes as
430+
wait_lock */
431+
trx_t* wait_trx;
430432
lock_t* wait_lock; /*!< if trx execution state is
431433
TRX_QUE_LOCK_WAIT, this points to
432434
the lock request, otherwise this is

0 commit comments

Comments
 (0)