0

I have this query with multiple subqueries which runs quite slow.

SELECT DISTINCT pav.products_options_values_id, 
                pav.products_options_values_name, 
                pav.products_options_values_sort_order 
FROM            products_stock ps, 
                products_options_values pav, 
                ( 
                                SELECT DISTINCT pa.products_id, 
                                                pov.products_options_values_id, 
                                                pov.products_options_values_name, 
                                                pa.options_values_price, 
                                                pa.price_prefix 
                                FROM            products_attributes pa, 
                                                products_options_values pov, 
                                                ( 
                                                                SELECT DISTINCT p.products_image,
                                                                                p.products_quantity,
                                                                                p.products_status,
                                                                                m.manufacturers_id,
                                                                                p.products_id,
                                                                                p.products_date_added,
                                                                                p.products_subimage1,
                                                                                pd.products_name,
                                                                                p.products_price,
                                                                                p.products_length,
                                                                                p.products_width,
                                                                                p.products_height,
                                                                                p.products_tax_class_id,IF(s.status, s.specials_new_products_price, NULL)             AS specials_new_products_price,
                                                                                IF(s.status, s.specials_new_products_price, p.products_price)                         AS final_price,
                                                                                IF(p.clearance_price < p.products_cost*2.25, p.clearance_price, p.products_cost*2.25) AS sorting_price
                                                                FROM            products p 
                                                                LEFT JOIN       manufacturers m
                                                                using          (manufacturers_id)
                                                                LEFT JOIN       specials s 
                                                                ON              p.products_id = s.products_id
                                                                LEFT JOIN       products_attributes pa
                                                                ON              p.products_id = pa.products_id
                                                                LEFT JOIN       products_options po
                                                                ON              pa.options_id = po.products_options_id
                                                                LEFT JOIN       products_options_values pov
                                                                ON              pa.options_values_id = pov.products_options_values_id ,
                                                                                products_description pd,
                                                                                categories c,
                                                                                products_to_categories p2c
                                                                WHERE           p.products_status = '1'
                                                                AND             p.products_id = pd.products_id
                                                                AND             pd.language_id = '1'
                                                                AND             p.products_id = p2c.products_id
                                                                AND             p2c.categories_id = c.categories_id
                                                                AND             (( 
                                                                                                                pd.products_name LIKE '%a%'
                                                                                                OR              po.products_options_name LIKE '%a%'
                                                                                                OR              pov.products_options_values_name LIKE '%a%'
                                                                                                OR              pd.products_description LIKE '%a%') )
                                                                ORDER BY        p.products_id DESC) m
                                WHERE           m.products_id = pa.products_id 
                                AND             pa.options_id = 1
                                AND             pa.options_values_id = pov.products_options_values_id
                                AND             pov.language_id = '1' 
                                GROUP BY        pov.products_options_values_id 
                                ORDER BY        pov.products_options_values_sort_order ASC) q
WHERE           q.products_id = ps.products_id 
AND             ps.products_stock_attributes = concat('1-', pav.products_options_values_id) 
AND             ps.products_stock_quantity > 0 
ORDER BY        pav.products_options_values_sort_order ASC

This is EXPLAIN result: http://grabilla.com/06201-5ce9af70-1837-4bf4-b42e-1ecdda39b3b9.png

Been trying to optimize it for hours, but I probably misread the EXPLAIN information because no matter what I do, it doesn't seem to make it faster, so I am reaching out for help from the experts here.

What can be the cause of it becoming so slow and what should I do to make it fast?

Nikita 웃
  • 2,042
  • 20
  • 45
  • notice that there are 4 entries there which show that the query is doing a full table scan as the indexes are not usable. on lines 1, 2, 4 and 7 of the explain make sure that the relevant columns (from the where clauses) are indexed correctly. i.e. if it is a covering index that the columns are ordered correctly in the index. – Adam Copley Feb 01 '16 at 20:02

1 Answers1

1

First, I cleaned up the query using consistent JOIN clauses vs comma listed tables. Next, your inner-most query has an order by which apparently is not being utilized to any benefit. Order by will typically cause big delay in queries if it cant be optimized.

SELECT DISTINCT 
      pav.products_options_values_id,
      pav.products_options_values_name,
      pav.products_options_values_sort_order
   FROM
      products_stock ps
         JOIN products_options_values pav
            ON ps.products_stock_attributes = concat('1-', pav.products_options_values_id) 
            AND ps.products_stock_quantity > 0 
         JOIN ( SELECT DISTINCT 
                      pa.products_id, 
                      pov.products_options_values_id, 
                      pov.products_options_values_name, 
                      pa.options_values_price, 
                      pa.price_prefix 
                   FROM
                      products_attributes pa
                         JOIN products_options_values pov
                            ON pa.options_values_id = pov.products_options_values_id
                            AND pov.language_id = '1' 
                         JOIN ( SELECT DISTINCT 
                                      p.products_image,
                                      p.products_quantity,
                                      p.products_status,
                                      p.manufacturers_id,
                                      p.products_id,
                                      p.products_date_added,
                                      p.products_subimage1,
                                      pd.products_name,
                                      p.products_price,
                                      p.products_length,
                                      p.products_width,
                                      p.products_height,
                                      p.products_tax_class_id,
                                      IF(s.status, s.specials_new_products_price, NULL ) 
                                         AS specials_new_products_price,
                                      IF(s.status, s.specials_new_products_price, p.products_price )
                                         AS final_price,
                                      IF( p.clearance_price < p.products_cost * 2.25, p.clearance_price, p.products_cost * 2.25 ) 
                                         AS sorting_price
                                   FROM
                                      products p 
                                         JOIN products_description pd
                                            ON p.products_id = pd.products_id
                                            AND pd.language_id = '1'

THIS SECTION CAN BE REMOVED              JOIN products_to_categories p2c
                                            AND p.products_id = p2c.products_id
                                            JOIN categories c
                                               ON p2c.categories_id = c.categories_id
                                         LEFT JOIN manufacturers m
UP TO THIS LINE                              ON P.manufacturers_id = m.manufacturers_id

                                         LEFT JOIN specials s 
                                            ON p.products_id = s.products_id
                                         LEFT JOIN products_attributes pa
                                            ON p.products_id = pa.products_id
                                            LEFT JOIN products_options po
                                               ON pa.options_id = po.products_options_id
                                            LEFT JOIN products_options_values pov
                                               ON pa.options_values_id = pov.products_options_values_id,
                                   WHERE
                                          p.products_status = '1'
                                      AND (    pd.products_name LIKE '%a%'
                                            OR pd.products_description LIKE '%a%'
                                            OR po.products_options_name LIKE '%a%'
                                            OR pov.products_options_values_name LIKE '%a%' )
                                   ORDER BY
                                      p.products_id DESC) m 
                   WHERE
                          pa.products_id = m.products_id
                      AND pa.options_id = 1
                   GROUP BY
                      pov.products_options_values_id 
                   ORDER BY
                      pov.products_options_values_sort_order ASC) q
            ON ps.products_id = q.products_id 
   ORDER BY
      pav.products_options_values_sort_order ASC

You have a join to your categories table, but not pulling any values or other criteria to return columns. I have thus removed as it is unnecessary. WITH that gone, I then looked at your Products_To_Categories table AND that too has no bearing in the query other than an extra join not being used anywhere else and thus removed. The Manufacturers table is also not required as all you are getting is the manufacturer's ID, which exists on the products table, so yet ANOTHER table not required

Now on to indexes. I would look at the following indexes for your tables

table                   index
products                ( products_status, products_id )
products_description    ( products_id, language_id )
products_to_categories  ( products_id, categories_id ) 
categories              ( categories_id )
manufacturers           ( manufacturers_id )
specials                ( products_id, status, specials_new_products_price )
products_attributes     ( products_id, options_id, options_values_id )
products_options        ( products_options_id, products_options_name )
products_options_values ( products_options_values_id, products_options_values_name )

This is just a first pass at the review. Now I want to take it one additional step to possibly remove a layer of nested queries. Your INNER-MOST query does a join to "products_options_values" but ONLY for Language ID = 1. Why would you not add that column criteria to the inner query. Then add the pov.products_options_values_id and pov.products_options_values_name columns to your inner query and language ID to the inner query and you don't need to re-join to them again. They can be referenced via the "m." alias of the inner-most query. vs the rejoin.

Similarly, your inner-most query does a LEFT JOIN to the products_attributes, but outside the "m" alias query result applies a WHERE clause ONLY of options_id = 1. This doesn't make sense. Why not just add that too to the inner-most query. Eliminate the extra levels would probably help a lot as you are limiting down a pull of all records if you are only looking for things like Language ID = 1, or Options_ID = 1.

Again, this formatted query IS the same context as yours, just structured slightly different and shorter indentation to see better the context and nested requirements.

FEEDBACK FROM CHAT

select 
      PQ.*
   from
    ( SELECT STRAIGHT_JOIN DISTINCT 
          p.products_id, 
          p.products_image, 
          p.products_quantity, 
          p.products_status, 
          p.manufacturers_id, 
          p.products_date_added, 
          p.products_subimage1, 
          pd.products_name, 
          p.products_price, 
          p.products_length, 
          p.products_width, 
          p.products_height, 
          pov.products_options_values_id, 
          pov.products_options_values_name, 
          pov.language_id, 
          pov.products_options_values_sort_order, 
          p.products_tax_class_id,
          IF(s.status, s.specials_new_products_price, NULL) 
             AS specials_new_products_price, 
          IF(s.status, s.specials_new_products_price, p.products_price) 
             AS final_price, 
          IF(p.clearance_price < p.products_cost*2.25, p.clearance_price, p.products_cost*2.25) 
             AS sorting_price 
       FROM 
          products_attributes pa 
             JOIN products_options_values pov 
                ON pa.options_values_id = pov.products_options_values_id
                AND pov.language_id = '1' 
                LEFT JOIN products_options po 
                   ON pa.options_id = po.products_options_id 
             JOIN products p 
                ON pa.products_id = p.products_id
                AND p.products_status = '1' 
                JOIN products_description pd
                   ON p.products_id = pd.products_id 
                   AND pd.language_id = '1' 
                JOIN products_to_categories p2c 
                   ON p.products_id = p2c.products_id 
                   JOIN categories c
                      ON p2c.categories_id = c.categories_id 
                LEFT JOIN manufacturers m 
                   using (manufacturers_id) 
                LEFT JOIN specials s 
                   ON p.products_id = s.products_id 
       WHERE 
              pa.options_id = '1' 
          AND (  pd.products_name LIKE '%a%' 
              OR po.products_options_name LIKE '%a%' 
              OR pov.products_options_values_name LIKE '%a%' 
              OR pd.products_description LIKE '%a%'))  PQ
   order by 
      PQ.Products_id

Also, note, I removed the final "Order by" clause

DRapp
  • 47,638
  • 12
  • 72
  • 142
  • Thanks so much for the time and efforts in writing this answer. I tried the rewritten query, but its performance was a bit slower than mine. Then I started adding some additional indexes that you suggested and modified the query according to your first paragraph under the indexes part. That helped speeding up the query by around 30%. It is still quite slow tho. The main issue is actually INNER-MOST query m (http://pastebin.com/1cCWkCrJ), which by itself takes too long. This is the EXPLAIN: http://grabilla.com/06202-44284c84-8a8a-4dff-a037-3f875e640070.png I am really lost here. suggestions? – Nikita 웃 Feb 02 '16 at 11:06
  • @CreativeMind, again, not understanding the context on why you have some criteria at the outer level not being applied to the inner would be an issue. Ex: Inner query is all transactions for something, but the outer query only cares about transactions for Dec, 2015. Why query all records if looking for a specific set such as the Options_ID = 1 and Language_ID = 1. Restricting that at the inner-most level with additional column in those table indexes would help, but I don't really understand your reasoning for the super nesting. Also, did you remove the unnecessary join/tables? – DRapp Feb 02 '16 at 12:30
  • Yes, I do understand the context. Have you had a chance to look at my pastebin and the other EXPLAIN? – Nikita 웃 Feb 02 '16 at 12:48
  • Can we have a quick chat? http://chat.stackoverflow.com/rooms/102353/room-for-creativemind-and-drapp – Nikita 웃 Feb 02 '16 at 12:49
  • @CreativeMind, sorry, side-tracked... I am in that chat room now. – DRapp Feb 02 '16 at 13:20
  • thank you SO MUCH! removing the `order by` clause did boost it significantly. however we need the results to be sorted that way, which is why the `order by` is used. any way to go around it? – Nikita 웃 Feb 02 '16 at 14:51
  • @CreativeMind, you could re-add it back in and note the difference. For those other queries that have this query nested within it and they have an outer order by clause, it would be redundant. Add it in and see what it does. Also, order by the "PA." version of products_id since that is part of the index anyhow. – DRapp Feb 02 '16 at 14:54
  • when I added it, the speed dropped to what it was when we started :( – Nikita 웃 Feb 02 '16 at 14:55
  • @CreativeMind, try the revision. I wrapped the query as alias "PQ" for PreQuery and took that result and ordered it. SOMETIMES that is faster as the ordering is done based on the final set after the virtually immediate inner query. Let me know. – DRapp Feb 02 '16 at 15:04
  • yeah, I just saw this suggested here http://stackoverflow.com/questions/884661/slow-query-when-using-order-by and tried it and also tried your revised query. same slow speed. odd, isn't it? – Nikita 웃 Feb 02 '16 at 15:06
  • @CreativeMind, one last idea. I moved the "products_id" column into the FIRST position. By having the DISTINCT typically pre-forcing the order... AND the first position IS the products_id in the first position, does THAT return the results faster. Also, if it DOES, try again by keeping it in first position AND REMOVING the order by. Does it STILL return in the proper products_id order as result of inner DISTINCT query. – DRapp Feb 02 '16 at 15:20
  • The problem is that the order by clause is applied programmatically based on the user selection. It can either be products_id, price, or other criteria... – Nikita 웃 Feb 02 '16 at 15:30
  • @CreativeMind, for grins, just test the theory first, then we can work around later via email and other chat room activity. Testing the THEORY of it first. – DRapp Feb 02 '16 at 15:31
  • Ok, sure, will test it in about 30 minutes, when I'm back from dinner :) – Nikita 웃 Feb 02 '16 at 15:33
  • putting the `products_id` first does improve the speed, but very slightly. removing the `ORDER BY` is again making the query blazing fast and return the proper `products_id` list, but in ASC order, while the sort should be DESC. – Nikita 웃 Feb 02 '16 at 16:27
  • Does this accepted answer apply for my case? http://stackoverflow.com/questions/714950/mysql-performance-optimization-order-by-datetime-field – Nikita 웃 Feb 02 '16 at 16:32
  • Thanks so much for all your help and patience, mate. I have optimized it eventually by adding an order by subquery and removing the order by from the end. sooo much faster now :) You can see it in my new question and the email I sent you. :) http://stackoverflow.com/questions/35162640/why-is-mysql-select-count-so-slow-on-fast-subquery – Nikita 웃 Feb 02 '16 at 20:20