Skip to content

Commit de5ab15

Browse files
arcivanovdr-m
authored andcommitted
MDEV-37977 InnoDB deadlock report incorrectly reports rolled back transaction number
The "WE ROLL BACK TRANSACTION (N)" message in the deadlock report referred to the wrong transaction number. The victim selection loop and the display loop in `Deadlock::report()` traversed the cycle in the same order (`cycle->wait_trx, ..., cycle`) but used misaligned position numbering: - **Victim selection** initialized `victim = cycle` at position 1 *before* the loop, then started iterating from `cycle->wait_trx` at position 2. - **Display loop** started from `cycle->wait_trx` at label `(1)`, with `cycle` displayed last at label `(N)`. This caused `victim_pos` to be off by one relative to the displayed transaction labels. Fix: restructure the victim selection loop to start with `l=0` and `victim=nullptr`, letting the loop handle all transactions uniformly. The first iteration unconditionally picks `cycle->wait_trx` as the initial victim at position 1, matching the display loop. The `thd_deadlock_victim_preference()` call is guarded with a `victim != nullptr` check to skip it on the first iteration (where no prior victim exists to compare against).
1 parent a40bf40 commit de5ab15

3 files changed

Lines changed: 116 additions & 12 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#
2+
# MDEV-37977 InnoDB deadlock report incorrectly reports
3+
# rolled back transaction number
4+
#
5+
SET @save_print_all_deadlocks= @@GLOBAL.innodb_print_all_deadlocks;
6+
SET GLOBAL innodb_print_all_deadlocks= ON;
7+
CREATE TABLE t1 (id INT PRIMARY KEY, val INT) ENGINE=InnoDB;
8+
INSERT INTO t1 VALUES (1, 10), (2, 20);
9+
#
10+
# Classic deadlock: con1 locks row 1 then tries row 2;
11+
# default locks row 2 then tries row 1.
12+
# The requesting transaction (default) is always the preferred
13+
# victim due to bit 0 in calc_victim_weight(). In a 2-transaction
14+
# cycle, find_cycle() returns the other transaction, so the
15+
# requesting transaction is displayed as "(1) TRANSACTION".
16+
# The rollback message must say "WE ROLL BACK TRANSACTION (1)".
17+
#
18+
connect con1,localhost,root,,;
19+
BEGIN;
20+
UPDATE t1 SET val= 11 WHERE id= 1;
21+
connection default;
22+
BEGIN;
23+
UPDATE t1 SET val= 22 WHERE id= 2;
24+
connection con1;
25+
UPDATE t1 SET val= 12 WHERE id= 2;
26+
connection default;
27+
UPDATE t1 SET val= 21 WHERE id= 1;
28+
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
29+
ROLLBACK;
30+
connection con1;
31+
ROLLBACK;
32+
disconnect con1;
33+
connection default;
34+
FOUND 1 /WE ROLL BACK TRANSACTION \(1\)/ in mysqld.1.err
35+
SET GLOBAL innodb_print_all_deadlocks= @save_print_all_deadlocks;
36+
DROP TABLE t1;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
--echo #
2+
--echo # MDEV-37977 InnoDB deadlock report incorrectly reports
3+
--echo # rolled back transaction number
4+
--echo #
5+
6+
--source include/not_embedded.inc
7+
--source include/have_innodb.inc
8+
--source include/count_sessions.inc
9+
10+
SET @save_print_all_deadlocks= @@GLOBAL.innodb_print_all_deadlocks;
11+
SET GLOBAL innodb_print_all_deadlocks= ON;
12+
13+
CREATE TABLE t1 (id INT PRIMARY KEY, val INT) ENGINE=InnoDB;
14+
INSERT INTO t1 VALUES (1, 10), (2, 20);
15+
16+
--echo #
17+
--echo # Classic deadlock: con1 locks row 1 then tries row 2;
18+
--echo # default locks row 2 then tries row 1.
19+
--echo # The requesting transaction (default) is always the preferred
20+
--echo # victim due to bit 0 in calc_victim_weight(). In a 2-transaction
21+
--echo # cycle, find_cycle() returns the other transaction, so the
22+
--echo # requesting transaction is displayed as "(1) TRANSACTION".
23+
--echo # The rollback message must say "WE ROLL BACK TRANSACTION (1)".
24+
--echo #
25+
26+
--connect (con1,localhost,root,,)
27+
BEGIN;
28+
UPDATE t1 SET val= 11 WHERE id= 1;
29+
30+
--connection default
31+
BEGIN;
32+
UPDATE t1 SET val= 22 WHERE id= 2;
33+
34+
--connection con1
35+
--send UPDATE t1 SET val= 12 WHERE id= 2
36+
37+
--connection default
38+
let $wait_condition=
39+
SELECT COUNT(*) >= 2 FROM INFORMATION_SCHEMA.INNODB_LOCKS
40+
WHERE lock_table LIKE '%t1%';
41+
--source include/wait_condition.inc
42+
43+
--error ER_LOCK_DEADLOCK
44+
UPDATE t1 SET val= 21 WHERE id= 1;
45+
ROLLBACK;
46+
47+
--connection con1
48+
--reap
49+
ROLLBACK;
50+
--disconnect con1
51+
52+
--connection default
53+
let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err;
54+
let SEARCH_PATTERN= WE ROLL BACK TRANSACTION \(1\);
55+
--source include/search_pattern_in_file.inc
56+
57+
SET GLOBAL innodb_print_all_deadlocks= @save_print_all_deadlocks;
58+
DROP TABLE t1;
59+
--source include/wait_until_count_sessions.inc

storage/innobase/lock/lock0lock.cc

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7014,30 +7014,38 @@ and less modified rows. Bit 0 is used to prefer orig_trx in case of a tie.
70147014
}
70157015

70167016
{
7017-
unsigned l= 1;
7017+
unsigned l= 0;
70187018
/* Now that we are holding lock_sys.wait_mutex again, check
70197019
whether a cycle still exists. */
70207020
trx_t *cycle= find_cycle(trx);
70217021
if (!cycle)
70227022
goto func_exit; /* One of the transactions was already aborted. */
70237023

70247024
lock_sys.deadlocks++;
7025-
victim= cycle;
7026-
undo_no_t victim_weight= calc_victim_weight(victim, trx);
7027-
unsigned victim_pos= l;
7025+
/* Select the victim among the cycle participants. Traverse
7026+
the cycle in the same order as the display loop below
7027+
(cycle->wait_trx, ..., cycle as positions 1, 2, ..., N)
7028+
so that victim_pos matches the displayed transaction number. */
7029+
undo_no_t victim_weight= 0;
7030+
unsigned victim_pos= 0;
70287031
for (trx_t *next= cycle;;)
70297032
{
70307033
next= next->lock.wait_trx;
70317034
l++;
70327035
const undo_no_t next_weight= calc_victim_weight(next, trx);
70337036
#ifdef HAVE_REPLICATION
7034-
const int pref=
7035-
thd_deadlock_victim_preference(victim->mysql_thd, next->mysql_thd);
7036-
/* Set bit 63 for any non-preferred victim to make such preference take
7037-
priority in the weight comparison.
7038-
-1 means victim is preferred. 1 means next is preferred. */
7039-
undo_no_t victim_not_pref= (1ULL << 63) & (undo_no_t)(int64_t)(-pref);
7040-
undo_no_t next_not_pref= (1ULL << 63) & (undo_no_t)(int64_t)pref;
7037+
undo_no_t victim_not_pref= 0;
7038+
undo_no_t next_not_pref= 0;
7039+
if (UNIV_LIKELY(victim != nullptr))
7040+
{
7041+
const int pref=
7042+
thd_deadlock_victim_preference(victim->mysql_thd, next->mysql_thd);
7043+
/* Set bit 63 for any non-preferred victim to make such preference
7044+
take priority in the weight comparison.
7045+
-1 means victim is preferred. 1 means next is preferred. */
7046+
victim_not_pref= (1ULL << 63) & (undo_no_t)(int64_t)(-pref);
7047+
next_not_pref= (1ULL << 63) & (undo_no_t)(int64_t)pref;
7048+
}
70417049
#else
70427050
undo_no_t victim_not_pref= 0;
70437051
undo_no_t next_not_pref= 0;
@@ -7051,7 +7059,8 @@ and less modified rows. Bit 0 is used to prefer orig_trx in case of a tie.
70517059
- Else the TRX_WEIGHT in bits 1-61 will decide, if not equal.
70527060
- Else, if one of them is the original trx, bit 0 will decide.
70537061
- If all is equal, previous victim will arbitrarily be chosen. */
7054-
if ((next_weight|next_not_pref) < (victim_weight|victim_not_pref))
7062+
if (UNIV_UNLIKELY(victim == nullptr) ||
7063+
(next_weight|next_not_pref) < (victim_weight|victim_not_pref))
70557064
{
70567065
victim_weight= next_weight;
70577066
victim= next;

0 commit comments

Comments
 (0)