簡體   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