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 所有具有三个筛选属性的产品。
我想如果我理解了这个问题,你需要做的是:
- 查找具有正确 tag/color/size 选项的所有
shop_product.id
的列表
- 获取该产品 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
我查看了类似的 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 所有具有三个筛选属性的产品。
我想如果我理解了这个问题,你需要做的是:
- 查找具有正确 tag/color/size 选项的所有
shop_product.id
的列表 - 获取该产品 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