Query works too slow when there is no results. How

2019-09-09 02:47发布

问题:

I have three tables

filters (id, name)
items(item_id, name)
items_filters(item_id, filter_id, value_id)
values(id, filter_id, filter_value)

about 20000 entries in items. about 80000 entries in items_filters.

SELECT i.*
    FROM items_filters itf INNER JOIN items i ON i.item_id = itf.item_id
     WHERE (itf.filter_id = 1 AND itf.value_id = '1') 
     OR (itf.filter_id = 2 AND itf.value_id = '7') 
    GROUP BY itf.item_id 
    WITH ROLLUP 
    HAVING COUNT(*) = 2
   LIMIT 0,10;

It 0.008 time when there is entries that match query and 0.05 when no entries match.

I tried different variations before:

SELECT * FROM items WHERE item_id IN (
    SELECT `item_id` 
     FROM `items_filters` 
     WHERE (`filter_id`='1' AND `value_id`=1) 
     OR (`filter_id`='2' AND `value_id`=7)
    GROUP BY `item_id` 
    HAVING COUNT(*) = 2
   ) LIMIT 0,6;

This completely freezes mysql when there are no entries.

What I really don't get is that SELECT i.* FROM items_filters itf INNER JOIN items i ON i.item_id = itf.item_id WHERE itf.filter_id = 1 AND itf.value_id = '1' LIMIT 0,1 takes ~0.05 when no entries found and ~0.008 when there are

Explain

| id | select_type | table | type | possible_keys | key     | key_len | ref                 | rows | Extra                           |
|  1 | SIMPLE      | i     | ALL  | PRIMARY       | NULL    | NULL    | NULL                |   10 | Using temporary; Using filesort |
|  1 | SIMPLE      | itf   | ref  | item_id       | item_id | 4       | ss_stylet.i.item_id |    1 | Using where; Using index        |

回答1:

Aside from ensuring and index on items_filters on both (filter_id, value_id), I would prequalify your item IDs up front with a group by, THEN join to the items table. It looks like you are trying to find an item that meets two specific conditions, and for those, grab the items...

I've also left the "group by with rollup" in the outer, even though there will be a single instance per ID returned from the inner query. But since the inner query is already applying the limit of 0,10 records, its not throwing too many results to be joined to your items table.

However, since you are not doing any aggregates, I believe the outer group by and rollup are not really going to provide you any benefit and could otherwise be removed.

SELECT i.*
   FROM
      ( select itf.item_id
           from items_filters itf
          WHERE (itf.filter_id = 1 AND itf.value_id = '1') 
             OR (itf.filter_id = 2 AND itf.value_id = '7') 
          GROUP BY itf.item_id 
          HAVING COUNT(*) = 2
          LIMIT 0, 10 ) PreQualified
      JOIN items i 
         ON  PreQualified.item_id = i.item_id

Another approach MIGHT be to do a JOIN on the inner query so you don't even need to apply a group by and having. Since you are explicitly looking for exactly two items, I would then try the following. This way, the first qualifier is it MUST have an entry of the ID = 1 and value = '1'. It it doesn't even hit THAT entry, it would never CARE about the second. Then, by applying a join to the same table (aliased itf2), it has to find on that same ID -- AND the conditions for the second (id = 2 value = '7'). This basically forces a look almost like a single pass against the one entry FIRST and foremost before CONSIDERING anything else. That would STILL result in your limited set of 10 before getting item details.

SELECT i.*
   FROM
      ( select itf.item_id
           from items_filters itf
              join items_filters itf2
                 on itf.item_id = itf2.item_id
                AND itf2.filter_id = 2 
                AND itf2.value_id = '7'
          WHERE 
             itf.filter_id = 1 AND itf.value_id = '1'
          LIMIT 0, 10 ) PreQualified
      JOIN items i 
         ON  PreQualified.item_id = i.item_id

I also removed the group by / with rollup as per your comment of duplicates (which is what I expected).



回答2:

That looks like four tables to me.

Do an EXPLAIN PLAN on the query and look for a TABLE SCAN. If you see one, add indexes on the columns in the WHERE clauses. Those will certainly help.