Details
-
Bug
-
Status: Open (View Workflow)
-
Major
-
Resolution: Unresolved
-
12.2.2
-
None
-
None
-
OS: Ubuntu 20.04
DBMS: MariaDB 12.2.2
Storage engine: InnoDB
innodb_snapshot_isolation=ON/OFF
Isolation levels: READ COMMITTED, READ UNCOMMITTED
Description
Description
I found a possible InnoDB locking-read issue in MariaDB 12.2.2.
The issue can be reproduced with `innodb_snapshot_isolation=ON` and also with `innodb_snapshot_isolation=OFF`.
It is reproducible under both `READ COMMITTED` and `READ UNCOMMITTED`.
In the following test case, the table has the following indexes after initialization:
PRIMARY(c0)
|
c1(c1)
|
idx_c0(c0)
|
The statement at [2-2] is:
SELECT DISTINCTROW t0.c0 FROM t0 WHERE TRUE LOCK IN SHARE MODE; |
When this statement uses the default index path, it waits because Transaction 1 has not committed yet. After Transaction 1 continues inserting rows and commits, [2-2] resumes execution.
However, after the lock wait is released, [2-2] returns only 6 rows. It misses the three rows inserted by [1-3]:
1.25715
|
1.32932
|
1.38422
|
At the same time, the result still contains the row inserted by [1-4]:
1.93502
|
This looks suspicious to me.
First, semantically, [2-2] is a locking read. After the blocking transaction commits, I expected the locking read to see the latest committed rows.
Second, if the explanation is that [2-2] had already scanned past the insertion range before [1-3] was executed, then it is difficult to explain why the result still contains both boundary rows such as `1.16039` and `900788833`, and also contains the later inserted row from [1-4], namely `1.93502`. In `c0` order, the missing rows and the returned [1-4] row are all in the scan range.
Third, the result depends on the chosen index path. When the same locking read is forced to use the `c1` index or the `PRIMARY` index, it returns all 9 rows. However, when it uses the default index path, it returns only 6 rows.
I think the index path should not change the logical result of `SELECT ... LOCK IN SHARE MODE` after lock wait recovery.
How to repeat
Create and initialize the table:
DROP TABLE IF EXISTS t0; |
CREATE TABLE t0 ( |
c0 DOUBLE ZEROFILL UNIQUE PRIMARY KEY NOT NULL COMMENT 'asdf', |
c1 BIGINT ZEROFILL UNIQUE COMMENT 'asdf' |
) ENGINE=InnoDB;
|
INSERT IGNORE INTO t0(c0, c1) VALUES(1.32728, 61); |
INSERT IGNORE INTO t0(c0, c1) VALUES(1.69376, 73), (1.32728, 31), (1.16039, 13); |
CREATE INDEX idx_c0 ON t0(c0); |
Use two sessions.
For `READ COMMITTED`, set both sessions to:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; |
For `READ UNCOMMITTED`, set both sessions to:
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; |
Transaction 1:
[1-0] BEGIN; |
[1-1] UPDATE IGNORE t0 SET c1=NULL WHERE TRUE; |
[1-2] INSERT INTO t0(c0, c1) VALUES(1.68135, 15); |
[1-3] INSERT INTO t0(c0, c1) VALUES(1.25715, 10), (1.38422, 6), (1.32932, 97); |
[1-4] INSERT IGNORE INTO t0(c0, c1) VALUES(1.93502, 35); |
[1-5] COMMIT; |
Transaction 2:
[2-0] BEGIN; |
[2-1] INSERT INTO t0(c0, c1) VALUES(9.00788833E8, 1); |
[2-2] SELECT DISTINCTROW t0.c0 FROM t0 WHERE TRUE LOCK IN SHARE MODE; |
[2-3] COMMIT; |
Execute the statements in the following order:
[1-0] BEGIN; |
[1-1] UPDATE IGNORE t0 SET c1=NULL WHERE TRUE; |
[1-2] INSERT INTO t0(c0, c1) VALUES(1.68135, 15); |
[2-0] BEGIN; |
[2-1] INSERT INTO t0(c0, c1) VALUES(9.00788833E8, 1); |
[2-2] SELECT DISTINCTROW t0.c0 FROM t0 WHERE TRUE LOCK IN SHARE MODE; |
-- blocked
|
[1-3] INSERT INTO t0(c0, c1) VALUES(1.25715, 10), (1.38422, 6), (1.32932, 97); |
[1-4] INSERT IGNORE INTO t0(c0, c1) VALUES(1.93502, 35); |
[1-5] COMMIT; |
[2-2] resumes execution
|
[2-3] COMMIT; |
Actual result
When [2-2] uses the default index path, the query returns only 6 rows:
1.16039
|
1.32728
|
1.68135
|
1.69376
|
1.93502
|
900788833
|
The following three rows inserted by [1-3] are missing:
1.25715
|
1.32932
|
1.38422
|
However, the row inserted by [1-4] is returned:
1.93502
|
Additional comparison
If I force the same query to use the `PRIMARY` index, it returns all 9 rows:
SELECT DISTINCTROW t0.c0 |
FROM t0 FORCE INDEX(PRIMARY) |
WHERE TRUE LOCK IN SHARE MODE; |
If I force the same query to use the `c1` index, it also returns all 9 rows:
SELECT DISTINCTROW t0.c0 |
FROM t0 FORCE INDEX(c1) |
WHERE TRUE LOCK IN SHARE MODE; |
The expected 9-row result is:
1.16039
|
1.25715
|
1.32728
|
1.32932
|
1.38422
|
1.68135
|
1.69376
|
1.93502
|
900788833
|
Expected result
I expected [2-2] to return all 9 rows after the lock wait is released.
The reason is that [2-2] is a locking read with `WHERE TRUE`. Transaction 1 does not delete any row. After Transaction 1 commits, all inserted rows are committed and should be visible to the locking read.
Even if the query had already started scanning before [1-3] was executed, the observed result is still difficult to explain. The query misses exactly the three rows inserted by [1-3], but still returns the row inserted later by [1-4]. Since the missing rows and the returned [1-4] row are all in the `c0` scan range, the result seems inconsistent.
Furthermore, using `FORCE INDEX(PRIMARY)` or `FORCE INDEX(c1)` returns all 9 rows. Therefore, the logical result of `SELECT ... LOCK IN SHARE MODE` appears to depend on the index path.