在本文中,將講述四項技術,這些技術可用于提高基于 SQL Server 的性能或改善其可伸縮性,將仔細說明 LEFT JOIN、CROSS JOIN 的使用以及 IDENTITY 值的檢索。請記住,根本沒有神奇的解決方案。調(diào)整您的數(shù)據(jù)庫及其查詢需要占用時間、進行分析,還需要大量的測試。這些技術都已被證明行之有效,但對您的應用程序而言,可能其中一些技術比另一些技術更適用。
某些時候,查詢需要將數(shù)據(jù)與其他一些可能只能通過執(zhí)行 GROUP BY 然后執(zhí)行標準查詢才能收集的數(shù)據(jù)進行聯(lián)接。例如,如果要查詢最新五個定單的有關信息,您首先需要知道是哪些定單。這可以使用返回定單 ID 的 SQL 查詢來檢索。此數(shù)據(jù)就會存儲在臨時表(這是一個常用技術)中,然后與 Products 表進行聯(lián)接,以返回這些定單售出的產(chǎn)品數(shù)量:
CREATE TABLE #Temp1 (OrderID INT NOT NULL, _ OrderDate DATETIME NOT NULL) INSERT INTO #Temp1 (OrderID, OrderDate) SELECT TOP 5 o.OrderID, o.OrderDate FROM Orders o ORDER BY o.OrderDate DESC SELECT p.ProductName, SUM(od.Quantity) AS ProductQuantity FROM #Temp1 t INNER JOIN [Order Details] od ON t.OrderID = od.OrderID INNER JOIN Products p ON od.ProductID = p.ProductID GROUP BY p.ProductName ORDER BY p.ProductName DROP TABLE #Temp1
SELECT p.ProductName, SUM(od.Quantity) AS ProductQuantity FROM ( SELECT TOP 5 o.OrderID, o.OrderDate FROM Orders o ORDER BY o.OrderDate DESC ) t INNER JOIN [Order Details] od ON t.OrderID = od.OrderID INNER JOIN Products p ON od.ProductID = p.ProductID GROUP BY p.ProductName ORDER BY p.ProductName
當然,有很多時候您需要執(zhí)行 LEFT JOIN 和使用 NULL 值。但是,它們并不適用于所有情況。改變 SQL 查詢的構建方式可能會產(chǎn)生將一個花幾分鐘運行的報告縮短到只花幾秒鐘這樣的天壤之別的效果。有時,必須在查詢中調(diào)整數(shù)據(jù)的形態(tài),使之適應應用程序所要求的顯示方式。雖然 TABLE 數(shù)據(jù)類型會減少大量占用資源的情況,但在查詢中還有許多區(qū)域可以進行優(yōu)化。SQL 的一個有價值的常用功能是 LEFT JOIN。它可以用于檢索第一個表中的所有行、第二個表中所有匹配的行、以及第二個表中與第一個表不匹配的所有行。例如,如果希望返回每個客戶及其定單,使用 LEFT JOIN 則可以顯示有定單和沒有定單的客戶。 此工具可能會被過度使用。LEFT JOIN 消耗的資源非常之多,因為它們包含與 NULL(不存在)數(shù)據(jù)匹配的數(shù)據(jù)。在某些情況下,這是不可避免的,但是代價可能非常高。LEFT JOIN 比 INNER JOIN 消耗資源更多,所以如果您可以重新編寫查詢以使得該查詢不使用任何 LEFT JOIN,則會得到非??捎^的回報。
加快使用 LEFT JOIN 的查詢速度的一項技術涉及創(chuàng)建一個 TABLE 數(shù)據(jù)類型,插入第一個表(LEFT JOIN 左側的表)中的所有行,然后使用第二個表中的值更新 TABLE 數(shù)據(jù)類型。此技術是一個兩步的過程,但與標準的 LEFT JOIN 相比,可以節(jié)省大量時間。一個很好的規(guī)則是嘗試各種不同的技術并記錄每種技術所需的時間,直到獲得用于您的應用程序的執(zhí)行性能最佳的查詢。
測試查詢的速度時,有必要多次運行此查詢,然后取一個平均值。因為查詢(或存儲過程)可能會存儲在 SQL Server 內(nèi)存中的過程緩存中,因此第一次嘗試耗費的時間好像稍長一些,而所有后續(xù)嘗試耗費的時間都較短。另外,運行您的查詢時,可能正在針對相同的表運行其他查詢。當其他查詢鎖定和解鎖這些表時,可能會導致您的查詢要排隊等待。例如,如果您進行查詢時某人正在更新此表中的數(shù)據(jù),則在更新提交時您的查詢可能需要耗費更長時間來執(zhí)行。
避免使用 LEFT JOIN 時速度降低的最簡單方法是盡可能多地圍繞它們設計數(shù)據(jù)庫。例如,假設某一產(chǎn)品可能具有類別也可能沒有類別。如果 Products 表存儲了其類別的 ID,而沒有用于某個特定產(chǎn)品的類別,則您可以在字段中存儲 NULL 值。然后您必須執(zhí)行 LEFT JOIN 來獲取所有產(chǎn)品及其類別。您可以創(chuàng)建一個值為“No Category”的類別,從而指定外鍵關系不允許 NULL 值。通過執(zhí)行上述操作,現(xiàn)在您就可以使用 INNER JOIN 檢索所有產(chǎn)品及其類別了。雖然這看起來好像是一個帶有多余數(shù)據(jù)的變通方法,但可能是一個很有價值的技術,因為它可以消除 SQL 批處理語句中消耗資源較多的 LEFT JOIN。在數(shù)據(jù)庫中全部使用此概念可以為您節(jié)省大量的處理時間。請記住,對于您的用戶而言,即使幾秒鐘的時間也非常重要,因為當您有許多用戶正在訪問同一個聯(lián)機數(shù)據(jù)庫應用程序時,這幾秒鐘實際上的意義會非常重大。
如果由于笛卡爾乘積占用的資源可能會很多,而不需要真正的笛卡爾乘積,則可以謹慎地使用 CROSS JOIN。例如,如果對產(chǎn)品和類別執(zhí)行了 CROSS JOIN,然后使用 WHERE 子句、DISTINCT 或 GROUP BY 來篩選出大多數(shù)行,那么使用 INNER JOIN 會獲得同樣的結果,而且效率高得多。如果需要為所有的可能性都返回數(shù)據(jù)(例如在您希望使用每月銷售日期填充一個圖表時),則笛卡爾乘積可能會非常有幫助。但是,您不應該將它們用于其他用途,因為在大多數(shù)方案中 INNER JOIN 的效率要高得多。
拾遺補零
這里介紹其他一些可幫助提高 SQL 查詢效率的常用技術。假設您將按區(qū)域對所有銷售人員進行分組并將他們的銷售額進行小計,但是您只想要那些數(shù)據(jù)庫中標記為處于活動狀態(tài)的銷售人員。您可以按區(qū)域對銷售人員分組,并使用 HAVING 子句消除那些未處于活動狀態(tài)的銷售人員,也可以在 WHERE 子句中執(zhí)行此操作。在 WHERE 子句中執(zhí)行此操作會減少需要分組的行數(shù),所以比在 HAVING 子句中執(zhí)行此操作效率更高。HAVING 子句中基于行的條件的篩選會強制查詢對那些在 WHERE 子句中會被去除的數(shù)據(jù)進行分組。 另一個提高效率的技巧是使用 DISTINCT 關鍵字查找數(shù)據(jù)行的單獨報表,來代替使用 GROUP BY 子句。在這種情況下,使用 DISTINCT 關鍵字的 SQL 效率更高。請在需要計算聚合函數(shù)(SUM、COUNT、MAX 等)的情況下再使用 GROUP BY。另外,如果您的查詢總是自己返回一個唯一的行,則不要使用 DISTINCT 關鍵字。在這種情況下,DISTINCT 關鍵字只會增加系統(tǒng)開銷。
您已經(jīng)看到了,有大量技術都可用于優(yōu)化查詢和實現(xiàn)特定的業(yè)務規(guī)則,技巧就是進行一些嘗試,然后比較它們的性能。最重要的是要測試、測試、再測試。在此專欄的將來各期內(nèi)容中,我將繼續(xù)深入講述 SQL Server 概念,包括數(shù)據(jù)庫設計、好的索引實踐以及 SQL Server 安全范例。