使用SQL Server 2008的SQL Server性能和索引視圖
(很抱歉,如果這原來是一篇文章,但我想給儘可能多的信息越好。)
我有多個位置其中每個部門都包含多個部門,每個部門都包含多個可以進行零到多次掃描的項目。每次掃描都涉及可能會或可能不會有中斷時間的特定操作。每個項目也屬於一個特定的包,屬於特定的工作,屬於特定的項目,屬於特定的客戶。每個作業都包含一個或多個包含一個或多個項目的包。
+=============+ +=============+
| Projects | --> | Clients |
+=============+ +=============+
^
|
+=============+ +=============+
| Locations | | Jobs |
+=============+ +=============+
^ ^
| |
+=============+ +=============+ +=============+
| Departments | <-- | Items | --> | Packages |
+=============+ +=============+ +=============+
^
|
+=============+ +=============+
| Scans | --> | Operations |
+=============+ +=============+
項目表中大約有24,000,000條記錄,掃描表中大約有48,000,000條記錄。新項目偶爾會全天插入到數據庫中,通常數以萬計的流行時間。新掃描每小時插入一次,每個插入的數量從幾百到幾十萬不等。
這些表格被嚴格查詢,切片和切塊每一個方式。我正在編寫非常具體的存儲過程,但它變成了維護的噩夢,因爲我處在一個沒有終點的網站中的一百個存儲過程的邊緣(例如類似於ScansGetDistinctCountByProjectIDByDepartmentIDGroupedByLocationID,ScansGetDistinctCountByPackageIDByDepartmentIDGroupedByLocationID等)。幸運的是,需求幾乎每天都會改變(感覺),每次我必須更改/添加/刪除一列,那麼......我最終在酒吧。
所以我創建了一個索引視圖和一些帶有參數的通用存儲過程,以確定過濾和分組。不幸的是,性能下降了。 我想第一個問題是,因爲選擇性能是最重要的,我應該堅持具體的方法,並通過改變基礎表來對抗嗎?或者,可以做些什麼來加速索引視圖/通用查詢方法?除了緩解維護噩夢之外,我實際上希望索引視圖能夠提高性能。
下面是生成視圖的代碼:
CREATE VIEW [ItemScans] WITH SCHEMABINDING AS
SELECT
p.ClientID
, p.ID AS [ProjectID]
, j.ID AS [JobID]
, pkg.ID AS [PackageID]
, i.ID AS [ItemID]
, s.ID AS [ScanID]
, s.DateTime
, o.Code
, o.Cutoff
, d.ID AS [DepartmentID]
, d.LocationID
-- other columns
FROM
[Projects] AS p
INNER JOIN [Jobs] AS j
ON p.ID = j.ProjectID
INNER JOIN [Packages] AS pkg
ON j.ID = pkg.JobID
INNER JOIN [Items] AS i
ON pkg.ID = i.PackageID
INNER JOIN [Scans] AS s
ON i.ID = s.ItemID
INNER JOIN [Operations] AS o
ON s.OperationID = o.ID
INNER JOIN [Departments] AS d
ON i.DepartmentID = d.ID;
和聚簇索引:
CREATE UNIQUE CLUSTERED INDEX [IDX_ItemScans] ON [ItemScans]
(
[PackageID] ASC,
[ItemID] ASC,
[ScanID] ASC
)
這裏的通用存儲的特效之一。它獲取已掃描並有一個截止的項目數:
PROCEDURE [ItemsGetFinalizedCount]
@FilterBy int = NULL
, @ID int = NULL
, @FilterBy2 int = NULL
, @ID2 sql_variant = NULL
, @GroupBy int = NULL
WITH RECOMPILE
AS
BEGIN
SELECT
CASE @GroupBy
WHEN 1 THEN
CONVERT(sql_variant, LocationID)
WHEN 2 THEN
CONVERT(sql_variant, DepartmentID)
-- other cases
END AS [ID]
, COUNT(DISTINCT ItemID) AS [COUNT]
FROM
[ItemScans] WITH (NOEXPAND)
WHERE
(@ID IS NULL OR
@ID = CASE @FilterBy
WHEN 1 THEN
ClientID
WHEN 2 THEN
ProjectID
-- other cases
END)
AND (@ID2 IS NULL OR
@ID2 = CASE @FilterBy2
WHEN 1 THEN
CONVERT(sql_variant, ClientID)
WHEN 2 THEN
CONVERT(sql_variant, ProjectID)
-- other cases
END)
AND Cutoff IS NOT NULL
GROUP BY
CASE @GroupBy
WHEN 1 THEN
CONVERT(sql_variant, LocationID)
WHEN 2 THEN
CONVERT(sql_variant, DepartmentID)
-- other cases
END
END
我第一次運行查詢,看了看實際的執行計劃,我創建的缺失索引,它建議:
CREATE NONCLUSTERED INDEX [IX_ItemScans_Counts] ON [ItemScans]
(
[Cutoff] ASC
)
INCLUDE ([ClientID],[ProjectID],[JobID],[ItemID],[SegmentID],[DepartmentID],[LocationID])
創建索引會將執行時間縮短到大約五秒鐘,但仍然不可接受(查詢的「特定」版本在亞秒級運行。)我嘗試向索引中添加不同的列,而不是僅將它們包括在內獲得成績(並沒有真正的幫助,我不知道我現在在做什麼。)
下面是查詢計劃:
這裏是爲第一指標的細節尋求(它似乎返回所有行的視圖,其中截止IS NOT NULL):
感謝您的快速響應。我不得不承認,我從來沒有考慮動態SQL。我知道這是一個時間和地點,但我仍然有「動態SQL永遠是邪惡的」在我腦海中迴盪。似乎無法動搖它。 – Frank 2013-05-01 19:20:41
帶參數的'sp_executesql'提供了很多procs的性能,並且可以避免大部分'exec(@sql)'的恐怖。我認爲「必須...使用... PROCS!」的日子很大程度上落後於我們,但不幸的是,我仍然在許多數據庫中保留了數百個數據庫。 – 2013-05-01 19:32:57
你會喜歡這個......如果你看看我的存儲過程,在聲明參數後,我有WITH RECOMPILE。如果我刪除它並在存儲過程的末尾添加OPTION(RECOMPILE),它會飛行。我沒有足夠的智慧去了解它們之間的差異,但是我很高興你發佈了這個鏈接,把我帶到了Erland Sommarskog的網站,我注意到了它的不同之處。另一個原因是,當人們指引我朝着正確的方向而不是告訴我如何做某件事時,我更喜歡它。再次感謝。 – Frank 2013-05-01 20:16:52