2017-08-17 39 views
0

我試圖抓住每個「主角」的最新事件。我創建了索引,這個查詢仍然需要30分鐘以上。爲最近的事件組優化大型MySQL查詢(73MM行)

SELECT l.id, 
     l.home_number, 
     l.mobile_number, 
     CASE WHEN l.soldprice < 2 THEN 0 ELSE 1 END as sold, 
     l.lead_date 
FROM (
    SELECT l.home_number, MAX(l.id) as id 
    FROM lead l 
    WHERE l.lead_date >= DATE_SUB(NOW(), INTERVAL 52 WEEK) 
    AND l.state NOT IN ('NY','AR','VT','WV','GA','CT','DC','SD') 
    GROUP BY l.home_number) a 
JOIN lead l ON l.id=a.id; 

我的表索引低於:

Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_typ  
lead 0 PRIMARY  1 id   A 63123648 NULL NULL  BTREE  
lead 1 id   1 id   A 63266540 NULL NULL  BTREE  
lead 1 soldprice 1 soldprice A 14715  NULL NULL YES BTREE  
lead 1 lead_date 1 lead_date A 15351477 NULL NULL YES BTREE 

而我的表模式:

CREATE TABLE lead 
( 
    id    BIGINT unsigned NOT NULL, 
    lead_date   DATETIME NULL, 
    first_name  VARCHAR(50) NULL, 
    last_name   VARCHAR(50) NULL, 
    hashed_ssn  VARCHAR(34) NULL, 
    city    VARCHAR(50) NULL, 
    state    VARCHAR(2) NULL, 
    home_number  VARCHAR(10) NULL, 
    mobile_number  VARCHAR(10) NULL, 
    email    VARCHAR(255) NULL, 
    soldprice   DECIMAL(5,2) NULL, 
    requested_amount INT NULL, 
    time_zone   VARCHAR(5), 
    camp_id   VARCHAR(9), 
    leadtype_id  VARCHAR(3), 
    hittype_id  VARCHAR(3), 
    PRIMARY KEY  (id)       
); 

任何建議,將不勝感激。

編輯:我使用的MySQL版本5.7.19-0ubuntu0.16.04.1

+0

'home_number'和'state'之間有關係嗎?例如,「212 -...」總是紐約的電話號碼;我可能會利用這一點。 –

回答

1

文藝青年最愛的你需要一個複合(多列)索引。

專業提示:除非你知道你需要它們,否則不要創建大量的單列索引。他們在複雜的查詢中很少幫助,並且會降低插入和更新的速度。

您已經完成了使用子查詢爲要獲取的行分配id值的不錯功能。不過,想必大部分的時間進入你的子查詢,這樣的:

SELECT l.home_number, MAX(l.id) as id 
FROM lead l 
WHERE l.lead_date >= DATE_SUB(NOW(), INTERVAL 52 WEEK) 
AND l.state NOT IN ('NY','AR','VT','WV','GA','CT','DC','SD') 
GROUP BY l.home_number 

它通常是智能調試子查詢,然後將它們加入到主查詢。

首先要做的事情是:在(lead_date, home_number, id)上創建一個複合索引。然後運行這個簡化的子查詢,省略對狀態的排除。這應該很快,因爲它可以隨機訪問日期,然後使用索引來處理分組,並使用鬆散的索引掃描來獲取最大ID值。

SELECT l.home_number, MAX(l.id) as id 
FROM lead l 
WHERE l.lead_date >= DATE_SUB(NOW(), INTERVAL 52 WEEK) 
GROUP BY l.home_number 

接下來,嘗試上(lead_date, state, home_number, id)創建一個複合索引並嘗試原始查詢。如果速度相當快,就完成了。您的查詢將更快。刪除第一個複合索引。

但它可能不是,因爲MySQL並不能很好地處理大量的NOT IN子句。

在這種情況下,請保留第一個複合索引並刪除第二個索引,然後將狀態排除移動到外部查詢。

這將是這樣的:

SELECT l.id, 
     l.home_number, 
     l.mobile_number, 
     CASE WHEN l.soldprice < 2 THEN 0 ELSE 1 END as sold, 
     l.lead_date 
FROM (
    SELECT l.home_number, MAX(l.id) as id 
    FROM lead l 
    WHERE l.lead_date >= DATE_SUB(NOW(), INTERVAL 52 WEEK) 
    GROUP BY l.home_number) a 
JOIN lead l ON l.id=a.id 
WHERE l.state NOT IN ('NY','AR','VT','WV','GA','CT','DC','SD') 

這應該幫助。

http://use-the-index-luke.com/是這類工作的一個很好的參考。

+1

謝謝@O。瓊斯。我將着手製定並報告兩者的結果,以防將來遇到這種情況。 – fcol

+0

「,因爲MySQL在大量使用NOT IN子句時效果不佳。」和NULL(如果你的數據允許的話)既不是'不在'也不是'在'狀態......不是在這裏它可能很重要,但列確實允許空值... – xQbert

0

這是一個棘手的查詢來優化,因爲你有你的子查詢條件。作爲一般規則,您可以使用索引來優化某些條件,但只能使用一個範圍謂詞或GROUP BY或ORDER BY。

但是你有兩個範圍謂詞和一個GROUP BY:

  • l.lead_date >= DATE_SUB(NOW(), INTERVAL 52 WEEK)
  • l.state NOT IN ('NY','AR','VT','WV','GA','CT','DC','SD')
  • GROUP BY l.home_number

您可以使用lead_date索引來縮小行選擇。您可以使用state上的索引來縮小行選擇的範圍。或者,您可以使用索引幫助按組順序讀取查詢,並嘗試避免臨時表。 但是,您只能在給定查詢中進行以下三種優化中的一種。

然後訣竅就是選擇哪一個你會優先考慮。歸根結底,考慮到您擁有的數據分佈,每個人都能提高您的查詢效率。這取決於你的數據,這不是我們可以回答的問題。因此,您必須使用EXPLAIN測試所有三種情況,或者只需運行帶查詢的查詢以查看它有多大幫助。

通常,使用縮小到行的最小子集的範圍謂詞。然後,即使另一個範圍謂詞和GROUP BY必須在沒有索引幫助的情況下工作,他們只需要處理更小的一組行,因此總成本不會太差(希望)。

+0

謝謝@ Bill Karwin。我從前面的建議中運行了兩個測試,並且還會添加您的嘗試建議,只在子查詢中選擇一個謂詞,並將其他兩個移到外部查詢。今天晚些時候我會報告結果。 – fcol

+0

我沒有建議將謂詞移動到外部查詢。您仍然可以在子查詢中找到它們,但它們不會受到索引的幫助。 –

0

我要走出去,對數據做一些假設。

SELECT l.id, l.home_number, l.mobile_number, 
     (l.soldprice < 2) as sold, 
     l.lead_date 
    FROM 
    (
     SELECT l.home_number, MAX(l.id) as maxid 
      FROM lead l 
      GROUP BY l.home_number 
    ) a 
    JOIN lead l ON l.id = a.maxid; 
      WHERE l.lead_date >= DATE_SUB(NOW(), INTERVAL 52 WEEK) 
       AND l.state NOT IN ('NY','AR','VT', 'WV','GA','CT','DC', 'SD') 

,並有

INDEX(home_number, id) 

假設:

  • 子查詢將是相當快的,多虧了指數。
  • 優化器將在查看WHERE之前運行子查詢。 (如果失敗,將其更改爲HAVING
  • 的MAX(ID)和「在去年」被大量相關
  • 每個home_number是針對特定的state

讓我們知道這是否得到相同的結果,但更快。