Bug #119713 MySQL has hard time using the right indexes
Submitted: 18 Jan 11:19 Modified: 20 Jan 9:35
Reporter: Aaditya Dubey Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: Optimizer Severity:S3 (Non-critical)
Version:8.0.39, 8.0.44, 8.4.6 OS:Any
Assigned to: CPU Architecture:Any

[18 Jan 11:19] Aaditya Dubey
Description:
In MariaDB, the page loaded in ~0.5 seconds. In MySQL, the same query runs much slower, increasing the page load time from ~0.5 seconds to 3–5+ seconds.

It looks like a few queries that “fly” on MariaDB don’t perform nearly as well on MySQL.

What’s worse: when we upgraded from MySQL 8.0 to 8.4, things got even slower by about 0.2–0.3 seconds instead of improving.

How to repeat:
Load the data: prod_db.sql.gz

Run the below-mentioned queries on 8.0 and 8.4

MySQL 8.0.44:

1st iteration:

SELECT   p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) AS quantity, pl.`description`, pl.`description_short`, pl.`available_now`, pl.`available_later`, pl.`link_rewrite`, pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`, pl.`name`, image_shop.`id_image` id_image, il.`legend` as legend, m.`name` AS manufacturer_name, cl.`name` AS category_default, DATEDIFF(product_shop.`date_add`, DATE_SUB("2024-10-09 00:00:00", INTERVAL 30 DAY)) > 0 AS new, product_shop.price AS orderprice FROM `ps_category_product` cp LEFT JOIN `ps_product` p ON p.`id_product` = cp.`id_product` INNER JOIN ps_product_shop product_shop ON (product_shop.id_product = p.id_product AND product_shop.id_shop = 7) LEFT JOIN ps_stock_available stock ON (stock.id_product = `p`.id_product AND stock.id_product_attribute = 0 AND stock.id_shop_group = 2 AND stock.id_shop = 0 ) LEFT JOIN `ps_category_lang` cl ON (product_shop.`id_category_default` = cl.`id_category` AND cl.`id_lang` = 4 AND cl.id_shop = 7 ) LEFT JOIN `ps_product_lang` pl ON (p.`id_product` = pl.`id_product` AND pl.`id_lang` = 4 AND pl.id_shop = 7 ) LEFT JOIN `ps_image_shop` image_shop ON (image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop=7) LEFT JOIN `ps_image_lang` il ON (image_shop.`id_image` = il.`id_image` AND il.`id_lang` = 4) LEFT JOIN `ps_manufacturer` m ON m.`id_manufacturer` = p.`id_manufacturer` WHERE product_shop.`id_shop` = 7 AND cp.`id_category` = 115 AND product_shop.`active` = 1 AND product_shop.`visibility` IN ("both", "catalog") ORDER BY cp.`position` ASC LIMIT 0,35;
...
35 rows in set, 65535 warnings (18.11 sec)

2nd iteration:

mysql [localhost:8047] {root} (test) >   SELECT   p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) AS quantity, pl.`description`, pl.`description_short`, pl.`available_now`, pl.`available_later`, pl.`link_rewrite`, pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`, pl.`name`, image_shop.`id_image` id_image, il.`legend` as legend, m.`name` AS manufacturer_name, cl.`name` AS category_default, DATEDIFF(product_shop.`date_add`, DATE_SUB("2024-10-09 00:00:00", INTERVAL 30 DAY)) > 0 AS new, product_shop.price AS orderprice FROM `ps_category_product` cp LEFT JOIN `ps_product` p ON p.`id_product` = cp.`id_product` INNER JOIN ps_product_shop product_shop ON (product_shop.id_product = p.id_product AND product_shop.id_shop = 7) LEFT JOIN ps_stock_available stock ON (stock.id_product = `p`.id_product AND stock.id_product_attribute = 0 AND stock.id_shop_group = 2 AND stock.id_shop = 0 ) LEFT JOIN `ps_category_lang` cl ON (product_shop.`id_category_default` = cl.`id_category` AND cl.`id_lang` = 4 AND cl.id_shop = 7 ) LEFT JOIN `ps_product_lang` pl ON (p.`id_product` = pl.`id_product` AND pl.`id_lang` = 4 AND pl.id_shop = 7 ) LEFT JOIN `ps_image_shop` image_shop ON (image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop=7) LEFT JOIN `ps_image_lang` il ON (image_shop.`id_image` = il.`id_image` AND il.`id_lang` = 4) LEFT JOIN `ps_manufacturer` m ON m.`id_manufacturer` = p.`id_manufacturer` WHERE product_shop.`id_shop` = 7 AND cp.`id_category` = 115 AND product_shop.`active` = 1 AND product_shop.`visibility` IN ("both", "catalog") ORDER BY cp.`position` ASC LIMIT 0,35;
35 rows in set, 65535 warnings (3.45 sec)

MySQL 8.4.6:

1st iteration:

mysql [localhost:8407] {root} (test) > SELECT p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) AS quantity, pl.`description`, pl.`description_short`, pl.`available_now`, pl.`available_later`, pl.`link_rewrite`, pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`, pl.`name`, image_shop.`id_image` id_image, il.`legend` as legend, m.`name` AS manufacturer_name, cl.`name` AS category_default, DATEDIFF(product_shop.`date_add`, DATE_SUB("2024-10-09 00:00:00", INTERVAL 30 DAY)) > 0 AS new, product_shop.price AS orderprice FROM `ps_category_product` cp LEFT JOIN `ps_product` p ON p.`id_product` = cp.`id_product` INNER JOIN ps_product_shop product_shop ON (product_shop.id_product = p.id_product AND product_shop.id_shop = 7) LEFT JOIN ps_stock_available stock ON (stock.id_product = `p`.id_product AND stock.id_product_attribute = 0 AND stock.id_shop_group = 2 AND stock.id_shop = 0 ) LEFT JOIN `ps_category_lang` cl ON (product_shop.`id_category_default` = cl.`id_category` AND cl.`id_lang` = 4 AND cl.id_shop = 7 ) LEFT JOIN `ps_product_lang` pl ON (p.`id_product` = pl.`id_product` AND pl.`id_lang` = 4 AND pl.id_shop = 7 ) LEFT JOIN `ps_image_shop` image_shop ON (image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop=7) LEFT JOIN `ps_image_lang` il ON (image_shop.`id_image` = il.`id_image` AND il.`id_lang` = 4) LEFT JOIN `ps_manufacturer` m ON m.`id_manufacturer` = p.`id_manufacturer` WHERE product_shop.`id_shop` = 7 AND cp.`id_category` = 115 AND product_shop.`active` = 1 AND product_shop.`visibility` IN ("both", "catalog") ORDER BY cp.`position` ASC LIMIT 0,35;
35 rows in set, 65535 warnings (30.35 sec)

2nd iteration:

mysql [localhost:8407] {root} (test) > SELECT p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) AS quantity, pl.`description`, pl.`description_short`, pl.`available_now`, pl.`available_later`, pl.`link_rewrite`, pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`, pl.`name`, image_shop.`id_image` id_image, il.`legend` as legend, m.`name` AS manufacturer_name, cl.`name` AS category_default, DATEDIFF(product_shop.`date_add`, DATE_SUB("2024-10-09 00:00:00", INTERVAL 30 DAY)) > 0 AS new, product_shop.price AS orderprice FROM `ps_category_product` cp LEFT JOIN `ps_product` p ON p.`id_product` = cp.`id_product` INNER JOIN ps_product_shop product_shop ON (product_shop.id_product = p.id_product AND product_shop.id_shop = 7) LEFT JOIN ps_stock_available stock ON (stock.id_product = `p`.id_product AND stock.id_product_attribute = 0 AND stock.id_shop_group = 2 AND stock.id_shop = 0 ) LEFT JOIN `ps_category_lang` cl ON (product_shop.`id_category_default` = cl.`id_category` AND cl.`id_lang` = 4 AND cl.id_shop = 7 ) LEFT JOIN `ps_product_lang` pl ON (p.`id_product` = pl.`id_product` AND pl.`id_lang` = 4 AND pl.id_shop = 7 ) LEFT JOIN `ps_image_shop` image_shop ON (image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop=7) LEFT JOIN `ps_image_lang` il ON (image_shop.`id_image` = il.`id_image` AND il.`id_lang` = 4) LEFT JOIN `ps_manufacturer` m ON m.`id_manufacturer` = p.`id_manufacturer` WHERE product_shop.`id_shop` = 7 AND cp.`id_category` = 115 AND product_shop.`active` = 1 AND product_shop.`visibility` IN ("both", "catalog") ORDER BY cp.`position` ASC LIMIT 0,35;
35 rows in set, 65535 warnings (28.68 sec)

We have some more queries with a similar performance issue:

mysql [localhost:8407] {root} (test) > SELECT   COUNT(DISTINCT p.id_product) FROM   ps_product p   LEFT JOIN ps_product_lang pl ON p.id_product = pl.id_product   AND pl.id_lang = '4'   AND pl.id_shop = p.id_shop_default   LEFT JOIN ps_product_shop ps ON p.id_product = ps.id_product   AND ps.id_shop = p.id_shop_default WHERE   NOT EXISTS(     SELECT       1     FROM       ps_image_shop img     WHERE       (p.id_product = img.id_product)       AND (img.id_shop = p.id_shop_default)   );
1 row in set (27.05 sec)

mysql [localhost:8407] {root} (test) > SELECT   p.id_product,   p.reference,   p.active,   pl.name FROM   ps_product p   LEFT JOIN ps_product_lang pl ON p.id_product = pl.id_product   AND pl.id_lang = '4'   AND pl.id_shop = p.id_shop_default   LEFT JOIN ps_product_shop ps ON p.id_product = ps.id_product   AND ps.id_shop = p.id_shop_default WHERE   EXISTS(     SELECT       1     FROM       ps_stock_available stock     WHERE       (p.id_product = stock.id_product)       AND (         NOT EXISTS(           SELECT             1           FROM             ps_product_attribute pa           WHERE             pa.id_product = p.id_product         )       )       AND (IFNULL(stock.quantity, 0) <= 0)   ) ORDER BY   name asc LIMIT   20;
20 rows in set (1 min 7.06 sec)

mysql [localhost:8407] {root} (test) > SELECT   COUNT(DISTINCT p.id_product) FROM   ps_product p   LEFT JOIN ps_product_lang pl ON p.id_product = pl.id_product   AND pl.id_lang = '4'   AND pl.id_shop = p.id_shop_default   LEFT JOIN ps_product_shop ps ON p.id_product = ps.id_product   AND ps.id_shop = p.id_shop_default WHERE   (p.price = 0)   AND (p.wholesale_price = 0)   AND (     NOT EXISTS(       SELECT         1       FROM         ps_specific_price sp       WHERE
     p.id_product = sp.id_product     )   );
1 row in set (20.11 sec)
[18 Jan 12:18] Aaditya Dubey
mysql [localhost:8407] {root} (test) > explain SELECT   p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) AS quantity, pl.`description`, pl.`description_short`, pl.`available_now`, pl.`available_later`, pl.`link_rewrite`, pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`,
    -> pl.`name`, image_shop.`id_image` id_image, il.`legend` as legend, m.`name` AS manufacturer_name, cl.`name` AS category_default, DATEDIFF(product_shop.`date_add`, DATE_SUB("2024-10-09 00:00:00", INTERVAL 30 DAY)) > 0 AS new, product_shop.price AS orderprice FROM `ps_category_product` cp LEFT JOIN `ps_product` p ON p.`id_product` = cp.`id_product` INNER JOIN ps_product_shop product_shop ON (product_shop.id_product = p.id_product AND product_shop.id_shop = 7) LEFT JOIN ps_stock_available stock ON (stock.id_product = `p`.id_product
    -> AND stock.id_product_attribute = 0 AND stock.id_shop_group = 2 AND stock.id_shop = 0 ) LEFT JOIN `ps_category_lang` cl ON (product_shop.`id_category_default` = cl.`id_category` AND cl.`id_lang` = 4 AND cl.id_shop = 7 ) LEFT JOIN `ps_product_lang` pl ON (p.`id_product` = pl.`id_product` AND pl.`id_lang` = 4 AND pl.id_shop = 7 ) LEFT JOIN `ps_image_shop` image_shop ON (image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop=7) LEFT JOIN `ps_image_lang` il ON (image_shop.`id_image` = il.`id_image` AND il.`id_lang` = 4) LEFT JOIN `ps_manufacturer` m ON m.`id_manufacturer` = p.`id_manufacturer` WHERE product_shop.`id_shop` = 7 AND cp.`id_category` = 115 AND product_shop.`active` = 1 AND product_shop.`visibility` IN ("both", "catalog") ORDER BY cp.`position` ASC LIMIT 0,35;
+----+-------------+--------------+------------+--------+---------------------------------------------------------------------------------------------------------------------------+------------------------------+---------+---------------------------------------------------+---------+----------+-----------------------------------------------------------+
| id | select_type | table        | partitions | type   | possible_keys                                                                                                             | key                          | key_len | ref                                               | rows    | filtered | Extra                                                     |
+----+-------------+--------------+------------+--------+---------------------------------------------------------------------------------------------------------------------------+------------------------------+---------+---------------------------------------------------+---------+----------+-----------------------------------------------------------+
|  1 | SIMPLE      | cp           | NULL       | index  | ps_category_product_id_product_id_category_u,ps_category_product_id_product,ps_product_idx_id_product_id_categor_position | ps_category_product_position | 4       | NULL                                              | 3386491 |    10.00 | Using where; Using index; Using temporary; Using filesort |
|  1 | SIMPLE      | product_shop | NULL       | eq_ref | PRIMARY,ps_shop_idx_id_shop_active_visibility                                                                             | PRIMARY                      | 8       | test.cp.id_product,const                          |       1 |     5.00 | Using where                                               |
|  1 | SIMPLE      | p            | NULL       | eq_ref | PRIMARY,ps_product_idx_id_product_id_manufacturer                                                                         | PRIMARY                      | 4       | test.cp.id_product                                |       1 |   100.00 | NULL                                                      |
|  1 | SIMPLE      | stock        | NULL       | eq_ref | product_sqlstock,id_shop,id_shop_group,id_product,id_product_attribute                                                    | product_sqlstock             | 16      | test.cp.id_product,const,const,const              |       1 |   100.00 | NULL                                                      |
|  1 | SIMPLE      | cl           | NULL       | eq_ref | PRIMARY                                                                                                                   | PRIMARY                      | 12      | test.product_shop.id_category_default,const,const |       1 |   100.00 | NULL                                                      |
|  1 | SIMPLE      | pl           | NULL       | eq_ref | PRIMARY,id_lang                                                                                                           | PRIMARY                      | 12      | test.cp.id_product,const,const                    |       1 |   100.00 | NULL                                                      |
|  1 | SIMPLE      | image_shop   | NULL       | eq_ref | id_product,id_shop                                                                                                        | id_product                   | 10      | test.cp.id_product,const,const                    |       1 |   100.00 | Using index                                               |
|  1 | SIMPLE      | il           | NULL       | eq_ref | PRIMARY,id_image                                                                                                          | PRIMARY                      | 8       | test.image_shop.id_image,const                    |       1 |   100.00 | NULL                                                      |
|  1 | SIMPLE      | m            | NULL       | ALL    | PRIMARY                                                                                                                   | NULL                         | NULL    | NULL                                              |       2 |   100.00 | Using where; Using join buffer (hash join)                |
+----+-------------+--------------+------------+--------+---------------------------------------------------------------------------------------------------------------------------+------------------------------+---------+---------------------------------------------------+---------+----------+-----------------------------------------------------------+
9 rows in set, 1 warning (0.02 sec)
[20 Jan 9:35] Øystein Grøvlen
Hi Aaditya,

Thank you, for your bug report.
The main query is verified as described.

A work-around is to set optimizer_switch='block_nested_loop=off' since the use of hash join disrupts the natural sort order and requires the entire query result to be sorted.

For the other queries you mention, I fail to see that there are any better query plans.  The use of LEFT JOIN, limits possible join orders.  Do you have any indication that better query plans exist?