Mysql - 优化 - 多个 group_concat 并使用 having 连接

Mysql - optimisation - multiple group_concat & joins using having

我查看了类似的 group_concat mysql 优化主题,但 none 似乎与我的问题相关,并且我的 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

我希望有人通常可以建议一种更好的方法来构建此查询,而无需重新构建数据库(如果没有数周的数据迁移和脚本更改,目前这不是一个真正的选择)?或任何关于优化的一般建议。当前使用解释 returns 下面(如您所见,索引无处不在!)。

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 

重写查询以使用 WHERE 而不是 HAVING。因为WHERE是在MySQL对行进行搜索时应用的,它可以使用索引。 HAVING 在行 selected 之后应用,以过滤已经 selected 的结果。 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

更新

我们每个 table 加入两次。
首先检查它是否包含一些值(来自 FIND_IN_SET 的条件)。
第二次联接将为 GROUP_CONCAT 到 select 的所有产品值生成数据 table.

更新 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

这将 select 所有具有三个筛选属性的产品。

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

  1. 查找具有正确 tag/color/size 选项的所有 shop_product.id 的列表
  2. 获取该产品 ID 可用的所有 tag/color/size 组合的列表。

我正试图为此制作一个 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 已关闭,但它应该能给您思路。

首先,我为您的查询设置了别名以缩短可读性。

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

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

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

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

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

table                     index
Shop_Products             ( category_id, name, id )

将有助于组和顺序,因为它们将按索引的自然顺序排列。

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