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

SELECT FOR UPDATE returns empty set instead of deadlocking under READ UNCOMMITTED / READ COMMITTED with innodb_snapshot_isolation=ON

    XMLWordPrintable

Details

    • Bug
    • Status: Confirmed (View Workflow)
    • Major
    • Resolution: Unresolved
    • 10.6, 10.11, 11.4, 11.8, 12.3, 12.2.2
    • 10.6, 10.11, 11.4, 11.8, 12.3
    • None
    • MariaDB Server 12.2.2
      Storage engine: InnoDB
      innodb_snapshot_isolation = ON
      Isolation levels tested: READ UNCOMMITTED, READ COMMITTED
    • Can result in unexpected behaviour

    Description

      Description:

      I found a possible bug in MariaDB 12.2.2 when innodb_snapshot_isolation=ON.

      In the following concurrent transaction schedule, SELECT * FROM t0 WHERE TRUE FOR UPDATE blocks after another transaction updates one row. Then the other transaction executes an UPDATE that modifies all rows but does not delete any row.

      Under READ UNCOMMITTED and READ COMMITTED, the blocked SELECT ... FOR UPDATE does not report a deadlock after the second UPDATE. Instead, it continues waiting, and after the blocking transaction commits, it returns an empty result set.

      However, under REPEATABLE READ, the same case reports a deadlock after executing the second UPDATE, which seems to be the expected behavior.

      This looks suspicious because the concurrent transaction only updates rows and does not delete them. Since the predicate is WHERE TRUE, the locking read should not return an empty result set after the wait is released.

      Reproduction:
      Please enable innodb_snapshot_isolation:
      SET GLOBAL innodb_snapshot_isolation = ON;
      Reconnect the test sessions, or make sure both sessions use innodb_snapshot_isolation=ON.

      Schema initialization:
      CREATE TABLE t0(
      c0 BOOLEAN UNIQUE,
      c1 REAL ZEROFILL NOT NULL,
      c2 BIGINT,
      c3 VARCHAR(100) UNIQUE NOT NULL
      ) ;
      INSERT INTO t0 VALUES (NULL, 0.71535, 807633510, '');
      INSERT INTO t0 VALUES (1, 0.12098, 1600742384, '0.5007103978381064');
      INSERT INTO t0 VALUES (2, 0.198, 16007384, '0.50071081064');
      CREATE INDEX ic0 ON t0(c2 ASC, c0, c3 DESC, c1);

      Initial table state:
      MariaDB [test]> select * from t0;
      -------------------------------------------------------------------+

      c0 c1 c2 c3

      -------------------------------------------------------------------+

      2 000000000000000000.198 16007384 0.50071081064
      NULL 0000000000000000.71535 807633510  
      1 0000000000000000.12098 1600742384 0.5007103978381064

      --------------------------------------------------------------------+

      – Transaction 102
      [102-0] BEGIN;
      [102-1] select * from t0 where true for update;
      [102-2] COMMIT;

      – Transaction 103
      [103-0] BEGIN;
      [103-1] UPDATE t0 SET c2 = 3141 where c0 = 2;
      [103-2] UPDATE t0 SET c2 = DEFAULT, c0 = DEFAULT WHERE TRUE;
      [103-3] COMMIT;

      The statements are executed according to the following schedule under READ UNCOMMITTED / READ COMMITTED :

      [102-0] BEGIN;
      [103-0] BEGIN;
      [103-1] UPDATE t0 SET c2 = 3141 WHERE c0 = 2;
      [102-1] SELECT * FROM t0 WHERE TRUE FOR UPDATE;
      – This statement becomes blocked.
      [103-2] UPDATE t0 SET c2 = DEFAULT, c0 = DEFAULT WHERE TRUE;
      Query OK, 3 rows affected (0.001 sec)
      Rows matched: 3 Changed: 3 Warnings: 0
      [103-3] COMMIT;
      [102-1] SELECT ... FOR UPDATE [The SELECT FOR UPDATE is unblocked and returns.]
      Epmty Set
      [102-2] COMMIT;

      Observed result under READ UNCOMMITTED / READ COMMITTED :
      After Transaction 103 commits, the blocked statement in Transaction 102 returns an empty result set:
      SELECT * FROM t0 WHERE TRUE FOR UPDATE;

      Observed output:
      Empty set

      Observed result under REPEATABLE READ:
      Under REPEATABLE READ, after executing:
      UPDATE t0 SET c2 = DEFAULT, c0 = DEFAULT WHERE TRUE;

      the blocked statement:
      SELECT * FROM t0 WHERE TRUE FOR UPDATE;
      reports a deadlock, which matches the expected lock conflict:
      ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

      Expected result:
      READ UNCOMMITTED and READ COMMITTED should not return an empty result set in this case.

      The concurrent transaction does not delete any row. It only updates existing rows:
      UPDATE t0 SET c2 = DEFAULT, c0 = DEFAULT WHERE TRUE;
      Therefore, after Transaction 103 commits, the table still contains three rows. The rows should be similar to:
      c0 c1 c2 c3
      NULL 0.71535 NULL ''
      NULL 0.12098 NULL '0.5007103978381064'
      NULL 0.198 NULL '0.50071081064'

      Multiple NULL values in the UNIQUE column c0 are allowed, so this UPDATE should not remove the rows or make them invisible.

      Therefore, the expected behavior should be one of the following:
      1.A deadlock is detected, as in REPEATABLE READ:
      ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
      2.A snapshot/check-read error is reported because innodb_snapshot_isolation=ON.
      3.If no error is reported, the blocked SELECT ... FOR UPDATE should return the latest committed rows after Transaction 103 commits.

      Returning an empty result set is unexpected because WHERE TRUE should match all existing rows.

      Attachments

        Issue Links

          Activity

            People

              marko Marko Mäkelä
              yousaha yousaha
              Votes:
              1 Vote for this issue
              Watchers:
              4 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.