加快查詢執行速度。我們可以添加索引或重寫查詢嗎
我一直在處理一個想要進一步減少查詢執行時間的開發人員交給我的查詢。目前,它需要大約 40 秒並輸出 262K 記錄。根據實際執行,查詢成本更多地用於“CacheClients”上的聚集索引掃描。但是,我不確定添加非聚集覆蓋索引是否會受益。另外,我在計劃的右下角看到了一個表掃描,根據開發人員的說法,他們正在使用該表上的索引進行測試,但他們無法找出適合標準的合適的索引,這基本上是他們將加入 id 欄位並根據日期週期執行查詢。
USE [ABC] GO /****** Object: View [report].[vw_MonthlyDealAllocations] Script Date: 11/24/2017 9:54:10 PM ******/ --SET ANSI_NULLS ON --GO --SET QUOTED_IDENTIFIER ON --GO --CREATE VIEW [report].[vw_MonthlyDealAllocations] as SELECT PeriodStartDate, PeriodEndDate, D.DealID, Dl.DealName, D.StageID, CONVERT(varchar(1), D.StageID) + ' - ' + DS.Stage Stage, DP.Platform, ISNULL(D.AllocatedOfficeID, Dl.OfficeID) AS AllocatedOfficeID, ISNULL(D.AllocatedDepartmentID, Dl.DepartmentID) AS AllocatedDepartmentID, CASE WHEN ISNULL(D.AllocatedDepartmentID, Dl.DepartmentID) = 1 THEN 'Investment Advisory' WHEN ISNULL(D.AllocatedDepartmentID, Dl.DepartmentID) = 2 THEN 'Debt/EP' WHEN ISNULL(D.AllocatedDepartmentID, Dl.DepartmentID) = 6 THEN 'HFF Securities' ELSE DM.Department END AS AllocatedDepartment, AO.Name AllocatedOffice, CASE WHEN DP.DisplayWithOffice = 1 THEN AO.Name + ' ' + DP.ShortName ELSE AO.Name END AllocatedOfficeDept, Dl.DepartmentID DealDepartmentID, DX.Department DealDepartment, Dl.OfficeID DealOfficeID, DO.Name DealOffice, CASE WHEN DP.DisplayWithOffice = 1 THEN DO.Name + ' ' + DP.ShortName ELSE DO.Name END DealOfficeDept, DT.DealType, D.OfficeAllocVolumePipeline, D.OfficeAllocVolumeCompleted, D.OfficeAllocFeePipeline, D.OfficeAllocFeeCompleted, D.OfficeAllocVolumePipelineGBP, D.OfficeAllocVolumeCompletedGBP, D.OfficeAllocFeePipelineGBP, D.OfficeAllocFeeCompletedGBP, PTG.GroupName, PT.PropertyType, CASE WHEN C.CountryID <> 1 THEN 'Foreign' ELSE R.Region END AS Region, AP.Address1, C.City, S.StateLong, AP.Zip, Investor.ClientName MSA, Cl.ClientName RollupMSA, ITI.InvestorType PrimaryInvestor, ITC.InvestorType PrimaryClient, P.FirstName + ' ' + P.LastName AS PrimaryProducer, I.FirstName + ' ' + I.LastName AS IL_Producer, D.CountDealCompleted, CASE Dl.isSecuritized WHEN 0 THEN 'No' WHEN 1 THEN 'Yes' ELSE '' END AS isSecuritized, CASE Dl.isServiced WHEN 0 THEN 'No' WHEN 1 THEN 'Yes' ELSE '' END AS isServiced, --QCount.QtCt, /*IIF (CASE WHEN D.isCompleted = 1 THEN D.VolumeCompleted WHEN (D.isPipeline = 1 OR D.StageID = 0) THEN D.VolumePipeline ELSE 0 END >= 50000000,1,NULL ) AS Over50M, */ IIF(Dl.PortfolioID IS NOT NULL, 1, NULL) AS isPortfolio, --IIF (isMultitransactional.linktype IS NOT NULL,1,NULL) AS isMultiTransactional, DP.PlatformID, P.EmployeeID AS PrimaryProducerID, I.EmployeeID AS IL_ProducerID, --report.fnReportGetDealClients(D.DealID) Clients, --report.fnReportGetDealInvestors(D.DealID) Investors, DFM.FinancingMethod, IIF((Dl.isSecuritized = 1 AND ISNULL(ISNULL(ITID2C.InvestorType, ITI.InvestorType), 'Unknown') <> 'Agency'), 'Conduit/CMBS', ISNULL(ISNULL(ITID2C.InvestorType, ITI.InvestorType), 'Unknown')) AS InvestorType FROM report.MonthlyDealAllocations D INNER JOIN dbo.Deals Dl ON Dl.DealID = D.DealID INNER JOIN DealTypes DT ON DT.DealTypeID = Dl.DealTypeID INNER JOIN DealPlatforms DP ON DP.PlatformID = DT.PlatformID INNER JOIN DealStages DS ON DS.StageID = D.StageID LEFT JOIN Departments DM ON DM.DepartmentID = ISNULL(D.AllocatedDepartmentID, Dl.DepartmentID) LEFT JOIN Departments DX ON DX.DepartmentID = Dl.DepartmentID LEFT JOIN Offices AO ON AO.OfficeID = ISNULL(D.AllocatedOfficeID, Dl.OfficeID) LEFT JOIN Offices DO ON DO.OfficeID = Dl.OfficeID LEFT JOIN dbo.DealProperties DPP ON D.DealID = DPP.DealID AND DPP.IsMainProperty = 1 LEFT JOIN PropertyTypes PT ON PT.PropertyTypeID = DPP.PropertyTypeID LEFT JOIN PropertyTypeGroups PTG ON PTG.GroupID = PT.GroupID LEFT JOIN DealProperties AS DPZ ON D.DealID = DPZ.DealID AND DPZ.isMainProperty = 1 LEFT JOIN Assets AP ON DPZ.AssetID = AP.AssetID LEFT JOIN Cities C ON C.CityID = AP.CityID LEFT JOIN States S ON S.StateID = C.StateID LEFT JOIN Regions R ON ISNULL(C.RCARegionID, ISNULL(S.RCARegionID, S.RegionID)) = R.RegionID LEFT JOIN DealsToClients DTCC ON D.DealID = DTCC.DealID AND DTCC.isPrimary = 1 AND DTCC.MemberType IN (1, 3) LEFT JOIN DealsToClients DTCI ON D.DealID = DTCI.DealID AND DTCI.isPrimary = 1 AND DTCI.MemberType IN (2, 4) LEFT JOIN CacheClients Cl ON DTCC.ClientID = Cl.ClientID LEFT JOIN CacheClients Investor ON DTCI.ClientID = Investor.ClientID LEFT JOIN InvestorTypes ITI ON Investor.InvestorTypeID = ITI.InvestorTypeID LEFT JOIN InvestorTypes ITC ON Cl.InvestorTypeID = ITC.InvestorTypeID --LEFT JOIN (SELECT DISTINCT D2D.DealID, D2D.LinkType FROM DealsToDeals D2D WHERE LinkType = 2) isMultitransactional ON D.DealID = isMultitransactional.DealID LEFT JOIN DealsToEmployees EP ON D.DealID = EP.DealID AND EP.MemberType = 1 AND EP.isPrimary = 1 LEFT JOIN DealsToEmployees EI ON D.DealID = EI.DealID AND EI.MemberType = 2 AND EI.isPrimary = 1 LEFT JOIN Employees P ON EP.EmployeeID = P.EmployeeID LEFT JOIN Employees I ON EI.EmployeeID = I.EmployeeID LEFT JOIN DealFinancingMethods DFM ON Dl.DealFinancingMethodID = DFM.DealFinancingMethodsID LEFT JOIN InvestorTypes AS ITID2C ON DTCI.InvestorTypeID = ITID2C.InvestorTypeID --LEFT JOIN (SELECT DealID, COUNT(QuoteID) AS QtCt FROM Quotes WHERE DateRemoved IS NULL GROUP BY DealID) QCount ON D.dealid = Qcount.DealID go
下面是他們創建的索引。
USE [ABC] GO /****** Object: Index [IX_MonthlyDealAllocations_DealID_AllocDeptID_AllocOfficeID] Script Date: 11/24/2017 11:43:00 PM ******/ CREATE NONCLUSTERED INDEX [IX_MonthlyDealAllocations_DealID_AllocDeptID_AllocOfficeID] ON [report].[MonthlyDealAllocations] ( [DealID] ASC, [AllocatedDepartmentID] ASC, [AllocatedOfficeID] ASC ) INCLUDE ( [PeriodStartDate], [PeriodEndDate], [CountDealCompleted], [StageID]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] GO
執行計劃連結 https://www.brentozar.com/pastetheplan/?id=SJu_XK8lM
SQL 腳本文件(包括架構和索引 DDL) https://pastebin.com/BB8p9hqa
如果您使用它執行查詢
SET STATISTICS IO ON;
,將為您提供有關所有連接表所需的 IO 的資訊。根據估計的計劃成本,您將從在CacheClients
表格上添加覆蓋索引中獲得最大收益。該表有大約 50 列,但您在查詢中只使用了三列。ClientID
在、ClientName
和上創建索引InvestorTypeID
將減少該表的 IO 要求,因為 SQL Server 將能夠掃描僅包含這三列而不是整個表的索引。即使聯接類型沒有改變,您也會看到好處。除此之外,這裡沒有很多東西。您的基數估計從
MonthlyDealAllocations
永遠不會改變,因此您不應該首先加入特定表以提高性能。如果可能,更改一些預期會更改基數的連接LEFT OUTER JOINS
或在某些表上定義唯一索引可以改進您的估計,以在整個計劃中將它們保持在常數 262336。這可能對避免節點 id 61 處的 tempdb 溢出有一點好處。如果您真的很絕望,您可以嘗試通過提示
MAXDOP 1
或禁用來查看查詢執行時會發生什麼。正如您從一堆不同的表中請求數據一樣,SQL Server 將花費時間來完成所有這些連接。沒有神奇的查詢重寫會改變這一點。我建議通過定義一個可接受的最小查詢性能時間目標來解決這個問題,而不是只想在沒有具體目標的情況下讓它更快。MERGE JOINS``OPTION (LOOP JOIN, HASH JOIN)
您的查詢簡單而冗長。
i)您需要在查詢中進行較小的更正。希望您的查詢給出正確的輸出。也希望所有其他左連接都是合理的。
LEFT JOIN Departments DM ON DM.DepartmentID = ISNULL(D.AllocatedDepartmentID, Dl.DepartmentID) LEFT JOIN Departments DX ON DX.DepartmentID = Dl.DepartmentID
這可以寫成,
LEFT JOIN Departments DM ON DM.DepartmentID = D.AllocatedDepartmentID LEFT JOIN Departments DX ON DX.DepartmentID = Dl.DepartmentID LEFT JOIN Offices AO ON AO.OfficeID = ISNULL(D.AllocatedOfficeID, Dl.OfficeID) LEFT JOIN Offices DO ON DO.OfficeID = Dl.OfficeID
改寫為,
LEFT JOIN Offices AO ON AO.OfficeID = D.AllocatedOfficeID LEFT JOIN Offices DO ON DO.OfficeID = Dl.OfficeID
將此行替換為,
LEFT JOIN Cities C ON C.CityID = AP.CityID LEFT JOIN States S ON S.StateID = C.StateID LEFT JOIN Regions R ON ISNULL(C.RCARegionID, ISNULL(S.RCARegionID, S.RegionID)) = R.RegionID LEFT JOIN Cities C ON C.CityID = AP.CityID LEFT JOIN States S ON S.StateID = C.StateID LEFT JOIN Regions R ON R.RegionID=C.RCARegionID LEFT JOIN Regions R1 R1.RegionID=(S.RCARegionID and S.RCARegionID is not NULL) or (S.RegionID and S.RCARegionID is NULL))
MAXDOP :
我對 MAXDOP 了解不多。您目前的 MAXDOP 為 4。如果通過查詢 HINT 減少到 MAXDOP=2,那麼速度可能會有所提高。同時我不確定。
Index :
恕我直言,您應該在 MonthlyDealAllocations 上刪除現有的複合 NON CI,因為複合索引不能以這種方式工作。為每個單獨創建 NON CI 並保留一個覆蓋索引。為什麼 MonthlyDealAllocations 中沒有 CI。如果有任何 CI 並且它我們正在使用然後不需要覆蓋索引。每個表上也應該有超過 400 行的索引。我不確定數字。