Mysql - 优化 - 多个group_concat&加入使用

时间:2016-05-24 12:33:11

标签: mysql optimization group-concat having

我看过类似的group_concat mysql优化线程,但似乎没有一个与我的问题相关,而且我的mysql知识正在被这个扩展。

我的任务是提高脚本的速度,其中包含极其繁重的Mysql查询。

有问题的查询使用GROUP_CONCAT创建一个与特定产品相关的颜色,标签和尺寸列表。然后,它使用HAVING / FIND_IN_SET过滤这些连接列表以查找由用户控件设置的属性并显示结果。

在下面的示例中,它正在查找product_tag = 1,product_colour = 18和product_size = 17的所有产品。因此,对于男性(标签),这可能是中等(大小)的蓝色产品(颜色)。

shop_products表包含大约3500行,因此不是特别大,但下面大约需要30秒才能执行。它可以在1或2个连接中正常工作,但在第三个连接中添加它只会杀死它。

SELECT shop_products.id, shop_products.name, shop_products.default_image_id, 
GROUP_CONCAT( DISTINCT shop_product_to_colours.colour_id ) AS product_colours, 
GROUP_CONCAT( DISTINCT shop_products_to_tag.tag_id ) AS product_tags, 
GROUP_CONCAT( DISTINCT shop_product_colour_to_sizes.tag_id ) AS product_sizes
FROM shop_products
LEFT JOIN shop_product_to_colours ON shop_products.id = shop_product_to_colours.product_id
LEFT JOIN shop_products_to_tag ON shop_products.id = shop_products_to_tag.product_id
LEFT JOIN shop_product_colour_to_sizes ON shop_products.id = shop_product_colour_to_sizes.product_id
WHERE shop_products.category_id =  '50'
GROUP BY shop_products.id
HAVING((FIND_IN_SET( 1, product_tags ) >0) 
AND(FIND_IN_SET( 18, product_colours ) >0)
AND(FIND_IN_SET( 17, product_sizes ) >0))
ORDER BY shop_products.name ASC 
LIMIT 0 , 30

我希望有人一般可以建议更好的方法来构建这个查询而不重新构建数据库(在没有数周的数据迁移和脚本更改的情况下,此时这不是一个真正的选项)?或任何关于优化的一般建议。使用说明目前返回以下内容(正如您所看到的那样,索引到处都是!)。

id  select_type table                          type possible_keys                         key           key_len ref rows            Extra   
1   SIMPLE      shop_products                  ref  category_id,category_id_2             category_id   2   const   3225    Using where; Using temporary; Using filesort
1   SIMPLE      shop_product_to_colours        ref  product_id,product_id_2,product_id_3  product_id    4   candymix_db.shop_products.id    13  
1   SIMPLE      shop_products_to_tag           ref  product_id,product_id_2               product_id    4   candymix_db.shop_products.id    4   
1   SIMPLE      shop_product_colour_to_sizes   ref  product_id                            product_id    4   candymix_db.shop_products.id    133 

3 个答案:

答案 0 :(得分:3)

重写查询以使用WHERE而不是HAVING。因为当MySQL对行执行搜索时会应用WHERE,并且它可以使用索引。选择行后应用HAVING来过滤已选择的结果。设计HAVING不能使用索引 你可以这样做,例如,这样:

SELECT p.id, p.name, p.default_image_id, 
    GROUP_CONCAT( DISTINCT pc.colour_id ) AS product_colours, 
    GROUP_CONCAT( DISTINCT pt.tag_id ) AS product_tags, 
    GROUP_CONCAT( DISTINCT ps.tag_id ) AS product_sizes
FROM shop_products p
    JOIN shop_product_to_colours pc_test ON p.id = pc_test.product_id AND pc_test.colour_id = 18
    JOIN shop_products_to_tag pt_test ON p.id = pt_test.product_id AND pt_test.tag_id = 1
    JOIN shop_product_colour_to_sizes ps_test ON p.id = ps_test.product_id AND ps_test.tag_id = 17
    JOIN shop_product_to_colours pc ON p.id = pc.product_id
    JOIN shop_products_to_tag pt ON p.id = pt.product_id
    JOIN shop_product_colour_to_sizes ps ON p.id = ps.product_id
WHERE p.category_id =  '50'
GROUP BY p.id
ORDER BY p.name ASC

更新

我们两次加入每张桌子 首先检查它是否包含某些值(来自FIND_IN_SET的条件) 第二次加入将生成GROUP_CONCAT的数据,以从表中选择所有产品值。

更新2

正如@Matt Raines评论的那样,如果我们不需要列出GROUP_CONCAT的列表产品值,那么查询就变得更加简单了:

SELECT p.id, p.name, p.default_image_id
FROM shop_products p
    JOIN shop_product_to_colours pc ON p.id = pc.product_id
    JOIN shop_products_to_tag pt ON p.id = pt.product_id
    JOIN shop_product_colour_to_sizes ps ON p.id = ps.product_id
WHERE p.category_id =  '50'
    AND (pc.colour_id = 18 AND pt.tag_id = 1 AND ps.tag_id = 17)
GROUP BY p.id
ORDER BY p.name ASC

这将选择具有三个过滤属性的所有产品。

答案 1 :(得分:0)

我想如果我理解这个问题,你需要做的是:

  1. 查找具有正确标记/颜色/尺寸选项的所有shop_product.id的列表
  2. 获取该产品ID可用的所有标签/颜色/尺寸组合的列表。
  3. 我试图让你成为一个SQLFiddle,但该网站目前似乎已被打破。尝试类似:

    SELECT shop_products.id, shop_products.name, shop_products.default_image_id, 
    GROUP_CONCAT( DISTINCT shop_product_to_colours.colour_id ) AS product_colours, 
    GROUP_CONCAT( DISTINCT shop_products_to_tag.tag_id ) AS product_tags, 
    GROUP_CONCAT( DISTINCT shop_product_colour_to_sizes.tag_id ) AS product_sizes
    FROM 
    shop_products INNER JOIN
    (SELECT shop_products.id id, 
     FROM
     shop_products
     LEFT JOIN shop_product_to_colours ON shop_products.id = shop_product_to_colours.product_id
     LEFT JOIN shop_products_to_tag ON shop_products.id = shop_products_to_tag.product_id
     LEFT JOIN shop_product_colour_to_sizes ON shop_products.id = shop_product_colour_to_sizes.product_id
     WHERE
     shop_products.category_id =  '50'
     shop_products_to_tag.tag_id=1
     shop_product_to_colours.colour_id=18
     shop_product_colour_to_sizes.tag_id=17
    ) matches ON shop_products.id = matches.id
    LEFT JOIN shop_product_to_colours ON shop_products.id = shop_product_to_colours.product_id
    LEFT JOIN shop_products_to_tag ON shop_products.id = shop_products_to_tag.product_id
    LEFT JOIN shop_product_colour_to_sizes ON shop_products.id = shop_product_colour_to_sizes.product_id
    GROUP BY shop_products.id
    ORDER BY shop_products.name ASC 
    LIMIT 0 , 30;
    

    第一种方法的问题是它需要数据库创建每个产品的每个组合然后过滤。在我的示例中,我首先过滤产品ID,然后生成组合。

    我的查询未经测试,因为我没有方便的MySQL环境而SQLFiddle已关闭,但它应该会给你这个想法。

答案 2 :(得分:0)

首先,我将您的查询别名以缩短可读性。

SP = Shop_Products
PC = Shop_Products_To_Colours
PT = Shop_Products_To_Tag
PS = Shop_Products_To_Sizes

接下来,你应该是一个WHERE,因为你明确地寻找一些东西。在返回结果后,无需尝试查询整个系统只是为了抛出记录。第三,你有LEFT-JOIN,但是当适用于WHERE或HAVING,并且你不允许NULL时,它强制TO JOIN(两个部分都需要)。最后,您的WHERE子句在您要查找的ID周围有引号,但无论如何这可能是整数。删除引号。

现在,对于那里的索引和优化。为了帮助处理条件,分组和JOIN,我将使用以下复合索引(多个字段),而不是仅使用单个列作为索引的表。

table                     index
Shop_Products             ( category_id, id, name )
Shop_Products_To_Colours  ( product_id, colour_id )
Shop_Products_To_Tag      ( product_id, tag_id )
Shop_Products_To_Sizes    ( product_id, tag_id )

修订查询

SELECT 
      SP.id, 
      SP.name, 
      SP.default_image_id, 
      GROUP_CONCAT( DISTINCT PC.colour_id ) AS product_colours, 
      GROUP_CONCAT( DISTINCT PT.tag_id ) AS product_tags, 
      GROUP_CONCAT( DISTINCT PS.tag_id ) AS product_sizes
   FROM 
      shop_products SP
         JOIN shop_product_to_colours PC
            ON SP.id = PC.product_id
           AND PC.colour_id = 18
         JOIN shop_products_to_tag PT
            ON SP.id = PT.product_id
           AND PT.tag_id = 1
         JOIN shop_product_colour_to_sizes PS
            ON SP.id = PS.product_id
           AND PS.tag_id = 17
   WHERE 
      SP.category_id = 50
   GROUP BY 
      SP.id
   ORDER BY 
      SP.name ASC 
   LIMIT 
      0 , 30

最后一条评论。由于您按NAME排序,但按ID分组,可能会导致最终排序延迟。但是,如果您通过NAME PLUS ID将其更改为分组,则ID仍然是唯一的,但是您的Shop_Products上的调整后的索引

table                     index
Shop_Products             ( category_id, name, id )

将有助于组AND订单,因为它们将从索引开始按自然顺序排列。

   GROUP BY 
      SP.name,
      SP.id
   ORDER BY 
      SP.name ASC,
      SP.ID