Bug #117386 Ambiguous functional index not based on any column under-fetches rows
Submitted: 5 Feb 10:13 Modified: 5 Feb 13:39
Reporter: Emily Ong Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: Optimizer Severity:S2 (Serious)
Version:8.0, 8.0.41, 8.4.4 OS:Ubuntu
Assigned to: CPU Architecture:x86

[5 Feb 10:13] Emily Ong
Description:
Using a functional index based on a constant value and constructing a query with a WHERE clause that references the particular constant value might under-fetch rows.

How to repeat:
CREATE TABLE IF NOT EXISTS t0(c0 INT);
INSERT INTO t0(c0) VALUES(1);
CREATE INDEX i0 ON t0((-123));
CREATE INDEX i1 ON t0(c0 ASC);

(1) The table is as follows:

|  c0  |
| ---- |
| 1    |

(2) In the following query, 0 rows are returned (unexpected):

SELECT
    t0.c0 AS ref0
FROM t0
WHERE t0.c0 >= (((NOT (-123)) AND NULL));

| ref0 |
| ---- |

This is unexpected because the constant expression `(((NOT (-123)) AND NULL))` evaluates to 0, based on the documentation of the logical operator at https://dev.mysql.com/doc/refman/8.4/en/logical-operators.html#operator_and.

Thus, the first row of the table in (1) should be returned because its c0-column is 1.

The expected result should be:

| ref0 |
| ---- |
| 1    |

(3) When the index i0 or i1 is removed, 1 row is returned (expected):

DROP INDEX i0 ON t0; -- Or DROP INDEX i1 ON t0;
SELECT
    t0.c0 AS ref0
FROM t0
WHERE t0.c0 >= (((NOT (-123)) AND NULL));

| ref0 |
| ---- |
| 1    |

According to the documentation (https://dev.mysql.com/doc/refman/8.4/en/mysql-indexes.html), indexes are used during query optimization to "find rows with specific column values quickly".
Thus, both the queries in (2) and (3) are semantically equivalent since the use of indexes should not change the result of the query execution. But, these queries produce different results in this case.

(4) Moreover, the constant expression `-123` in the functional index `i0` appears to have a dependence with the constant expression `-123` that is used in the WHERE clause of the query.

For example, the following query (where `-123` in the WHERE clause is replaced by `-246`) returns one row as expected:

CREATE TABLE IF NOT EXISTS t0(c0 INT);
INSERT INTO t0(c0) VALUES(1);
CREATE INDEX i0 ON t0((-123));
CREATE INDEX i1 ON t0(c0 ASC);

SELECT
    t0.c0 AS ref0
FROM t0
WHERE t0.c0 >= (((NOT (-246)) AND NULL));

Likewise, the following query (where `-123` in the index `i0` is replaced by `-246`) returns one row as expected:

CREATE TABLE IF NOT EXISTS t0(c0 INT);
INSERT INTO t0(c0) VALUES(1);
CREATE INDEX i0 ON t0((-246));
CREATE INDEX i1 ON t0(c0 ASC);

SELECT
    t0.c0 AS ref0
FROM t0
WHERE t0.c0 >= (((NOT (-123)) AND NULL));

(5) The query plan is as follows:

CREATE TABLE IF NOT EXISTS t0(c0 INT);
INSERT INTO t0(c0) VALUES(1);
CREATE INDEX i0 ON t0((-123));
CREATE INDEX i1 ON t0(c0 ASC);

EXPLAIN SELECT
    t0.c0 AS ref0
FROM t0
WHERE t0.c0 >= (((NOT (-123)) AND NULL))

+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                          |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+
|  1 | SIMPLE      | NULL  | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL |     NULL | no matching row in const table |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+

In this case, based on the message, the documentation (https://dev.mysql.com/doc/refman/8.4/en/explain-output.html) implies that "For a query with a join, there was an empty table or a table with no rows satisfying a unique index condition.".
However, in this query, there is neither an empty table, nor a unique/primary key index condition used.

Furthermore, the documentation (https://dev.mysql.com/doc/refman/8.4/en/explain-output.html#jointype_const) mentioned that the `const` table is used when "you compare all parts of a PRIMARY KEY or UNIQUE index to constant values", which is not the case in this scenario.

Thus, this is an unexpected result.

Furthermore, in MySQL-compatible databases, such as TiDB (8.5), the same query produces the expected result.

| ref0 |
| ---- |
| 1    |
[5 Feb 13:39] MySQL Verification Team
Hello Emily Ong,

Thank you for the report and test case.

regards,
Umesh