2013-03-20 180 views
3

我想從多個表中拉出數據,當我用ORDER BY一個日期時間字段它會在至少10秒後返回結果,但如果我做同樣的查詢沒有ORDER BY那麼它返回2秒以內的結果。ORDER BY datetime使查詢非常緩慢

這是我的當前查詢

SELECT 
ph.call_subject AS callSubject, 
ac.account_name AS accountName, 
DATE_FORMAT(ph.trigger_on, "%c/%e/%Y %h:%i %p") AS triggerOn, 
ind.name AS industry, 
cc.call_code_name AS callCode 
FROM phone_calls AS ph 
INNER JOIN accounts AS ac ON ph.account_id = ac.account_id 
INNER JOIN industries AS ind ON ind.industry_id = ac.industry_id 
INNER JOIN call_codes AS cc ON ph.call_code_id = cc.call_code_id 
WHERE ac.status = 1 AND ph.status = 1 AND ph.owner_id = 1 AND ac.do_not_call = 0 
AND ph.trigger_on BETWEEN '2012-11-19 00:00:00' AND '2013-03-19 23:59:59' 
ORDER BY ph.trigger_on ASC LIMIT 0,1000 

下列字段是所有類型INT(11)UNSIGNED

ph.account_id 
ac.account_id 
ind.industry_id 
ac.industry_id 
ph.call_code_id 
cc.call_code_id 
ph.owner_id 

以下字段的全部類型TINYINT(1)

ac.status 
ph.status 
ac.do_not_call 

該字段是日期時間類型

ph.trigger_on 

請注意,有帳戶300K記錄和phone_calls有500萬條記錄。 我能做些什麼來使ORDER BY更快?請注意,我所有的where子句字段,我所有的ON子句和ph.trigger_on都被編入索引。我使用InnoDB存儲引擎而不是MyIsam。

感謝

+4

請包括表定義,所以我們可以看到您選擇什麼類型和到位 – 2013-03-20 00:06:03

+0

請檢查我的職務索引再次爲我的一些字段類型更新它 – Jaylen 2013-03-20 00:15:19

+0

請你告訴我們解釋什麼回報? – redmoon7777 2013-03-20 04:51:23

回答

2

請試試這個:

  1. 建立在列(phone_calls.trigger_on phone_Calls.status,phone_calls索引。owner_id)稱之爲pcto
  2. 更改FROM子句:

    FROM phone_calls pH值FORCE INDEX pcto

這是理想的。如果它不起作用,那麼添加一條評論,我會給你另一種方法,它可以保證工作,併爲你提供所需的性能改進。

請注意:在查詢中的「每個」列上建立索引並沒有關係(並且確實沒有好處)。 MySQL只能使用每個表的一個索引(或者更準確地說,每個表別名)。你需要建立我們告訴你的索引。

+0

好的,哇,這個伎倆!你能告訴我你作爲選項B的第二個想法是什麼,所以我可以有另一個竅門,當在不同的陳述中再次遇到這個問題時使用?謝謝。 – Jaylen 2013-03-20 15:23:44

+0

另一種方法,只有在真正需要時才使用(而且它非常稀少,以至於它是需要的)就是用一個子選擇代替FROM子句,因此「FROM(SELECT * FROM phone_calls phs WHERE phs.status = 1和phs.owner_id = 1 ORDER BY phs.trigger_on)AS ph「。你仍然需要在我的答案中建立正確的索引。順便提一下,現在您已經構建了正確的索引,請在沒有「FORCE INDEX pcto」的情況下嘗試查詢。你可能會發現它仍然可以正常工作。只有索引是不夠的,你需要有明智的索引。 – 2013-03-20 15:48:17

+0

非常感謝你讓Ben有很多幫助:) – Jaylen 2013-03-21 05:51:32

3

如果你有5行的限制則沒有訂單查詢可以搶前5行找到您搜索的其他條件。

如果你有一個ORDER BY子句,它必須查看所有符合其他條件的行並選擇5個最低的行。

+0

我剛剛更新了我的帖子並更改了限制。爲了測試的目的,我限制了5個,但它會拉動更多的5個。那麼,如何在不失去速度因素的情況下對結果進行排序呢? – Jaylen 2013-03-20 00:18:36

+0

基本上你不能。 ORDER花費的時間是運行完整查詢的實際時間。沒有ORDER的LIMIT只是掩飾了這一點。如果您的應用程序中有可用內存,則可能會發現查詢所有沒有ORDER的行並在您的應用程序中排序會更快。請參閱臨時表上的此文檔http://dev.mysql.com/doc/refman/5.1/en/internal-temporary-tables.html。如果MySQL爲你的查詢在磁盤上創建一個臨時表,將應用程序中的所有內容放入應用程序然後將其排序。 – 2013-04-10 14:37:02

0

根據我的經驗,從SQL查詢中獲得性能的最快方法是將其簡化爲多個步驟。利用臨時表並減少每步的聯接和操作次數(吃內存,獲得速度)。請原諒我下面,我沒有使用MySQL的很長一段時間,現在可能的語法錯誤,但可以按如下方式重寫查詢:

CREATE TEMPORARY TABLE scratch1 AS (
    SELECT 
      ph.call_subject AS callSubject, 
      ac.account_name AS accountName, 
      DATE_FORMAT(ph.trigger_on, "%c/%e/%Y %h:%i %p") AS triggerOn, 
      ac.industry_id, 
      ph.call_code_id 
    FROM 
      phone_calls AS ph 
      INNER JOIN accounts AS ac ON ph.account_id = ac.account_id 
    WHERE 
      ac.status = 1 AND ph.status = 1 AND ph.owner_id = 1 AND ac.do_not_call = 0 
      AND ph.trigger_on BETWEEN '2012-11-19 00:00:00' AND '2013-03-19 23:59:59') 

ALTER TABLE scratch1 ADD industry VARCHAR(255) 
ALTER TABLE scratch1 ADD callCode VARCHAR(255) 

UPDATE scratch1 s JOIN industries ind ON ind.industry_id = s.industry_id 
SET s.industry = ind.name 

UPDATE scratch1 s JOIN call_codes cc ON cc.call_code_id = s.call_code_id 
SET s.callCode = cc.call_code_name 

CREATE TEMPORARY TABLE scratch2 AS (
    SELECT * FROM scratch1 ORDER BY triggerOn ASC) 

SELECT * FROM scratch2 LIMIT 0, 1000 
+0

我不能像這樣使用臨時表。對於一個不適用於每天由系統使用執行1000次查詢的過程來說,這是一個很好的解決方案。我從來不使用使用臨時表進行常規查詢的Web應用程序。 – Jaylen 2013-03-20 00:52:51

+0

雖然在這種情況下,我同意提出的臨時表將無濟於事,但建議他們僅對過程有用並且僅僅因爲您「從不使用[SIC]使用臨時表進行常規查詢的Web應用程序」並不意味着它們在某些情況下並不完全合理。請不要過來問有經驗的程序員的建議,然後聲稱知道比他們更多。如果你知道那麼多,你不需要在這裏問,你會! – 2013-03-20 10:56:15

+0

@CaptainPayalytic,我沒有說我比你更瞭解你!我只說過,我從來沒有看到這種情況發生在每天執行1000次的查詢上。但是,在報告和誘惑項目等其他事情上,你的想法很多。感謝您試圖幫助:) – Jaylen 2013-03-20 13:00:31

0

這是闡述Ersun的解決方案/評論。

如果沒有order by,SQL會評估查詢。在這種情況下,它是一堆連接。很有可能,你在連接字段上有索引。因此,查詢通過讀取phone_calls中的記錄,查找數據,檢查過濾條件並返回。然後它會記錄下來,等等。總的來說,它可能會讀取幾千或幾萬條記錄。

對於order by,SQL必須評估全部查詢中的記錄。它必須讀取全部的電話,因爲最後一個可能有最小值。然後進行排序並返回正確的記錄。

您可以通過讓phone_calls(status, owner_id, trigger_on)上的索引滿足where子句來加快查詢速度。

+0

你的意思是有一個二列表3列?請注意,owner_id值將根據網站中登錄的用戶標識更改。我有價值1,因爲這是我的個人ID。 – Jaylen 2013-03-20 00:55:38

+0

@Mike。 。 。我的意思是你想要一個多列索引表。 – 2013-03-20 01:02:26

+0

但我已經將它們編入索引。我有我的專欄索引中使用的每列。 – Jaylen 2013-03-20 03:16:46

0

當你在SELECT(SELECT)aka上做一個SELECT時,它實際上就像在一個臨時表上工作。下面的例子在一個主要的大表上有幾個連接。當ORDER BY在整個表查詢中時,此解決方案將查詢降低到0.2秒,而對於20秒,查詢則爲20秒。

SELECT * FROM (SELECT `cse_notes`.`notes_id`, `cse_notes`.`dateandtime`, 
    `cse_case`.`case_id`, `cse_case_notes`.`attribute` 
    FROM `cse_notes` 
    INNER JOIN `cse_case_notes` 
    ON `cse_notes`.`notes_uuid` = `cse_case_notes`.`notes_uuid` 
    INNER JOIN `cse_case` 
    ON `cse_case_notes`.`case_uuid` = `cse_case`.`case_uuid` 
    WHERE `cse_notes`.`deleted` = 'N' AND `cse_case`.`case_id` = :case_id 
    AND `cse_notes`.customer_id = :customer_id) notes 
    ORDER BY `dateandtime` DESC 

這是運行速度很慢的錯誤查詢。我認爲這很好,我不知道整個表必須在過濾開始之前進行排序。索引本身並沒有幫助。

SELECT `cse_notes`.`notes_id`, `cse_notes`.`dateandtime`, 
    `cse_case`.`case_id`, `cse_case_notes`.`attribute`  
    FROM `cse_notes`  
    INNER JOIN `cse_case_notes` ON `cse_notes`.`notes_uuid` = `cse_case_notes`.`notes_uuid`  
    INNER JOIN `cse_case` ON `cse_case_notes`.`case_uuid` = `cse_case`.`case_uuid`  
    WHERE `cse_notes`.`deleted` = 'N' 
    AND `cse_case`.`case_id` = :case_id  
    AND `cse_notes`.customer_id = :customer_id  
    ORDER BY `cse_notes`.dateandtime DESC LIMIT 0, 1000