Bug #108293 Prepared statement does not use generated column index
Submitted: 26 Aug 2022 9:30 Modified: 5 Sep 2022 7:43
Reporter: Xiaodi Z Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: Prepared statements Severity:S2 (Serious)
Version:8.0.29 OS:Any
Assigned to: CPU Architecture:Any
Tags: generated column, prepared statement

[26 Aug 2022 9:30] Xiaodi Z
Description:
Prepared statement does not use generated column index

Reported by GoldenDB Team

How to repeat:
create table t1(
  c1 json,
  c2 int,
  gc varchar(50) as (c1->>'$.str') stored,
  key k_gc(gc)
);

delimiter //
create procedure proc1(in begin int, in end int)
begin
    declare i int;
    set i = begin;
    while i < end do
        insert into t1(c1, c2) values(concat('{"str": "', i, '"}'), i);
        set i = i + 1;
    end while;
end//
delimiter ;

call proc1(0, 1000);

prepare p1 from "select * from t1 where json_extract(c1, '$.str') = ?";
set @a = '1';

mysql> set session optimizer_trace = "enabled=on";
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t1 where json_extract(c1, '$.str') = '1';
+--------------+------+------+
| c1           | c2   | gc   |
+--------------+------+------+
| {"str": "1"} |    1 | 1    |
+--------------+------+------+
1 row in set (0.00 sec)

mysql> select * from information_schema.optimizer_trace\G
...
          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ],
                "table": "`t1`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "access_type": "ref",
                      "index": "k_gc",
                      "rows": 1,
                      "cost": 0.35,
                      "chosen": true
                    },
                    {
                      "access_type": "range",
                      "range_details": {
                        "used_index": "k_gc"
                      },
                      "chosen": false,
                      "cause": "heuristic_index_cheaper"
                    }
                  ]
                },
                "condition_filtering_pct": 100,
                "rows_for_plan": 1,
                "cost_for_plan": 0.35,
                "chosen": true
              }
            ]
          },
...

mysql> execute p1 using @a;
+--------------+------+------+
| c1           | c2   | gc   |
+--------------+------+------+
| {"str": "1"} |    1 | 1    |
+--------------+------+------+
1 row in set (0.08 sec)

mysql> select * from information_schema.optimizer_trace\G
...
          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ],
                "table": "`t1`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "rows_to_scan": 1000,
                      "access_type": "scan",
                      "resulting_rows": 1000,
                      "cost": 101.5,
                      "chosen": true
                    }
                  ]
                },
                "condition_filtering_pct": 100,
                "rows_for_plan": 1000,
                "cost_for_plan": 101.5,
                "chosen": true
              }
            ]
          },
...

The execution time of prepared statement is more than query statement. From optimizer trace we can see, for query statement, index for generated column is used, but for prepared statement, it's not.

Suggested fix:
sql/range_optimizer/range_optimizer.cc

bool comparable_in_index(Item *cond_func, const Field *field,
                         const Field::imagetype itype,
                         Item_func::Functype comp_type, const Item *value) {
...
  /*
    We can't use indexes when comparing to a JSON value. For example,
    the string '{}' should compare equal to the JSON string "{}". If
    we use a string index to compare the two strings, we will be
    comparing '{}' and '"{}"', which don't compare equal.
    The only exception is Item_json, which is a basic const item and is
    used to contain value coerced to index's type.
  */
  if (value->result_type() == STRING_RESULT &&
      value->data_type() == MYSQL_TYPE_JSON && !value->basic_const_item())
    return false;

  return true;
}

https://dev.mysql.com/worklog/task/?id=9384
For Item_param, it is not a basic const item, so 'field' and 'value' are not comparable.
[5 Sep 2022 7:43] MySQL Verification Team
Hello Xiaodi Z,

Thank you for the report and test case.

regards,
Umesh