Uploaded image for project: 'MariaDB Server'
  1. MariaDB Server
  2. MDEV-40187

SELECT LOCK IN SHARE MODE misses rows depending on index path after lock wait recovery

    XMLWordPrintable

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
    • Hide
      With innodb_snapshot_isolation either ON or OFF, SELECT ... LOCK IN SHARE MODE under READ COMMITTED / READ UNCOMMITTED may return fewer rows after lock wait recovery when using the default secondary index path, while PRIMARY or c1 index paths return all rows.
      Show
      With innodb_snapshot_isolation either ON or OFF, SELECT ... LOCK IN SHARE MODE under READ COMMITTED / READ UNCOMMITTED may return fewer rows after lock wait recovery when using the default secondary index path, while PRIMARY or c1 index paths return all rows.

    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.

      Attachments

        Activity

          People

            Unassigned Unassigned
            yousaha yousaha
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

              Created:
              Updated:

              Git Integration

                Error rendering 'com.xiplink.jira.git.jira_git_plugin:git-issue-webpanel'. Please contact your Jira administrators.