繁体   English   中英

性能不佳的Mysql子查询 - 我可以把它变成一个Join吗?

[英]Poorly performing Mysql subquery — can I turn it into a Join?

我有一个导致性能不佳的子查询问题......我认为子查询可以使用连接重写,但我很难绕过它。

查询的要点是这样的:对于给定的EmailAddress和Product的组合,我需要得到一个不是最新的ID列表....这些订单将在表格中标记为“过时”只留下给定的EmailAddress和Product组合的最新订单......(这有意义吗?)

表定义

CREATE TABLE  `sandbox`.`OrderHistoryTable` (
 `id` INT( 11 ) NOT NULL AUTO_INCREMENT ,
 `EmailAddress` VARCHAR( 100 ) NOT NULL ,
 `Product` VARCHAR( 100 ) NOT NULL ,
 `OrderDate` DATE NOT NULL ,
 `rowlastupdated` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ,
PRIMARY KEY (  `id` ) ,
KEY  `EmailAddress` (  `EmailAddress` ) ,
KEY  `Product` (  `Product` ) ,
KEY  `OrderDate` (  `OrderDate` )
) ENGINE = MYISAM DEFAULT CHARSET = latin1;

询问

SELECT id
FROM
OrderHistoryTable AS EMP1
WHERE
OrderDate not in 
   (
   Select max(OrderDate)
   FROM OrderHistoryTable AS EMP2
   WHERE 
       EMP1.EmailAddress =  EMP2.EmailAddress
   AND EMP1.Product IN ('ProductA','ProductB','ProductC','ProductD')
   AND EMP2.Product IN ('ProductA','ProductB','ProductC','ProductD')
   )

重复'IN'语句的解释

13   bob@aol.com  ProductA  2010-10-01
15   bob@aol.com  ProductB  2010-20-02
46   bob@aol.com  ProductD  2010-20-03
57   bob@aol.com  ProductC  2010-20-04
158  bob@aol.com  ProductE  2010-20-05
206  bob@aol.com  ProductB  2010-20-06
501  bob@aol.com  ProductZ  2010-20-07

我的查询结果应为| 13 | | 15 | | 46 | | 57 |

这是因为,在列出的订单中,这4个已被同一类别的产品的新订单“取代”。 该“类别”包含产品A,B,C和D.

订单ID 158和501基于查询在其各自的类别中不显示其他订单。

最终查询基于以下接受的答案:我最终使用了以下查询而没有子查询,并且获得了大约3倍的性能(从90秒下降30秒)。 我现在还有一个单独的“组”表,我可以枚举组成员,而不是在查询本身中拼写出来...

SELECT DISTINCT id, EmailAddress FROM (
  SELECT a.id, a.EmailAddress, a.OrderDate
  FROM OrderHistoryTable a
  INNER JOIN OrderHistoryTable b ON a.EmailAddress = b.EmailAddress
  INNER JOIN groups g1  ON  a.Product = g1.Product 
  INNER JOIN groups g2  ON  b.Product = g2.Product 
  WHERE 
        g1.family = 'ProductGroupX'
    AND g2.family = 'ProductGroupX'
  GROUP BY a.id, a.OrderDate, b.OrderDate
  HAVING  a.OrderDate < MAX(b.OrderDate)
) dtX

采用:

   SELECT a.id
     FROM ORDERHISTORYTABLE AS a
LEFT JOIN (SELECT e.EmailAddress,
                  e.product,
                  MAX(OrderDate) AS max_date
             FROM OrderHistoryTable AS e
            WHERE e.Product IN ('ProductA','ProductB','ProductC','ProductD')
         GROUP BY e.EmailAddress) b ON b.emailaddress = a.emailaddress
                                   AND b.max_date = a.orderdate
                                   AND b.product = a.product
    WHERE x.emailaddress IS NULL
      AND a.Product IN ('ProductA','ProductB','ProductC','ProductD')

Rant: OMG小马的答案给出了你要求的东西 - 用连接重写。 但我不会太兴奋,你的性能杀手是电子邮件地址的内部联接,我认为,这不是特别选择性的,然后你的数据库需要筛选所有那些寻找订单日期最大值的行。

这对于MySQL来说实际上意味着要做一个文件排序(你可以发布EXPLAIN SELECT ....?)。

现在,如果mysql可以访问包含emailaddressproductorderdate的索引,特别是在MyISAM上可以更有效地确定MAX(orderdate)(并且不会,每个列上的索引都不同于在所有列上都有一个复合索引。 如果我试图优化该查询,我会打赌。

除了这个咆哮之外,我的版本not the latest from a category版本(我不认为它会更好,但它是不同的,你应该测试性能;它可能因为缺少子查询而更快)

我的尝试 (未经测试)

SELECT DISTINCT
    notlatest.id, 
    notlatest.emailaddress, 
    notlatest.product, 
    notlatest.orderdate
FROM
    OrderHistoryTable AS notlatest
    LEFT JOIN OrderHistoryTable AS EMP latest ON 
        notlatest.emailaddress = latest.emailaddress AND
        notlatest.orderdate < latest.orderdate AND
WHERE
    notlatest.product IN ('ProductA','ProductB','ProductC','ProductD') AND
    latest.product IN ('ProductA','ProductB','ProductC','ProductD') AND
    latest.id IS NOT NULL

评论:
- 如果类别中只有一条记录,则不会显示
- 再次索引应该加快上述速度

实际上,这可能是(可能)一个很好的例子,说明数据标准化将如何提高性能 - 您的产品意味着产品类别,但产品类别不会存储在任何地方,从长远来看IN测试将无法维护。

此外,通过创建产品类别,您可以直接在其上编制索引

如果产品在类别上编入索引,那么类别上的联接性能应该更好,然后对按值(而不是类别)索引的产品进行测试。 (实际上MyISAM的emailaddresscategoryorderdate的综合索引应该已经包含每个类别的最大,最小和计数,这应该是便宜的)。

我的MySQL有点生疏(我已经习惯了MSSQL),但这是我最好的猜测。 它可能需要在GROUP BYHAVING子句中进行一些调整。 此外,我从您的重复IN语句中假设您希望产品在两个表中都匹配。 如果不是这种情况,我会调整查询。

SELECT a.id
FROM OrderHistoryTable a
INNER JOIN OrderHistoryTable b
    ON a.Product = b.Product AND
       a.Employee = b.Employee
WHERE a.Product IN ('ProductA','ProductB','ProductC','ProductD')
GROUP BY a.id, a.OrderDate, b.OrderDate, 
HAVING b.OrderDate < MAX(a.OrderDate)

编辑:删除无关的AND

SELECT  *
FROM    (
        SELECT  product, MAX(OrderDate) AS md
        FROM    OrderHistoryTable
        WHERE   product IN ('ProductA','ProductB','ProductC','ProductD')
        GROUP BY
                product
        ) ohti
JOIN    orderhistorytable oht
ON      oht.product = ohti.product
        AND oht.orderdate <> ohti.md

OrderHistoryTable (product, orderdate)上创建一个索引OrderHistoryTable (product, orderdate)以便快速工作。

另请注意,如果有的话,它将返回产品中MAX(orderdate)重复项。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM