Fixed bug #31310.

Locked rows of the InnoDB storage was silently skipped in the read-committed
isolation level.

QUICK_RANGE_SELECT for unique ranges lacks second (blocking) read
of the record that was read semi-consistently and just skip it.

The handler::read_multi_range_next method has been modified
to retry previous unique range if the previous read was
semi-consistent.
parent 5df890e5
...@@ -29,6 +29,7 @@ eval SET SESSION STORAGE_ENGINE = $engine_type; ...@@ -29,6 +29,7 @@ eval SET SESSION STORAGE_ENGINE = $engine_type;
--disable_warnings --disable_warnings
drop table if exists t1,t2,t3,t1m,t1i,t2m,t2i,t4; drop table if exists t1,t2,t3,t1m,t1i,t2m,t2i,t4;
drop procedure if exists p1;
--enable_warnings --enable_warnings
...@@ -1146,4 +1147,128 @@ select @b:=f2 from t1; ...@@ -1146,4 +1147,128 @@ select @b:=f2 from t1;
select if(@a=@b,"ok","wrong"); select if(@a=@b,"ok","wrong");
drop table t1; drop table t1;
#
# Bug #31310: Locked rows silently skipped in read-committed isolation level.
#
connect (con1,localhost,root,,);
connect (con2,localhost,root,,);
SET SESSION AUTOCOMMIT = 0;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
--echo # Switch to connection con1
connection con1;
eval
CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(256))
ENGINE = $engine_type;
INSERT INTO t1 VALUES (1,2);
--#echo 1. test for locking:
BEGIN;
--enable_info
UPDATE t1 SET b = 12 WHERE a = 1;
--disable_info
SELECT * FROM t1;
--echo # Switch to connection con2
connection con2;
--enable_info
--disable_abort_on_error
--error ER_LOCK_WAIT_TIMEOUT
UPDATE t1 SET b = 21 WHERE a = 1;
--disable_info
--echo # Switch to connection con1
connection con1;
SELECT * FROM t1;
ROLLBACK;
--echo # 2. test for serialized update:
CREATE TABLE t2 (a INT);
TRUNCATE t1;
INSERT INTO t1 VALUES (1,'init');
DELIMITER |;
CREATE PROCEDURE p1()
BEGIN
UPDATE t1 SET b = CONCAT(b, '+con2') WHERE a = 1;
INSERT INTO t2 VALUES ();
END|
DELIMITER ;|
BEGIN;
--enable_info
UPDATE t1 SET b = CONCAT(b, '+con1') WHERE a = 1;
--disable_info
SELECT * FROM t1;
--echo # Switch to connection con2
connection con2;
--send CALL p1;
--echo # Switch to connection con1
connection con1;
SELECT * FROM t1;
COMMIT;
let $bug31310 = 1;
while ($bug31310)
{
let $bug31310= `SELECT 1 - COUNT(*) FROM t2`;
}
SELECT * FROM t1;
--echo # Switch to connection con2
connection con2;
SELECT * FROM t1;
--echo # Switch to connection con1
connection con1;
--echo # 3. test for updated key column:
TRUNCATE t1;
TRUNCATE t2;
INSERT INTO t1 VALUES (1,'init');
BEGIN;
--enable_info
UPDATE t1 SET a = 2, b = CONCAT(b, '+con1') WHERE a = 1;
--disable_info
SELECT * FROM t1;
--echo # Switch to connection con2
connection con2;
--send CALL p1;
--echo # Switch to connection con1
connection con1;
SELECT * FROM t1;
COMMIT;
let $bug31310 = 1;
while ($bug31310)
{
let $bug31310= `SELECT 1 - COUNT(*) FROM t2`;
}
SELECT * FROM t1;
--echo # Switch to connection con2
connection con2;
SELECT * FROM t1;
connection default;
disconnect con1;
disconnect con2;
DROP PROCEDURE p1;
DROP TABLE t1, t2;
--echo End of 5.1 tests --echo End of 5.1 tests
SET SESSION STORAGE_ENGINE = InnoDB; SET SESSION STORAGE_ENGINE = InnoDB;
drop table if exists t1,t2,t3,t1m,t1i,t2m,t2i,t4; drop table if exists t1,t2,t3,t1m,t1i,t2m,t2i,t4;
drop procedure if exists p1;
create table t1 ( create table t1 (
c_id int(11) not null default '0', c_id int(11) not null default '0',
org_id int(11) default null, org_id int(11) default null,
...@@ -1419,4 +1420,83 @@ select if(@a=@b,"ok","wrong"); ...@@ -1419,4 +1420,83 @@ select if(@a=@b,"ok","wrong");
if(@a=@b,"ok","wrong") if(@a=@b,"ok","wrong")
ok ok
drop table t1; drop table t1;
SET SESSION AUTOCOMMIT = 0;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
# Switch to connection con1
CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(256))
ENGINE = InnoDB;
INSERT INTO t1 VALUES (1,2);
BEGIN;
UPDATE t1 SET b = 12 WHERE a = 1;
affected rows: 1
info: Rows matched: 1 Changed: 1 Warnings: 0
SELECT * FROM t1;
a b
1 12
# Switch to connection con2
UPDATE t1 SET b = 21 WHERE a = 1;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
# Switch to connection con1
SELECT * FROM t1;
a b
1 12
ROLLBACK;
# 2. test for serialized update:
CREATE TABLE t2 (a INT);
TRUNCATE t1;
INSERT INTO t1 VALUES (1,'init');
CREATE PROCEDURE p1()
BEGIN
UPDATE t1 SET b = CONCAT(b, '+con2') WHERE a = 1;
INSERT INTO t2 VALUES ();
END|
BEGIN;
UPDATE t1 SET b = CONCAT(b, '+con1') WHERE a = 1;
affected rows: 1
info: Rows matched: 1 Changed: 1 Warnings: 0
SELECT * FROM t1;
a b
1 init+con1
# Switch to connection con2
CALL p1;;
# Switch to connection con1
SELECT * FROM t1;
a b
1 init+con1
COMMIT;
SELECT * FROM t1;
a b
1 init+con1
# Switch to connection con2
SELECT * FROM t1;
a b
1 init+con1+con2
# Switch to connection con1
# 3. test for updated key column:
TRUNCATE t1;
TRUNCATE t2;
INSERT INTO t1 VALUES (1,'init');
BEGIN;
UPDATE t1 SET a = 2, b = CONCAT(b, '+con1') WHERE a = 1;
affected rows: 1
info: Rows matched: 1 Changed: 1 Warnings: 0
SELECT * FROM t1;
a b
2 init+con1
# Switch to connection con2
CALL p1;;
# Switch to connection con1
SELECT * FROM t1;
a b
2 init+con1
COMMIT;
SELECT * FROM t1;
a b
2 init+con1
# Switch to connection con2
SELECT * FROM t1;
a b
2 init+con1
DROP PROCEDURE p1;
DROP TABLE t1, t2;
End of 5.1 tests End of 5.1 tests
...@@ -3145,6 +3145,8 @@ int handler::read_multi_range_next(KEY_MULTI_RANGE **found_range_p) ...@@ -3145,6 +3145,8 @@ int handler::read_multi_range_next(KEY_MULTI_RANGE **found_range_p)
} }
else else
{ {
if (was_semi_consistent_read())
goto scan_it_again;
/* /*
We need to set this for the last range only, but checking this We need to set this for the last range only, but checking this
condition is more expensive than just setting the result code. condition is more expensive than just setting the result code.
...@@ -3152,10 +3154,10 @@ int handler::read_multi_range_next(KEY_MULTI_RANGE **found_range_p) ...@@ -3152,10 +3154,10 @@ int handler::read_multi_range_next(KEY_MULTI_RANGE **found_range_p)
result= HA_ERR_END_OF_FILE; result= HA_ERR_END_OF_FILE;
} }
multi_range_curr++;
scan_it_again:
/* Try the next range(s) until one matches a record. */ /* Try the next range(s) until one matches a record. */
for (multi_range_curr++; for (; multi_range_curr < multi_range_end; multi_range_curr++)
multi_range_curr < multi_range_end;
multi_range_curr++)
{ {
result= read_range_first(multi_range_curr->start_key.keypart_map ? result= read_range_first(multi_range_curr->start_key.keypart_map ?
&multi_range_curr->start_key : 0, &multi_range_curr->start_key : 0,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment