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

BIT(8) column compared with string constant yields inconsistent results between full scan and index ref lookup: full scan returns 1 row, forced index returns empty set.

    XMLWordPrintable

Details

    • Bug
    • Status: Confirmed (View Workflow)
    • Major
    • Resolution: Unresolved
    • 10.11, 11.4, 11.8, 12.3, 13.0, 12.3.2
    • 10.11, 11.4, 11.8, 12.3, 13.0
    • Optimizer
    • Operating system:
      - Ubuntu 20.04.6 LTS (Focal Fossa)
      - Linux kernel: 5.4.0-204-generic
      - Architecture: x86_64

      Hardware:
      - CPU: 2 Ă— Intel Xeon Gold 5218R @ 2.10 GHz
      - 40 physical cores / 80 logical CPUs

    Description

          1. Problem Description
            In MariaDB, when a `BIT(8)` indexed column is compared with a string constant, a full table scan implicitly converts the string to a number at the SQL layer, while the index `ref` lookup path does not perform the same numeric conversion. This causes the same query to return different results under different execution plans. Constants of correct types (integer or bit literal) are not affected.
          1. How to Reproduce
            Execute the following SQL:

      ```sql
      DROP DATABASE IF EXISTS repro_mariadb809_db16_min;
      CREATE DATABASE repro_mariadb809_db16_min;
      USE repro_mariadb809_db16_min;

      CREATE TABLE base_rows (
      c0 BIT(8) NOT NULL
      ) ENGINE=MyISAM;

      INSERT INTO base_rows(c0)
      VALUES (1),(2),(3),(4),(5),(6),(7),(8);

      CREATE TABLE vp_source (
      vp_rowid BIGINT NOT NULL,
      c0 BIT(8) NOT NULL,
      PRIMARY KEY (vp_rowid)
      ) ENGINE=InnoDB;

      INSERT INTO vp_source(vp_rowid, c0)
      SELECT ROW_NUMBER() OVER (ORDER BY c0), c0
      FROM base_rows;

      CREATE TABLE vp_left (
      vp_rowid BIGINT NOT NULL PRIMARY KEY,
      c0 BIT(8) NOT NULL
      ) ENGINE=InnoDB;

      CREATE TABLE vp_right (
      vp_rowid BIGINT NOT NULL PRIMARY KEY,
      c0 BIT(8) NOT NULL
      ) ENGINE=InnoDB;

      INSERT INTO vp_left
      SELECT vp_rowid, c0 FROM vp_source;

      INSERT INTO vp_right
      SELECT vp_rowid, c0 FROM vp_source;

      CREATE INDEX idx_left_c0 ON vp_left(c0);
      ```

      *Single-table query:*
      ```sql
      SELECT DISTINCT
      CAST(LENGTHB(c0) AS UNSIGNED) AS result_value
      FROM vp_source
      WHERE c0 IN ('3bf0a4fa-72ff-433c-9a26-eecf4d741c6c');
      ```
      Actual result:
      ```
      result_value
      1
      ```

      *Reconstructed query, forcing vp_left(c0) index:*
      ```sql
      SELECT DISTINCT
      CAST(LENGTHB(c0) AS UNSIGNED) AS result_value
      FROM (
      SELECT
      l.vp_rowid,
      l.c0 AS c0
      FROM vp_left AS l FORCE INDEX (idx_left_c0)
      JOIN (
      SELECT
      vp_rowid,
      JSON_OBJECT('vp', vp_rowid) AS payload
      FROM vp_right
      ) AS p
      ON p.vp_rowid = l.vp_rowid
      JOIN vp_right AS r
      ON r.vp_rowid = p.vp_rowid
      ) AS reconstructed
      WHERE c0 IN ('3bf0a4fa-72ff-433c-9a26-eecf4d741c6c');
      ```
      Actual result:
      ```
      Empty set
      ```

      *Direct query execution plan:*
      ```
      id select_type table type possible_keys key rows Extra
      1 SIMPLE vp_source ALL NULL NULL 8 Using where; Using temporary
      ```

      *Forced-index reconstructed query execution plan:*
      ```
      id select_type table type possible_keys key key_len ref rows Extra
      1 SIMPLE l ref idx_left_c0 idx_left_c0 1 const 0 Using where; Using index; Using temporary
      1 SIMPLE vp_right
      eq_ref PRIMARY PRIMARY 8 repro_mariadb809_db16_min.l.vp_rowid 1 Distinct
      1 SIMPLE r eq_ref PRIMARY PRIMARY 8 repro_mariadb809_db16_min.l.vp_rowid 1 Distinct
      ```

      *Additionally, when the index is disabled:*
      ```sql
      SELECT DISTINCT
      CAST(LENGTHB(c0) AS UNSIGNED) AS result_value
      FROM (
      SELECT
      l.vp_rowid,
      l.c0 AS c0
      FROM vp_left AS l IGNORE INDEX (idx_left_c0)
      JOIN (
      SELECT
      vp_rowid,
      JSON_OBJECT('vp', vp_rowid) AS payload
      FROM vp_right
      ) AS p
      ON p.vp_rowid = l.vp_rowid
      JOIN vp_right AS r
      ON r.vp_rowid = p.vp_rowid
      ) AS reconstructed
      WHERE c0 IN ('3bf0a4fa-72ff-433c-9a26-eecf4d741c6c');
      ```
      The result recovers to:
      ```
      result_value
      1
      ```

          1. Expected Behavior
            The single-table query and the forced-index reconstructed query should return the same result. Whether a full table scan or an index `ref` lookup is used, the query result for the same string constant should be consistent.
          1. Actual Behavior
      • Single-table full scan: returns `result_value = 1`
      • Forced `idx_left_c0` index reconstructed query: returns `Empty set`
      • After disabling the index (`IGNORE INDEX`), full scan is restored and returns `result_value = 1` again
          1. Additional Analysis

      The `BIT(8)` column is compared with the string UUID `'3bf0a4fa-...'`. The numeric prefix of the string is `3`.

      • During a full table scan, the SQL layer implicitly converts the string to the number `3`, matches `c0=3`, `LENGTHB(c0)=1`, and therefore returns `1`.
      • When forcing the `idx_left_c0` index, the index `ref` lookup does not perform the same numeric conversion and may compare in binary/byte form, resulting in an empty set.
      • After `IGNORE INDEX`, full scan is restored and the result changes back to `1`.

      Attachments

        Issue Links

          Activity

            People

              psergei Sergei Petrunia
              Annie Annie liu
              Votes:
              0 Vote for this issue
              Watchers:
              3 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.