Bug #77340 Optimizer chooses wrong index for LEFT JOIN query

Submitted: 12 Jun 2015 22:34 Modified: 18 Dec 2015 10:58
Reporter: Sveta Smirnova (OCA) Email Updates:
Status: In progress Impact on me:
None 
Category:MySQL Server: Optimizer Severity:S3 (Non-critical)
Version:5.6.25, 5.7.7 OS:Any
Assigned to: CPU Architecture:Any

[12 Jun 2015 22:34] Sveta Smirnova
Description:
Under certain circumstances Optimizer uses wrong index for certain query.

How to repeat:
Use attached file, start MySQL with option innodb-file-format=barracuda, then run

mysql> flush tables; flush status;
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

mysql> SELECT COUNT(DISTINCT `t1`.`id`) FROM `t1` INNER JOIN `t2` ON `t2`.`id` = `t1`.`f1` LEFT OUTER JOIN `t3` ON `t3`.`f1` = `t1`.`id` AND t3.f3 IS NULL AND `t3`.`f4` = 'Entry' LEFT OUTER JOIN `t3` `thumbnails_t3` ON `thumbnails_t3`.`f3` = `t3`.`id` WHERE `t1`.`f2` IS NULL AND `t1`.`f3` = 85260 AND (t1.f3 = 85260 AND t2.f2 IS NULL);
+---------------------------+
| COUNT(DISTINCT `t1`.`id`) |
+---------------------------+
| 512 |
+---------------------------+
1 row in set (13.73 sec)

mysql> show status like 'Handler_%';
+----------------------------+--------+
| Variable_name | Value |
+----------------------------+--------+
| Handler_commit | 1 |
| Handler_delete | 0 |
| Handler_discover | 0 |
| Handler_external_lock | 8 |
| Handler_mrr_init | 0 |
| Handler_prepare | 0 |
| Handler_read_first | 1 |
| Handler_read_key | 514 |
| Handler_read_last | 0 |
| Handler_read_next | 512512 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 26 |
| Handler_rollback | 0 |
| Handler_savepoint | 0 |
| Handler_savepoint_rollback | 0 |
| Handler_update | 0 |
| Handler_write | 0 |
+----------------------------+--------+
18 rows in set (0.00 sec)

mysql> explain SELECT COUNT(DISTINCT `t1`.`id`) FROM `t1` INNER JOIN `t2` ON `t2`.`id` = `t1`.`f1` LEFT OUTER JOIN `t3` force index(f1) ON `t3`.`f1` = `t1`.`id` AND t3.f3 IS NULL AND `t3`.`f4` = 'Entry' LEFT OUTER JOIN `t3` `thumbnails_t3` ON `thumbnails_t3`.`f3` = `t3`.`id` WHERE `t1`.`f2` IS NULL AND `t1`.`f3` = 85260 AND (t1.f3 = 85260 AND t2.f2 IS NULL);
+----+-------------+---------------+------------+------+---------------+------+---------+------------------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------+------------+------+---------------+------+---------+------------------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | t1 | NULL | ref | f3 | f3 | 4 | const | 511 | 10.00 | Using where |
| 1 | SIMPLE | t2 | NULL | ALL | PRIMARY | NULL | NULL | NULL | 25 | 0.40 | Using where; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | t3 | NULL | ref | f1 | f1 | 1032 | test.t1.id,const | 19 | 100.00 | Using where |
| 1 | SIMPLE | thumbnails_t3 | NULL | ref | f3 | f3 | 9 | test.t3.id | 3 | 100.00 | Using index |
+----+-------------+---------------+------------+------+---------------+------+---------+------------------+------+----------+----------------------------------------------------+
4 rows in set, 1 warning (0.00 sec)

mysql> flush tables; flush status; Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

mysql> SELECT COUNT(DISTINCT `t1`.`id`) FROM `t1` INNER JOIN `t2` ON `t2`.`id` = `t1`.`f1` LEFT OUTER JOIN `t3` force index(f1) ON `t3`.`f1` = `t1`.`id` AND t3.f3 IS NULL AND `t3`.`f4` = 'Entry' LEFT OUTER JOIN `t3` `thumbnails_t3` ON `thumbnails_t3`.`f3` = `t3`.`id` WHERE `t1`.`f2` IS NULL AND `t1`.`f3` = 85260 AND (t1.f3 = 85260 AND t2.f2 IS NULL);
+---------------------------+
| COUNT(DISTINCT `t1`.`id`) |
+---------------------------+
| 512 |
+---------------------------+
1 row in set (0.14 sec)

mysql> show status like 'Handler_%'; +----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| Handler_commit | 1 |
| Handler_delete | 0 |
| Handler_discover | 0 |
| Handler_external_lock | 8 |
| Handler_mrr_init | 0 |
| Handler_prepare | 0 |
| Handler_read_first | 1 |
| Handler_read_key | 514 |
| Handler_read_last | 0 |
| Handler_read_next | 512 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 26 |
| Handler_rollback | 0 |
| Handler_savepoint | 0 |
| Handler_savepoint_rollback | 0 |
| Handler_update | 0 |
| Handler_write | 0 |
+----------------------------+-------+
18 rows in set (0.00 sec)

CREATE TABLE `t3` (
  `id` bigint(20) NOT NULL,
  `f1` bigint(20) DEFAULT NULL,
  `f3` bigint(20) DEFAULT NULL,
  `f4` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `f3` (`f3`),
  KEY `f1` (`f1`,`f4`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;

Note that in the first case index f3 chosen while using f1 demonstrates better result.

Sometimes Optimizer chooses f3 index in optimizer_switch set to "use_index_extensions=off", but this is more likely repeatable with larger tables.
[12 Jun 2015 22:38] Sveta Smirnova
First two tables, necessary to repeat the bug

Attachment: issue53536_renamed.sql.1.gz (application/gzip, text), 2.64 MiB.

[12 Jun 2015 22:39] Sveta Smirnova
Third table, necessary to repeat this bug

Attachment: issue53536_renamed.sql.2.gz (application/gzip, text), 670.81 KiB.

[12 Jun 2015 22:42] Sveta Smirnova
Originally reported at https://bugs.launchpad.net/percona-server/+bug/1464789
[13 Jun 2015 23:36] MySQL Verification Team
Thank you for the bug version. Version 5.5 not affected.

C:\dbs>c:\dbs\5.5\bin\mysql -uroot --port=3550 --prompt="mysql 5.5 > "
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.5.45-log Source distribution PULL: 2015/06/05

mysql 5.5 > use test
Database changed
mysql 5.5 > SHOW VARIABLES LIKE "innodb_file_format";
+--------------------+-----------+
| Variable_name      | Value     |
+--------------------+-----------+
| innodb_file_format | Barracuda |
+--------------------+-----------+
1 row in set (0.00 sec)

mysql 5.5 >  flush tables; flush status;
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

mysql 5.5 > SELECT COUNT(DISTINCT `t1`.`id`) FROM `t1` INNER JOIN `t2` ON `t2`.`id` = `t1`.`f1` LEFT OUTER JOIN `t3` ON `t3`.`f1` = `t1`.`id` AND t3.f3 IS NULL AND `t3`.`f4` = 'Entry' LEFT OUTER JOIN `t3` `thumbnails_t3` ON `thumbnails_t3`.`f3` = `t3`.`id` WHE
RE `t1`.`f2` IS NULL AND `t1`.`f3` = 85260 AND (t1.f3 = 85260 AND t2.f2 IS NULL);
+---------------------------+
| COUNT(DISTINCT `t1`.`id`) |
+---------------------------+
|                       512 |
+---------------------------+
1 row in set (0.00 sec)

mysql 5.5 > show status like 'Handler_%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Handler_commit             | 1     |
| Handler_delete             | 0     |
| Handler_discover           | 0     |
| Handler_prepare            | 0     |
| Handler_read_first         | 0     |
| Handler_read_key           | 514   |
| Handler_read_last          | 0     |
| Handler_read_next          | 512   |
| Handler_read_prev          | 0     |

C:\dbs>c:\dbs\5.6\bin\mysql -uroot --port=3560 -p --prompt="mysql 5.6 > "
Enter password: ******
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.6.26 Source distribution PULL 2015/06/05

mysql 5.6 > use test
Database changed
mysql 5.6 > SHOW VARIABLES LIKE "innodb_file_format";
+--------------------+-----------+
| Variable_name      | Value     |
+--------------------+-----------+
| innodb_file_format | Barracuda |
+--------------------+-----------+
1 row in set (0.00 sec)

mysql 5.6 >  flush tables; flush status;
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

mysql 5.6 > SELECT COUNT(DISTINCT `t1`.`id`) FROM `t1` INNER JOIN `t2` ON `t2`.`id` = `t1`.`f1` LEFT OUTER JOIN `t3` ON `t3`.`f1` = `t1`.`id` AND t3.f3 IS NULL AND `t3`.`f4` = 'Entry' LEFT OUTER JOIN `t3` `thumbnails_t3` ON `thumbnails_t3`.`f3` = `t3`.`id` WHE
RE `t1`.`f2` IS NULL AND `t1`.`f3` = 85260 AND (t1.f3 = 85260 AND t2.f2 IS NULL);
+---------------------------+
| COUNT(DISTINCT `t1`.`id`) |
+---------------------------+
|                       512 |
+---------------------------+
1 row in set (0.30 sec)

mysql 5.6 > show status like 'Handler_%';
+----------------------------+--------+
| Variable_name              | Value  |
+----------------------------+--------+
| Handler_commit             | 1      |
| Handler_delete             | 0      |
| Handler_discover           | 0      |
| Handler_external_lock      | 8      |
| Handler_mrr_init           | 0      |
| Handler_prepare            | 0      |
| Handler_read_first         | 0      |
| Handler_read_key           | 514    |
| Handler_read_last          | 0      |
| Handler_read_next          | 512512 |
| Handler_read_prev          | 0      |

mysql 5.6 >  flush tables; flush status;
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

mysql 5.6 > SELECT COUNT(DISTINCT `t1`.`id`) FROM `t1` INNER JOIN `t2` ON `t2`.`id` = `t1`.`f1` LEFT OUTER JOIN `t3` force index(f1) ON `t3`.`f1` = `t1`.`id` AND t3.f3 IS NULL AND `t3`.`f4` = 'Entry' LEFT OUTER JOIN `t3` `thumbnails_t3` ON `thumbnails_t3`.`f3`
 = `t3`.`id` WHERE `t1`.`f2` IS NULL AND `t1`.`f3` = 85260 AND (t1.f3 = 85260 AND t2.f2 IS NULL);
+---------------------------+
| COUNT(DISTINCT `t1`.`id`) |
+---------------------------+
|                       512 |
+---------------------------+
1 row in set (0.00 sec)

mysql 5.6 >  show status like 'Handler_%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Handler_commit             | 1     |
| Handler_delete             | 0     |
| Handler_discover           | 0     |
| Handler_external_lock      | 8     |
| Handler_mrr_init           | 0     |
| Handler_prepare            | 0     |
| Handler_read_first         | 0     |
| Handler_read_key           | 514   |
| Handler_read_last          | 0     |
| Handler_read_next          | 512   |
| Handler_read_prev          | 0     |

C:\dbs>c:\dbs\5.7\bin\mysql -uroot -p --port=3570 --prompt="mysql 5.7 > "
Enter password: ******
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.8-rc Source distribution PULL 2015/06/05

mysql 5.7 > use test
Database changed
mysql 5.7 > SHOW VARIABLES LIKE "innodb_file_format";
+--------------------+-----------+
| Variable_name      | Value     |
+--------------------+-----------+
| innodb_file_format | Barracuda |
+--------------------+-----------+
1 row in set, 1 warning (0.00 sec)

mysql 5.7 >  flush tables; flush status;
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

mysql 5.7 > SELECT COUNT(DISTINCT `t1`.`id`) FROM `t1` INNER JOIN `t2` ON `t2`.`id` = `t1`.`f1` LEFT OUTER JOIN `t3` ON `t3`.`f1` = `t1`.`id` AND t3.f3 IS NULL AND `t3`.`f4` = 'Entry' LEFT OUTER JOIN `t3` `thumbnails_t3` ON `thumbnails_t3`.`f3` = `t3`.`id` WHE
RE `t1`.`f2` IS NULL AND `t1`.`f3` = 85260 AND (t1.f3 = 85260 AND t2.f2 IS NULL);
+---------------------------+
| COUNT(DISTINCT `t1`.`id`) |
+---------------------------+
|                       512 |
+---------------------------+
1 row in set (0.38 sec)

mysql 5.7 > show status like 'Handler_%';
+----------------------------+--------+
| Variable_name              | Value  |
+----------------------------+--------+
| Handler_commit             | 1      |
| Handler_delete             | 0      |
| Handler_discover           | 0      |
| Handler_external_lock      | 9      |
| Handler_mrr_init           | 0      |
| Handler_prepare            | 0      |
| Handler_read_first         | 1      |
| Handler_read_key           | 514    |
| Handler_read_last          | 0      |
| Handler_read_next          | 512512 |
| Handler_read_prev          | 0      |
| Handler_read_rnd           | 0      |
| Handler_read_rnd_next      | 26     |
| Handler_rollback           | 0      |
| Handler_savepoint          | 0      |
| Handler_savepoint_rollback | 0      |
| Handler_update             | 0      |
| Handler_write              | 0      |
+----------------------------+--------+
18 rows in set (0.00 sec)
[18 Dec 2015 10:58] Sveta Smirnova
EXPLAIN FORMAT=JSON  cost_info for the slow query:

    "cost_info": {
      "query_cost": "1212.09"
    },

For the fast query:

    "cost_info": {
      "query_cost": "2349.71"
    },

For t3 in the slow query:

          "cost_info": {
            "read_cost": "191.75",
            "eval_cost": "38.35",
            "prefix_cost": "875.62",
            "data_read_per_join": "196K"
          },

For t3 in the fast query:

          "cost_info": {
            "read_cost": "273.36",
            "eval_cost": "54.67",
            "prefix_cost": "1870.04",
            "data_read_per_join": "279K"
          },

Looks like these calculations wrong, at least overall query cost.
[28 May 1:13] MySQL Admin
Posted by developer: Bug status updated to 'In progress'

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

Page generated in 0.044 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.