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

142 lines
5.1 KiB
Plaintext

--source include/have_debug_sync.inc
--echo #################################################################
--echo # #
--echo # Bug # 27898384: InnoDB: Assertion failure: lock0lock.cc:4226: #
--echo # !(lock2->trx == trx && lock_get_mode(lock2) == lock_mode) #
--echo # #
--echo #################################################################
--echo ##############
--echo # SCENARIO 1 #
--echo ##############
# To reproduce the problem we need a transaction in READ COMMITED mode
# to scan a range of rows, some of which do not match a criteria, which
# causes mysql to ask InnoDB to release a lock previously granted for
# such row. Then lock_rec_unlock is used to remove such lock, and it will
# fail if it is not sure which lock should be removed.
# Such a situation might happen if there are two different locks held for
# the same row, same trx, and same (LOCK_X) mode, but they differ in type.
# For example one is an LOCK_INSERT_INTENTION|LOCK_GAP|LOCK_REC|LOCK_X
# obtained in a previous query, and the another is a
# LOCK_REC_NOT_GAP|LOCK_REC|LOCK_X granted during the current scan.
# The scenario which leads to such situation is:
# 1. con1 attempts to DELETE a non-existing row with id=123.
# 2. con2 starts INSERT with id=123
# but has to wait for con1, thus enqueues LOCK_INSERT_INTENTION
# 3. con1 performs ROLLBACK
# and thus con2 can continue, and has a granted II lock
# 4. con2 performs SELECT ... FOR UPDATE with a query range containing
# id=123, and a condition which does not match the row with id=123.
# Which means it has to unlock the row with id=123.
CREATE TABLE t1 (
id INT PRIMARY KEY,
val INT
) Engine=InnoDB;
INSERT INTO t1 (id, val) VALUES (100,100),(200,200);
--connect (con1, localhost, root,,)
--connect (con2, localhost, root,,)
--connection con1
BEGIN;
DELETE FROM t1 WHERE id=123;
--connection con2
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
SET DEBUG_SYNC = 'lock_wait_will_wait SIGNAL con1_will_wait';
--send INSERT INTO t1 (id, val) VALUES (123,123);
--connection con1
SET DEBUG_SYNC = 'now WAIT_FOR con1_will_wait';
ROLLBACK;
--connection con2
--reap
SELECT * FROM t1 WHERE 100<=id AND id<=200 AND val=100 FOR UPDATE;
ROLLBACK;
# Clean up:
--connection default
--disconnect con1
--disconnect con2
DROP TABLE t1;
SET DEBUG_SYNC = 'RESET';
--echo ##############
--echo # SCENARIO 2 #
--echo ##############
CREATE TABLE t1 (
`a` int(11) NOT NULL auto_increment,
`b` int(11) default NULL,
`c` int default 7,
PRIMARY KEY (`a`),
UNIQUE KEY `b` (`b`)
) ENGINE=innodb;
INSERT INTO t1 (a,b,c) VALUES (1,1,1), (2,2,2), (3,3,3), (4,4,4);
--connect (con1, localhost, root,,)
--connect (con2, localhost, root,,)
--connection con1
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
# next UPDATE will fail because of duplicate key b=2
# also it will create an S-lock on index entry b=2 to preserve this
--error ER_DUP_ENTRY
UPDATE t1 SET b=2 WHERE a=1;
# next SELECT ... FOR SHARE is cleverly constructed to:
# 1. perform a scan over index for column b
# 2. in particular it visits b=2
# 3. it temporarily S-locks visited entries
# 4. it does not need to create an S-lock for b=2, because it already
# has it
# 5. it does however S-lock the row (2,2,2) in primary key
# and sets new_rec_locks=2
# 6. only at the mysql layer we observe that c=1 does not hold,
# so we discard this row
# 7. we see that new_rec_locks=2 so we think we have to unlock both:
# secondary and primary entry
# 8. thus we remove the S-lock in index on column b!
SELECT a FROM t1 WHERE 2<=b AND b<=3 AND c=1 FOR SHARE;
--connection con2
BEGIN;
# this DELETE should block, because transaction from con1 believes that
# there is a row with b=2, which is the reason UPDATE has failed.
# However, as you can see this DELETE will be performed without
# problems, because con1 no longer holds an S-lock on b=2!
SET DEBUG_SYNC = 'lock_wait_will_wait SIGNAL con2_will_wait';
--send DELETE FROM t1 WHERE b=2
--connection con1
# If the code has a bug, then con2 will never enter a wait, and the next
# line will fail with a timeout.
SET DEBUG_SYNC = 'now WAIT_FOR con2_will_wait';
COMMIT;
--connection con2
--reap
COMMIT;
# Clean up:
--connection default
--disconnect con1
--disconnect con2
DROP TABLE t1;
SET DEBUG_SYNC = 'RESET';
--echo ########################
--echo # #
--echo # End of Bug #27898384 #
--echo # #
--echo ########################