Bug #84024 Optimizer thinks clustered primary key is not covering

Submitted: 1 Dec 2016 2:40 Modified: 2 Dec 2016 9:07
Reporter: Manuel Ung Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: Optimizer Severity:S3 (Non-critical)
Version:5.7.16, 5.6.34 OS:Any
Assigned to: CPU Architecture:Any

[1 Dec 2016 2:40] Manuel Ung
Description:
The optimizer uses covering_keys as a heuristic to check whether to do a table scan or do a ref access in sql_planner.cc. This doesn't work well for clustered primary keys, because it's not in covering_keys, but the cost should be about the same.

How to repeat:
create table t1 (i int primary key, j int, key (j)) engine=innodb;
create table t2 (i int primary key, j int) engine=innodb;
create table t3 (i int, j int, primary key (i)) engine=innodb;
insert into t1 values (1, 1);
insert into t1 select i + 1, j + 1 from t1;
insert into t1 select i + 2, j + 2 from t1;
insert into t1 select i + 4, j + 4 from t1;
insert into t1 select i + 8, j + 8 from t1;
insert into t1 select i + 16, j + 16 from t1;
insert into t1 select i + 32, j + 32 from t1;
insert into t1 select i + 64, j + 64 from t1;
insert into t1 select i + 128, j + 128 from t1;
insert into t1 select i + 256, j + 256 from t1;
insert into t1 select i + 512, j + 512 from t1;
insert into t1 select i + 1024, j + 1024 from t1;
insert into t1 select i + 2048, j + 2048 from t1;
insert into t2 select * from t1;
insert into t3 values (1, 1);
analyze table t1, t2, t3;
explain select t3.j from (t1 left join t2 on t1.i = t2.i) left join t3 on t1.i = t3.i where t1.j > 0 and t1.j < 5000;

Gives you this plan:
id      select_type     table   type    possible_keys   key     key_len ref     rows    Extra
1       SIMPLE  t1      range   j       j       5       NULL    4094    Using where; Using index
1       SIMPLE  t2      eq_ref  PRIMARY PRIMARY 4       test.t1.i       1       Using index
1       SIMPLE  t3      ALL     PRIMARY NULL    NULL    NULL    1       Using where; Using join buffer (Block Nested Loop)

Note that t3 is not using primary key.

Suggested fix:
In sql/sql_planner.cc, do an extra check for s->table->file->primary_key_is_clustered() when evaluating the heuristic "covering_index_better_than_full_scan".
[1 Dec 2016 4:25] MySQL Verification Team
Hello Manuel,

Thank you for the report and test case.
Verified as described with 5.7.16 build.

Thanks,
Umesh
[1 Dec 2016 8:46] Øystein Grøvlen
AFAICT, for the given query, the reason table scan is preferred over index look-up is that the table contains only one row.  When there is only a few rows in the table, table scan with join-buffering is probably cheaper than primary index look-ups.  If I increase the number of rows, PK look-ups will be used:

mysql> insert into t3 select i+1, j from t3;
Query OK, 1 row affected (0,00 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> insert into t3 select i+2, j from t3;
Query OK, 2 rows affected (0,00 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> insert into t3 select i+4, j from t3;
Query OK, 4 rows affected (0,00 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> explain select t3.j from (t1 left join t2 on t1.i = t2.i) left join t3 on t1.i = t3.i where t1.j > 0 and t1.j < 5000;
+----+-------------+-------+------------+--------+---------------+---------+---------+---------------+------+----------+--------------------------+
| id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref           | rows | filtered | Extra                    |
+----+-------------+-------+------------+--------+---------------+---------+---------+---------------+------+----------+--------------------------+
|  1 | SIMPLE      | t1    | NULL       | index  | j             | j       | 5       | NULL          | 4096 |   100.00 | Using where; Using index |
|  1 | SIMPLE      | t2    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | bug84024.t1.i |    1 |   100.00 | Using index              |
|  1 | SIMPLE      | t3    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | bug84024.t1.i |    1 |   100.00 | NULL                     |
+----+-------------+-------+------------+--------+---------------+---------+---------+---------------+------+----------+--------------------------+
3 rows in set, 1 warning (0,00 sec)
[1 Dec 2016 22:22] Manuel Ung
Maybe it makes sense? Although consider that:

1. You'll get the more "expensive" plan if you use a covered key instead, so that's kind of strange.
2. In practice, creating the join buffer might be more expensive than rereading the low cardinality t3 table (especially if it's empty).
[2 Dec 2016 8:50] Øystein Grøvlen
Hi Manuel,

I can agree that the cost model for join buffering is not perfect.  If so, I think we should improve this model, not rely on heuristics about covering indexes versus table scan.
[2 Dec 2016 9:07] Manuel Ung
Improving the cost model would be great if possible.

Changing the heuristic just seemed like an easier/safe workaround since it takes effect on covering keys already.

Portions of this website are copyright © 2001, 2002 The PHP Group

Page generated in 0.057 sec. using MySQL 8.4.7-u6-cloud

Timestamp references displayed by the system are UTC.
Content reproduced on this site is the property of the respective copyright holders.
It is not reviewed in advance by Oracle and does not necessarily represent the opinion of Oracle or any other party.