我看过类似的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
答案 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
的数据,以从表中选择所有产品值。
正如@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)
我想如果我理解这个问题,你需要做的是:
shop_product.id
的列表我试图让你成为一个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