使用 UNION 优化 MySQL 查询

Optimize a MySQL query with UNIONs

我需要 运行 一个包含多个 UNION 的长 MySQL 查询。一些子查询花费了很多时间,导致 运行 时间很长。我已经在表上创建了索引,但到 运行 仍然需要大约 15 秒。我需要将 运行 时间减少到 1 秒。

这里是查询:

SELECT p.prd_id, NULL AS manu_name, NULL AS brand_name, NULL AS Categories, NULL AS Item_Color, NULL AS Ink_Type, NULL AS Industry,
 NULL AS Size, NULL AS Decoration_Method, NULL AS Rush_Production, NULL AS Themes, NULL AS Material, NULL AS Pattern, NULL AS Country_Origin, NULL AS Ships_From, pi.prd_img_name
 FROM products p
 LEFT JOIN product_image_p pi ON p.prd_id = pi.prd_id
 WHERE p.is_deleted = 'no' 

 UNION
SELECT p.prd_id, NULL AS manu_name, NULL AS brand_name, NULL AS Categories, NULL AS Item_Color, 
 NULL AS Ink_Type, NULL AS Industry, NULL AS Size, GROUP_CONCAT(decoration.dm_name SEPARATOR ', ') AS Decoration_Method, NULL AS Rush_Production, NULL AS Themes, NULL AS Material, 
 NULL AS Pattern, NULL AS Country_Origin, NULL AS Ships_From, NULL AS prd_img_name
 FROM products p
 JOIN product_dm pdm ON pdm.prd_id = p.prd_id
 JOIN decoration_method decoration ON decoration.dm_id = pdm.dm_id
 WHERE p.is_deleted = 'no'
 group by prd_id

 UNION

SELECT p.prd_id, NULL AS manu_name, NULL AS brand_name, NULL AS Categories, NULL AS Item_Color, 
 NULL AS Ink_Type, NULL AS Industry, NULL AS Size, NULL AS Decoration_Method, GROUP_CONCAT(rush.rush_title SEPARATOR ', ') AS Rush_Production, NULL AS Themes, NULL AS Material, 
 NULL AS Pattern, NULL AS Country_Origin, NULL AS Ships_From, NULL AS prd_img_name
 FROM products p
 JOIN product_rush_title prt ON prt.prd_id = p.prd_id
 JOIN rush_title rush ON rush.rush_id = prt.rush_id
 WHERE p.is_deleted = 'no'
 group by prd_id

 UNION
SELECT p.prd_id, NULL AS manu_name, NULL AS brand_name, NULL AS Categories, GROUP_CONCAT(ao.option_name SEPARATOR ', ') AS Item_Color, 
 NULL AS Ink_Type, NULL AS Industry, NULL AS Size, NULL AS Decoration_Method, NULL AS Rush_Production, NULL AS Themes, NULL AS Material, NULL AS Pattern, 
 NULL AS Country_Origin, NULL AS Ships_From, NULL AS prd_img_name
 FROM products p
 JOIN product_attributes pa ON pa.prd_id = p.prd_id 
 JOIN attributes_options ao ON ao.attr_opt_id = pa.attr_opt_id AND ao.attr_id = 19
 WHERE p.is_deleted = 'no' 
 group by prd_id

 UNION
 SELECT p.prd_id, NULL AS manu_name, NULL AS brand_name, NULL AS Categories, NULL AS Item_Color, 
 GROUP_CONCAT(ao.option_name SEPARATOR ', ') AS Ink_Type, NULL AS Industry, NULL AS Size, NULL AS Decoration_Method, NULL AS Rush_Production, NULL AS Themes, NULL AS Material, 
 NULL AS Pattern, NULL AS Country_Origin, NULL AS Ships_From, NULL AS prd_img_name
 FROM products p
 JOIN product_attributes pa ON pa.prd_id = p.prd_id 
 JOIN attributes_options ao ON ao.attr_opt_id = pa.attr_opt_id AND ao.attr_id = 10
 WHERE p.is_deleted = 'no'
 group by prd_id

 UNION
 SELECT p.prd_id, NULL AS manu_name, NULL AS brand_name, NULL AS Categories, NULL AS Item_Color, 
 NULL AS Ink_Type, GROUP_CONCAT(ao.option_name SEPARATOR ', ') AS Industry, NULL AS Size, NULL AS Decoration_Method, NULL AS Rush_Production, NULL AS Themes, NULL AS Material, 
 NULL AS Pattern, NULL AS Country_Origin, NULL AS Ships_From, NULL AS prd_img_name
 FROM products p
 JOIN product_attributes pa ON pa.prd_id = p.prd_id 
 JOIN attributes_options ao ON ao.attr_opt_id = pa.attr_opt_id AND ao.attr_id = 18
 WHERE p.is_deleted = 'no'
 group by prd_id

 UNION
 SELECT p.prd_id, NULL AS manu_name, NULL AS brand_name, NULL AS Categories, NULL AS Item_Color, 
 NULL AS Ink_Type, NULL AS Industry, GROUP_CONCAT(ao.option_name SEPARATOR ', ') AS Size, NULL AS Decoration_Method, NULL AS Rush_Production, NULL AS Themes, NULL AS Material, 
 NULL AS Pattern, NULL AS Country_Origin, NULL AS Ships_From, NULL AS prd_img_name
 FROM products p
 JOIN product_attributes pa ON pa.prd_id = p.prd_id 
 JOIN attributes_options ao ON ao.attr_opt_id = pa.attr_opt_id AND ao.attr_id IN (1, 13, 14)
 WHERE p.is_deleted = 'no'
 group by prd_id

 UNION
 SELECT p.prd_id, NULL AS manu_name, NULL AS brand_name, NULL AS Categories, NULL AS Item_Color, 
 NULL AS Ink_Type, NULL AS Industry, NULL AS Size, NULL AS Decoration_Method, NULL AS Rush_Production, GROUP_CONCAT(ao.option_name) AS Themes,
 NULL AS Material, NULL AS Pattern, NULL AS Country_Origin, NULL AS Ships_From, NULL AS prd_img_name
 FROM products p
 JOIN product_attributes pa ON pa.prd_id = p.prd_id 
 JOIN attributes_options ao ON ao.attr_opt_id = pa.attr_opt_id AND ao.attr_id = 17
 WHERE p.is_deleted = 'no'
 group by prd_id

 UNION
 SELECT p.prd_id, NULL AS manu_name, NULL AS brand_name, NULL AS Categories, NULL AS Item_Color, 
 NULL AS Ink_Type, NULL AS Industry, NULL AS Size, NULL AS Decoration_Method, NULL AS Rush_Production, NULL AS Themes, GROUP_CONCAT(ao.option_name) AS Material, 
 NULL AS Pattern, NULL AS Country_Origin, NULL AS Ships_From, NULL AS prd_img_name
 FROM products p
 JOIN product_attributes pa ON pa.prd_id = p.prd_id 
 JOIN attributes_options ao ON ao.attr_opt_id = pa.attr_opt_id AND ao.attr_id = 12
 WHERE p.is_deleted = 'no'
 group by prd_id

 UNION
 SELECT p.prd_id, NULL AS manu_name, NULL AS brand_name, NULL AS Categories, NULL AS Item_Color, 
 NULL AS Ink_Type, NULL AS Industry, NULL AS Size, NULL AS Decoration_Method, NULL AS Rush_Production, NULL AS Themes, NULL AS Material, 
 GROUP_CONCAT(ao.option_name) AS Pattern, NULL AS Country_Origin, NULL AS Ships_From, NULL AS prd_img_name
 FROM products p
 JOIN product_attributes pa ON pa.prd_id = p.prd_id 
 JOIN attributes_options ao ON ao.attr_opt_id = pa.attr_opt_id AND ao.attr_id = 2
 WHERE p.is_deleted = 'no'
 group by prd_id

 UNION
 SELECT p.prd_id, NULL AS manu_name, NULL AS brand_name, NULL AS Categories, NULL AS Item_Color, 
 NULL AS Ink_Type, NULL AS Industry, NULL AS Size, NULL AS Decoration_Method, NULL AS Rush_Production, NULL AS Themes, NULL AS Material, 
 NULL AS Pattern, GROUP_CONCAT(ao.option_name) AS Country_Origin, NULL AS Ships_From, NULL AS prd_img_name
 FROM products p
 JOIN product_attributes pa ON pa.prd_id = p.prd_id 
 JOIN attributes_options ao ON ao.attr_opt_id = pa.attr_opt_id AND ao.attr_id = 11
 WHERE p.is_deleted = 'no'
 group by prd_id

 UNION
 SELECT p.prd_id, NULL AS manu_name, NULL AS brand_name, NULL AS Categories, NULL AS Item_Color, 
 NULL AS Ink_Type, NULL AS Industry, NULL AS Size, NULL AS Decoration_Method, NULL AS Rush_Production, NULL AS Themes, NULL AS Material, 
 NULL AS Pattern, NULL AS Country_Origin, GROUP_CONCAT(ao.option_name) AS Ships_From, NULL AS prd_img_name
 FROM products p
 JOIN product_attributes pa ON pa.prd_id = p.prd_id 
 JOIN attributes_options ao ON ao.attr_opt_id = pa.attr_opt_id AND ao.attr_id = 8
 WHERE p.is_deleted = 'no'
 group by prd_id

您遇到的问题是重复加入产品属性和产品选项并进行完整查询并为您要查找的每个组件获取一条记录 returned。此外,对于来自选项 table 的所有部分,您可以为每个产品 pre-query 一次,然后 return 每个组件作为相关 PRD_ID 的一条记录.

为了帮助优化该组件,我强烈建议(如果还没有的话)以下索引...

Table                 Index
product_attributes    ( prd_id, attr_opt_id )
attributes_options    ( attr_opt_id, attr_id, option_name )
products              ( is_deleted, prd_id )
product_dm            ( prd_id, dm_id )
decoration_method     ( dm_id, dm_name )
product_rush_title    ( prd_id, rush_id )
rush_title            ( rush_id, rush_title )

索引将是 "covering" 个索引,因此系统不必返回原始数据页来为其中的大部分准备结果

然后,要将每个部分作为内部 pre-query 执行类似...

由于装饰和抢购内容来自其他table,也可以仅根据产品ID进行预查询和汇总,然后left-joined

SELECT
      p2.prd_id,
      GROUP_CONCAT( case when ao.attr_id = 19
                        then ao.option_name else null end SEPARATOR ', ') 
          AS Item_Color,
      GROUP_CONCAT( case when ao.attr_id = 10
                        then ao.option_name else null end SEPARATOR ', ') 
          AS Ink_Type,
      GROUP_CONCAT( case when ao.attr_id = 18
                        then ao.option_name else null end SEPARATOR ', ') 
          AS Industry,
      GROUP_CONCAT( case when ao.attr_id = 17
                        then ao.option_name else null end SEPARATOR ', ') 
          AS Themes,
      GROUP_CONCAT( case when ao.attr_id = 12
                        then ao.option_name else null end SEPARATOR ', ') 
          AS Material,
      GROUP_CONCAT( case when ao.attr_id = 2
                        then ao.option_name else null end SEPARATOR ', ') 
          AS Pattern,
      GROUP_CONCAT( case when ao.attr_id = 11
                        then ao.option_name else null end SEPARATOR ', ') 
          AS Country_Origin,
      GROUP_CONCAT( case when ao.attr_id = 8
                        then ao.option_name else null end SEPARATOR ', ') 
          AS Ships_From,
      GROUP_CONCAT( case when ao.attr_id IN ( 1, 13, 14 )
                        then ao.option_name else null end SEPARATOR ', ') 
          AS Size
   from
      products p2
         JOIN product_attributes pa 
            ON p2.prd_id = pa.prd_id
            JOIN attributes_options ao 
               ON pa.attr_opt_id = ao.attr_opt_id                   
   WHERE 
      p2.is_deleted = 'no'
   group by
      p2.prd_id 

然后您可以将整个查询作为子查询滚动到您的主查询中,但使用简化的别名引用和每个 pre-group concat 字段结果

SELECT 
      p.prd_id, 
      NULL AS manu_name, 
      NULL AS brand_name, 
      NULL AS Categories, 
      preQuery.Item_Color, 
      preQuery.Ink_Type, 
      preQuery.Industry,
      preQuery.Themes, 
      preQuery.Material, 
      preQuery.Pattern, 
      preQuery.Country_Origin, 
      preQuery.Ships_From, 
      preQuery.Size, 
      tmpDeco.Decoration_Method, 
      tmpRush.Rush_Production, 
      pi.prd_img_name
   FROM 
      products p
         LEFT JOIN product_image_p pi 
            ON p.prd_id = pi.prd_id

         LEFT JOIN
         ( THE PRE-QUERY SAMPLE ABOVE ) PreQuery
            ON p.prd_id = PreQuery.prd_id

         LEFT JOIN
         ( select
                 p2.prd_id,
                 GROUP_CONCAT(decoration.dm_name SEPARATOR ', ') AS Decoration_Method 
              FROM 
                 products p2
                    JOIN product_dm pdm 
                       ON p2.prd_id = pdm.prd_id
                       JOIN decoration_method decoration ;
                          ON decoration.dm_id = pdm.dm_id
              WHERE 
                 p2.is_deleted = 'no'
              group by 
                 p2.prd_id ) as tmpDeco
            ON p.prd_id = tmpDeco.prd_id

         LEFT JOIN
         ( select
                 p2.prd_id,
                 GROUP_CONCAT(rush.rush_title SEPARATOR ', ') AS Rush_Production
              FROM 
                 products p2
                    JOIN product_rush_title prt 
                       ON p2.prd_id = prt.prd_id
                       JOIN rush_title rush 
                          ON prt.rush_id = rush.rush_id
              WHERE 
                 p2.is_deleted = 'no'
              group by 
                 p2.prd_id ) as tmpRush
            ON p.prd_id = tmpRush.prd_id

    WHERE
        p.is_deleted = 'no' 

希望您能够看到简化的上下文,即同时获取源自同一 table 的所有常见描述元素,然后在基于单个别名引用完成后加入。

我可能有一两个 type-o,但根据您的需要,我认为它是正确的。您也没有 Manu_Name、Brand_Name 类别的任何内容,因此您必须完成该组件。

FOLLOW-UP...

根据您的最新消息,我会为您的产品添加一些字段 table 以保存每个字段。然后,为产品添加 insert/update/delete 到产品属性 table 的触发器。当发生影响时,运行 只需简单地 sql-select group_concat() 在有问题的字段上并立即更新主要产品 table。是的,这在一定程度上进行了非规范化,但完全简化了执行密集查询和降低性能的需求。

看看我的回答。我正在创建这样一个类似的触发器。这样,您的主要 table 已经 pre-aggregated 并准备就绪,并且触发器应该几乎立即运行,因为您只会为单个产品执行单个字段。

部分性能问题可能是由于"over-normalization"。注意 GROUP_CONCAT(ao.option_name) 不是来自属性 table,而是通过另一个 JOIN。建议你摆脱 ao 并将 option_names 移动到 product_attributes.

(这是对@DRapp 的回答的补充,从表面上看,它似乎涵盖了查询中的许多问题。)