diff --git a/mysql-test/suite/innodb/r/mdev_37977.result b/mysql-test/suite/innodb/r/mdev_37977.result new file mode 100644 index 0000000000000..4d7a440405635 --- /dev/null +++ b/mysql-test/suite/innodb/r/mdev_37977.result @@ -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; +disconnect con1; +connection default; +FOUND 1 /WE ROLL BACK TRANSACTION \(1\)/ in mysqld.1.err +SET GLOBAL innodb_print_all_deadlocks= @save_print_all_deadlocks; +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/t/mdev_37977.test b/mysql-test/suite/innodb/t/mdev_37977.test new file mode 100644 index 0000000000000..4b4078c18f1f1 --- /dev/null +++ b/mysql-test/suite/innodb/t/mdev_37977.test @@ -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; +--disconnect con1 + +--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 + +SET GLOBAL innodb_print_all_deadlocks= @save_print_all_deadlocks; +DROP TABLE t1; +--source include/wait_until_count_sessions.inc diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc index 572a7591a775d..58723fcd5968a 100644 --- a/storage/innobase/lock/lock0lock.cc +++ b/storage/innobase/lock/lock0lock.cc @@ -6959,7 +6959,7 @@ 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); @@ -6967,22 +6967,30 @@ and less modified rows. Bit 0 is used to prefer orig_trx in case of a tie. 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 != nullptr)) + { + 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; @@ -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 == nullptr) || + (next_weight|next_not_pref) < (victim_weight|victim_not_pref)) { victim_weight= next_weight; victim= next;