polardbxengine/mysql-test/suite/innodb/t/mysql_tables_deadlocks.test

136 lines
4.9 KiB
Plaintext

--source include/have_debug_sync.inc
--echo # Bug #28523042
--echo # INNODB: ASSERTION FAILURE: LOCK0LOCK.CC:7034 IN DEADLOCKCHECKER::SEARCH*
# Save the original settings, to be restored at the end of test
SET @innodb_lock_wait_timeout_saved = @@global.innodb_lock_wait_timeout;
# Make sure that transactions will not finish prematurely
SET @@global.innodb_lock_wait_timeout = 100000;
# The original scenario from bug report, simplified a little, to still produce
# assertion failure on old code, but not involve XA and nondeterministic
# ouput.
CREATE TABLE t1(f1 INT KEY,f2 INT);
INSERT INTO t1 VALUES(2,2);
# We need SERIALIZABLE level, as in REPEATABLE READ, SELECT .. FOR SHARE
# does not take any locks on DD tables.
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
# This SELECT places an S,REC_NOT_GAP on a single row of innodb_table_stats
SELECT COUNT(1) FROM mysql.innodb_table_stats
WHERE database_name='test' AND table_name='t1' FOR SHARE;
# This INSERT causes a background update of stats for test.t1,
# which has to wait because we hold S,REC_NOT_GAP on this particular record
INSERT INTO t1 VALUES(1,2);
# This SELECT will attempt to get X,REC_NOT_GAP lock on the same record,
# but has to wait for the background thread due to FCFS nature of queue,
# which causes a deadlock cycle
SELECT count(1) FROM mysql.innodb_table_stats
WHERE database_name='test' AND table_name='t1' FOR UPDATE;
# The background thread will be chosen as a victim, which will cause an ERROR
# to be reported to the error log:
call mtr.add_suppression(
"Cannot save table statistics for table `test`.`t1`: Deadlock"
);
COMMIT;
DROP TABLE t1;
# The test below intends to check how our code reacts to a deadlock in an
# InnoDB table from mysql.* database schema. In particular, we don't want
# any assertion failures.
# Make a list of names of tables to test.
# These are the InnoDB tables visible to the end user in mysql.* database.
# We exclude some table names from the list:
# - ndb_binlog_index is only available on builds with NDB Cluster
CREATE TABLE t1 (
id INT PRIMARY KEY AUTO_INCREMENT,
name TEXT
) ENGINE=InnoDB
SELECT table_name AS name
FROM information_schema.tables
WHERE table_schema="mysql" AND engine="InnoDB"
AND table_name NOT IN (
"ndb_binlog_index"
)
ORDER BY table_name;
--let $i = 1
--let $n = `SELECT COUNT(*) FROM t1`
while($i <= $n)
{
--let $name = `SELECT name FROM t1 WHERE id = $i`
--echo Preparing deadlock scenario for mysql.$name
--let $cnt = `SELECT COUNT(*) FROM mysql.$name`
--disable_query_log
--disable_result_log
# We plan to create a deadlock cycle in a quite simple way:
# 1. c1: locks at least one row in $name FOR SHARE (let's call the row "A")
# 2. c2: locks the row with id=10 in t1 FOR SHARE (let's call the row "B")
# 3. c2: waits for a lock on the row "A" FOR UPDATE
# 4. c1: now tries to lock the row "B" FOR UPDATE
# However, if the table $name is initially empty, we need to first insert row "A",
# to have something to work with.
# This requires us to guess plauisible default values for all columns of "A".
# We exploit that in sql-mode="" we can simply pass 0 for each column.
# We simply count number of columns and prepare a string with that many
# zeros.
# Also, to force the c2 to be chosen as a victim, we ensure that c1 has
# greater weight, by allowing it to lock more rows in t1 in step 4.
# I am aware that these tests are fragile, so if it is costly to maintain them
# please feel free to remove them - they were useful during development, but
# probably not worth spending resources to keep them up-to-date.
--connect (c1, localhost, root,,)
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
if(!$cnt)
{
SET SESSION sql_mode = "";
let $zeros= `
SELECT REPEAT("0,", COUNT(*) - 1)
FROM information_schema.columns
WHERE table_schema = 'mysql'
AND table_name = '$name'
`;
--eval INSERT INTO mysql.$name VALUES ($zeros 0)
}
# 1. c1: locks at least one row in $name FOR SHARE (let's call the row "A")
--eval SELECT COUNT(*) FROM mysql.$name FOR SHARE
--connect (c2, localhost, root,,)
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
# 2. c2: locks the row with id=10 in t1 FOR SHARE (let's call the row "B")
SELECT id FROM t1 WHERE id=10 FOR SHARE;
SET DEBUG_SYNC = 'lock_wait_will_wait SIGNAL c2_will_wait';
# 3. c2: waits for a lock on the row "A" FOR UPDATE
--send_eval SELECT COUNT(*) FROM mysql.$name FOR UPDATE
--connection c1
SET DEBUG_SYNC = 'now WAIT_FOR c2_will_wait';
# 4. c1: now tries to lock the row "B" FOR UPDATE
SELECT COUNT(*) FROM t1 FOR UPDATE;
--enable_query_log
--enable_result_log
ROLLBACK;
--connection c2
--error ER_LOCK_DEADLOCK
--reap
ROLLBACK;
--connection default
--disconnect c1
--disconnect c2
--inc $i
}
DROP TABLE t1;
SET @@global.innodb_lock_wait_timeout = @innodb_lock_wait_timeout_saved;