我查看了类似的 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
我希望有人通常可以建议一种更好的方法来构建此查询,而无需重新构建数据库(如果没有数周的数据迁移和脚本更改,目前这不是一个真正的选择)?或任何关于优化的一般建议。当前使用 explain 返回以下内容(如您所见,索引无处不在!)。
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
在选择行后应用以过滤已选择的结果。 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
生成数据,以从表中选择所有产品值。
更新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
这将选择具有三个过滤属性的所有产品。
关于Mysql - 优化 - 使用 having 的多个 group_concat 和连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37413701/