Skip to content
/ server Public
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions mysql-test/suite/innodb/r/mdev_37977.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#
# MDEV-37977 InnoDB deadlock report incorrectly reports
# rolled back transaction number
#
SET @save_print_all_deadlocks= @@GLOBAL.innodb_print_all_deadlocks;
SET GLOBAL innodb_print_all_deadlocks= ON;
CREATE TABLE t1 (id INT PRIMARY KEY, val INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (1, 10), (2, 20);
#
# Classic deadlock: con1 locks row 1 then tries row 2;
# default locks row 2 then tries row 1.
# The requesting transaction (default) is always the preferred
# victim due to bit 0 in calc_victim_weight(). In a 2-transaction
# cycle, find_cycle() returns the other transaction, so the
# requesting transaction is displayed as "(1) TRANSACTION".
# The rollback message must say "WE ROLL BACK TRANSACTION (1)".
#
connect con1,localhost,root,,;
BEGIN;
UPDATE t1 SET val= 11 WHERE id= 1;
connection default;
BEGIN;
UPDATE t1 SET val= 22 WHERE id= 2;
connection con1;
UPDATE t1 SET val= 12 WHERE id= 2;
connection default;
UPDATE t1 SET val= 21 WHERE id= 1;
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
ROLLBACK;
connection con1;
ROLLBACK;
connection default;
FOUND 1 /WE ROLL BACK TRANSACTION \(1\)/ in mysqld.1.err
disconnect con1;
SET GLOBAL innodb_print_all_deadlocks= @save_print_all_deadlocks;
DROP TABLE t1;
59 changes: 59 additions & 0 deletions mysql-test/suite/innodb/t/mdev_37977.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
--echo #
--echo # MDEV-37977 InnoDB deadlock report incorrectly reports
--echo # rolled back transaction number
--echo #

--source include/not_embedded.inc
--source include/have_innodb.inc
--source include/count_sessions.inc

SET @save_print_all_deadlocks= @@GLOBAL.innodb_print_all_deadlocks;
SET GLOBAL innodb_print_all_deadlocks= ON;

CREATE TABLE t1 (id INT PRIMARY KEY, val INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (1, 10), (2, 20);

--echo #
--echo # Classic deadlock: con1 locks row 1 then tries row 2;
--echo # default locks row 2 then tries row 1.
--echo # The requesting transaction (default) is always the preferred
--echo # victim due to bit 0 in calc_victim_weight(). In a 2-transaction
--echo # cycle, find_cycle() returns the other transaction, so the
--echo # requesting transaction is displayed as "(1) TRANSACTION".
--echo # The rollback message must say "WE ROLL BACK TRANSACTION (1)".
--echo #

--connect (con1,localhost,root,,)
BEGIN;
UPDATE t1 SET val= 11 WHERE id= 1;

--connection default
BEGIN;
UPDATE t1 SET val= 22 WHERE id= 2;

--connection con1
--send UPDATE t1 SET val= 12 WHERE id= 2

--connection default
let $wait_condition=
SELECT COUNT(*) >= 2 FROM INFORMATION_SCHEMA.INNODB_LOCKS
WHERE lock_table LIKE '%t1%';
--source include/wait_condition.inc

--error ER_LOCK_DEADLOCK
UPDATE t1 SET val= 21 WHERE id= 1;
ROLLBACK;

--connection con1
--reap
ROLLBACK;

--connection default
let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err;
let SEARCH_PATTERN= WE ROLL BACK TRANSACTION \(1\);
--source include/search_pattern_in_file.inc

--disconnect con1
SET GLOBAL innodb_print_all_deadlocks= @save_print_all_deadlocks;
DROP TABLE t1;
--source include/wait_until_count_sessions.inc
Comment on lines +47 to +59
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could move the --disconnect con1 right after the ROLLBACK, to reduce the wait time at the end.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So would you prefer a descriptive name with a standalone test or integration into an existing one?

33 changes: 21 additions & 12 deletions storage/innobase/lock/lock0lock.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6959,30 +6959,38 @@ and less modified rows. Bit 0 is used to prefer orig_trx in case of a tie.
}

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

lock_sys.deadlocks++;
victim= cycle;
undo_no_t victim_weight= calc_victim_weight(victim, trx);
unsigned victim_pos= l;
/* Select the victim among the cycle participants. Traverse
the cycle in the same order as the display loop below
(cycle->wait_trx, ..., cycle as positions 1, 2, ..., N)
so that victim_pos matches the displayed transaction number. */
undo_no_t victim_weight= 0;
unsigned victim_pos= 0;
for (trx_t *next= cycle;;)
{
next= next->lock.wait_trx;
l++;
const undo_no_t next_weight= calc_victim_weight(next, trx);
#ifdef HAVE_REPLICATION
const int pref=
thd_deadlock_victim_preference(victim->mysql_thd, next->mysql_thd);
/* Set bit 63 for any non-preferred victim to make such preference take
priority in the weight comparison.
-1 means victim is preferred. 1 means next is preferred. */
undo_no_t victim_not_pref= (1ULL << 63) & (undo_no_t)(int64_t)(-pref);
undo_no_t next_not_pref= (1ULL << 63) & (undo_no_t)(int64_t)pref;
undo_no_t victim_not_pref= 0;
undo_no_t next_not_pref= 0;
if (UNIV_LIKELY(victim))
{
const int pref=
thd_deadlock_victim_preference(victim->mysql_thd, next->mysql_thd);
/* Set bit 63 for any non-preferred victim to make such preference
take priority in the weight comparison.
-1 means victim is preferred. 1 means next is preferred. */
victim_not_pref= (1ULL << 63) & (undo_no_t)(int64_t)(-pref);
next_not_pref= (1ULL << 63) & (undo_no_t)(int64_t)pref;
}
#else
undo_no_t victim_not_pref= 0;
undo_no_t next_not_pref= 0;
Expand All @@ -6996,7 +7004,8 @@ and less modified rows. Bit 0 is used to prefer orig_trx in case of a tie.
- Else the TRX_WEIGHT in bits 1-61 will decide, if not equal.
- Else, if one of them is the original trx, bit 0 will decide.
- If all is equal, previous victim will arbitrarily be chosen. */
if ((next_weight|next_not_pref) < (victim_weight|victim_not_pref))
if (UNIV_UNLIKELY(!victim) ||
(next_weight|next_not_pref) < (victim_weight|victim_not_pref))
{
victim_weight= next_weight;
victim= next;
Expand Down