2010-08-25 127 views
2

我們有一張有600萬條記錄的表格,然後我們有一個SQL需要大約7分鐘來查詢結果。我認爲SQL不能再被優化。我該如何處理耗時的SQL?

查詢時間導致我們的weblogic拋出最大卡住的線程異常。

我有什麼建議來解決這個問題嗎?

以下是查詢,但對我來說很難去改變它,

SELECT * FROM table1 
WHERE trim(StudentID) IN ('354354','0') 
AND concat(concat(substr(table1.LogDate,7,10),'/'),substr(table1.LogDate,1,5)) 
     BETWEEN '2009/02/02' AND '2009/03/02' 
AND TerminalType='1' 
AND RecStatus='0' ORDER BY StudentID, LogDate DESC, LogTime 

不過,我知道這是費時使用字符串比較日期,但有人寫之前,我不能改變表結構...

LogDate被定義爲一個字符串,格式爲mm/dd/yyyy,所以我們需要對它進行子串和連接,比我們之間可以使用...和...我認爲很難優化這裏。

+4

不知道你的索引是什麼或表結構等,我們不能評論問題是否是一個SQL的問題。 – 2010-08-25 10:34:14

+4

在日期列上使用concat()和substr()對我來說沒有任何意義,並且肯定會阻止任何索引的使用。另外爲什麼你修剪()StudentID列?如果這是PK,這聽起來很奇怪。 – 2010-08-25 11:06:00

+1

你能告訴我你的桌子是怎麼樣的嗎?即什麼是列類型等? – Wolph 2010-08-25 13:13:34

回答

7

可能性是,此查詢正在執行全文件掃描,因爲您的WHERE條件不太可能利用任何索引。

LogDate是日期字段還是文本字段?如果它是日期字段,則不要執行substr和concat。只是說「LogTime在'2009-02-02'和'2009-02-03'之間或日期範圍是什麼,如果它被定義爲文本字段,你應該認真考慮重新定義它到日期字段(如果你的日期真的是文本並寫成mm/dd/yyyy,那麼您的ORDER BY ... LOGDATE DESC不會給出有用的結果,如果日期跨度超過一年。)

是否需要對StudentID進行修剪?在將數據放入數據庫之前清理數據,然後在每次檢索數據時嘗試清理數據要好得多

如果將LogDate定義爲日期並且可以修剪輸入的studentid,則創建索引在一個或兩個領域和查詢時間應該大幅下降。

或者,如果您想要一個快速而骯髒的解決方案,請在「trim(studentid)」上創建一個索引。

如果這沒有幫助,請提供有關您的表格佈局和索引的更多信息。

+0

oracle會自動確定這些「和」子句的執行順序嗎?由於RecStatus ='0'會過濾掉大部分數據 – MemoryLeak 2010-08-26 08:59:00

+0

簡答:基本上,是的。較長的回答:Oracle(或任何SQL引擎)有一個「優化器」,它構建一個「查詢計劃」,這是它將用來滿足查詢的邏輯。基本上,這意味着決定讀取和連接表的順序 - 此查詢中僅使用一個表 - 此處使用了哪些索引。它不是測試條件的順序,它能夠滿足索引的條件以及它通過記錄來讀取和保存或丟棄的條件。 (續...) – Jay 2010-08-26 14:52:01

+0

如果RecStatus測試可以消除大部分不需要的記錄,那麼您應該在RecStatus上創建一個索引。通常,每個查詢只能爲每個表使用一個索引,因此如果通常只需要某個StudentID中的某個RecStatus,則應該在(StudentID,RecStatus)上創建一個索引。 (Oracle也有位圖索引,您可以在每個查詢中使用多個位圖索引,但這會變得更加複雜。) – Jay 2010-08-26 14:53:51

2

沒有關於您正在執行什麼類型的查詢以及您是否使用索引的更多信息,很難提供任何特定信息。

但是,這裏有一些一般的提示。

  1. 確保您在經常過濾/排序的列上使用索引。
  2. 如果只是某個查詢的速度太慢,那麼也許您可以通過在數據庫更改時自動生成結果來阻止您執行該查詢。例如,而不是一個count()你通常可以保持一個計數存儲在某個地方。

嘗試通過自動調用之前您的數據trim() /而將其插入到表中刪除從查詢trim()。這樣你可以簡單地使用索引來找到StudentID

另外,date過濾器應該可以在本地數據庫中使用。如果不知道哪個數據庫可能是比較困難的,但這樣的事情或許應該工作:LogDate BETWEEN '2009-02-02' AND '2009-02-02'

如果您還對所有這些列中添加索引在一起(即StudentIDLogDateTerminalTypeRecStatusEmployeeID比它應該是快如閃電。

+0

我更新了查詢,你能給我一些建議嗎? – MemoryLeak 2010-08-25 10:53:52

+0

如果無法修剪執行插入操作的應用程序中的數據,則可以使用插入/更新觸發器強制使用TRIM修改new.StudentId。但是,Id列似乎不太可能是一個字符串。 – JulesLt 2010-08-25 16:48:51

+0

oracle會自動確定這些「和」子句的執行順序嗎? – MemoryLeak 2010-08-26 08:58:34

2

但查詢的時候引起我們的WebLogic扔最大阻塞線程例外。

如果查詢需要7分鐘,無法進行得更快,您必須停止實時運行此查詢。你可以改變你的應用程序來查詢你定期刷新的緩存結果表嗎?

作爲之前的緊急停車間隔,您可以實現一次只允許一個線程執行此查詢的鎖存器(Java)。第二個線程會立即失敗並出現錯誤(而不是整個系統關閉)。這可能不會使這個查詢的用戶感到滿意,但至少可以保護其他人。

我更新了查詢,你能給我一些建議嗎?

那些字符串操作使索引幾乎不可能。你確定至少不能擺脫「修剪」嗎?實際數據中是否真的存在冗餘空白?如果是這樣,你可以縮小隻有一個student_id,這應該會加速很多事情。

您希望在(student_id,log_date)上使用複合索引,並且希望複雜的log_date條件仍可以使用索引範圍掃描(對於給定學生ID)來解決。

3

如果你的數據庫支持它,你可能想試試materialized view

如果不是,可能需要考慮自己實現類似的東西,方法是安排一個計劃作業,該作業運行一個查詢,執行昂貴的修剪並連接並刷新結果表,以便可以針對更好的桌子,避免昂貴的東西。或者使用觸發器來維護這樣的表格。

0

與您所提供的信息很少,我的直覺是,下列條款給我們提供了線索:

 ... WHERE trim(StudentID) IN ('354354','0') 

如果你有大量與身份不明的學生(即studentID = 0)的指數記錄studentID會非常不平衡。

在600萬條記錄中,有多少個studentId = 0?

+0

另外,是學生證號碼還是文本?看看你的查詢中的引號,它似乎被定義爲文本列。如果它是一個整數,請刪除引號。 – Tim 2010-08-25 11:30:21

1

不知道你使用的是什麼數據庫,什麼是你的表結構,它很難提出任何改善,但查詢可以使用索引,提示等

在你查詢的以下部分 concat(concat(substr(table1.LogDate,7,10),'/'), substr(table1.LogDate,1,5)) BETWEEN '2009/02/02' AND '2009/02/02' 得到改善

太搞笑了。 BETWEEN'2009/02/02'和'2009/02/02' ??男人,你試圖做什麼?

你可以在這裏發佈你的表格結構嗎?

無論如何,600萬條記錄並不是什麼大事。

+0

對字符串BETWEEN +1;)我很高興看到這個代碼處理Gazillionember 2010的第7個。 – eyescream 2010-08-25 17:09:56

4
SELECT * ... WHERE trim(StudentID) IN ('354354','0') 

如果這是正常的構造,那麼你需要一個function based index。因爲沒有它,您可以強制數據庫服務器執行全表掃描。

作爲一個經驗法則,您應儘可能避免使用WHERE條款中的函數。 trim(StundentID),substr(table1.LogDate,7,10)阻止DB服務器使用任何索引或對查詢應用任何優化。嘗試儘可能使用原生數據類型,例如DATE而不是VARCHARLogDateStudentID也應該在客戶端軟件中通過例如在INSERT/UPDATE之前調整數據。

0

您的主要問題是您的查詢將所有內容視爲字符串。

如果LOGDATE是日期沒有時間組件,你想要的東西像下面

SELECT * FROM table1 
WHERE StudentID IN (:SearchStudentId,0) 
AND table1.LogDate = :SearchDate 
AND TerminalType='1' 
AND RecStatus='0' 
ORDER BY EmployeeID, LogDate DESC, LogTime 

如果LOGDATE有一個時間成分,SearchDate沒有時間分量,那麼這樣的事情。 (該0.99999將時間設定爲1秒午夜前)

SELECT * FROM table1 
WHERE StudentID IN (:SearchStudentId,:StudentId0) 
AND table1.LogDate BETWEEN :SearchDate AND :SearchDate+0.99999 
AND TerminalType='1' 
AND RecStatus='0' 
ORDER BY EmployeeID, LogDate DESC, LogTime 

注意使用綁定變量的該呼叫之間更改參數。它不會使查詢快得多,但它是'最佳實踐'。

根據您的調用語言,您可能需要添加TO_DATE等來將傳入的綁定變量轉換爲Date類型。

1

它被告知很多你的問題是在日期字段。您絕對需要將您的日期從字符串字段更改爲本機日期類型。如果這是以您確切的方式在您的應用中使用的傳統字段 - 您仍然可以創建基於功能的索引,該索引可將「字符串」日期轉換爲「日期」日期,並允許已提及的快速搜索between修改你的表格數據。

這應該會加速很多事情。

0

如果StudentID是char(通常是使用trim()原因),您可以通過填充的變量,而不是微調的領域,像這樣以獲得更好的性能(假設StudentID是char(10)):

StudentID IN (lpad('354354',10),lpad('0',10)) 

這將允許使用StudentID上的索引(如果存在)。