優化查詢以使用多個連接和過濾器隨機檢索記錄
我有以下架構:
這個問題也發佈在 StackOverflow 上,但我也想諮詢更專注於數據庫管理的專家,因為我的項目的性質。抱歉,如果這是一個錯誤
目前,該表
Property
擁有超過 70K 條記錄。我正在開發一個更新以支持超過 500 個並發會話。該應用程序將支持地圖 a 進行搜尋,這就是GeoLocation
聲明Coordinate
為geography
數據類型的原因。現在我們遇到了一個大問題,因為某些查詢(最重要的查詢)的響應時間非常慢。我的意思是,如果指定參數有這麼多的結果,應用程序必須一次返回大約 1000 條記錄。參數分佈在模式的所有表上(實際上,它是模式的一部分)。作為
Features
一張包含所有主要“特徵”的表格(臥室數量、車庫數量等)。考慮到這一點,現在需要花費大量時間的查詢如下:
DECLARE @cols NVARCHAR(MAX), @query NVARCHAR(MAX); DECLARE @properties TABLE( [ID] INT ) INSERT INTO @properties SELECT p.[Id] FROM[Property] p INNER JOIN[GeoLocation] AS[g] ON[p].[Id] = [g].[PropertyId] INNER JOIN[PropertyFeature] AS[pf] ON[pf].[PropertyId] = [p].[Id] INNER JOIN[Feature] AS[f] ON[pf].[FeatureId] = [f].[Id] WHERE[g].[Address] IS NOT NULL AND(([g].[Address] <> N'') OR[g].[Address] IS NULL) AND[pf].[FeatureId] IN( Select ID from feature where featuretype = 1) GROUP BY p.Id, p.ModificationDate ORDER BY [p].ModificationDate DESC, newid() OFFSET 0 ROWS FETCH NEXT 1000 ROWS ONLY DECLARE @features TABLE( [Name] NVARCHAR(80) ) INSERT INTO @features select Name from feature where FeatureType = 1 CREATE TABLE #temptable ( Id INT, Url NVARCHAR(200), Title NVARCHAR(300), Address NVARCHAR(200), Domain Tinyint, Price Real, Image NVARCHAR(150), Name NVARCHAR(80), Value NVARCHAR(150) ) INSERT INTO #temptable SELECT [t].[Id], [t].[Url], [t].[GeneratedTitle] AS[Title], [t].[Address], [t].[Domain], [t].[Price], (SELECT TOP(1) ISNULL([m].[Resize1200x1200], [m].Resize730x532) FROM [Multimedia] AS[m] WHERE [t].[Id] = [m].[PropertyId] and m.MultimediaType = 1 ORDER BY [m].[Order]) AS[Image], [t].[Name], [t].[Value] FROM (SELECT [p].[Id], [p].[Url], [p].[GeneratedTitle], [g].[Address], [p].[Domain], [pr].[Amount] AS Price, [p].[ModificationDate], [f].[Name], [pf].[Value] FROM [Property] AS [p] INNER JOIN [GeoLocation] AS[g] ON [p].[Id] = [g].[PropertyId] INNER JOIN [PropertyFeature] AS[pf] ON [pf].[PropertyId] = [p].[Id] INNER JOIN [Feature] AS[f] ON [pf].[FeatureId] = [f].[Id] INNER JOIN [Operation] AS [o] ON [p].[Id] = [o].[PropertyId] INNER JOIN [OperationType] AS [o0] ON [o].[OperationTypeId] = [o0].[Id] INNER JOIN [Price] AS [pr] ON [pr].[OperationId] = [o].[Id] WHERE p.Id in (Select Id from @properties) GROUP BY [p].[Id], [p].[Url], [p].[GeneratedTitle], [g].[Address], [p].[Domain], [pr].[Amount], [p].[ModificationDate], [f].[Name], [pf].[Value]) AS[t] ORDER BY[t].[ModificationDate] DESC SET @cols = STUFF( ( SELECT DISTINCT ','+QUOTENAME(c.[Name]) FROM @features c FOR XML PATH(''), TYPE ).value('.', 'nvarchar(max)'), 1, 1, ''); SET @query = 'SELECT [Id], [Url], [Title], [Address], [Domain], [Price], [Image], ' + @cols + ' FROM (SELECT [Id], [Url], [Title], [Address], [Domain], [Price], [Image], [Value] AS [value], [Name] AS[name] FROM #temptable)x PIVOT(max(value) for name in ('+@cols+')) p'; EXECUTE(@query); DROP TABLE #temptable
執行計劃和實時查詢統計顯示如下:
前面的查詢嘗試隨機獲取 X 個記錄 ID,保留所有過濾條件以僅獲取滿足該條件的記錄的 ID。現在的時間最長為 15 秒。如果我們談論超過 400 個使用者同時使用該應用程序,那就很多了。
請在這件事上給予我幫助。我花了三週時間試圖解決這個問題,但沒有成功,但已經取得了很多進展(在平均消耗 2 分鐘之前)。
如果有幫助,我可以讓您訪問具有相同數量記錄的數據庫的“虛擬”部署版本,以測試並直接查看問題。
提前致謝…
==================================================== ==================================================== = 索引:
目前在表上的索引如下:
GO CREATE UNIQUE NONCLUSTERED INDEX IX_Property_ModificationDate ON [dbo].[Property] (ModificationDate DESC) WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON) GO CREATE NONCLUSTERED INDEX [IX_Property_ParentId_StatusCode] ON [dbo].[Property] ([ParentId] ASC, [StatusCode] ASC) WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON); GO CREATE NONCLUSTERED INDEX [IX_Property_ParentId_StatusCode_Id_ModificationDate] ON [dbo].[Property] ([ParentId] ASC, [StatusCode] ASC, [Id] ASC, [ModificationDate] ASC) WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON); GO CREATE NONCLUSTERED INDEX [IX_Property_ParentId] ON [dbo].[Property]([ParentId] ASC) WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON); GO CREATE NONCLUSTERED INDEX [IX_Property_Identity_Domain_StatusCode] ON [dbo].[Property]([Identity] ASC, [Domain] ASC, [StatusCode] ASC) WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON); GO CREATE NONCLUSTERED INDEX [IX_Property_Id_ModificationDate] ON [dbo].[Property] (Id ASC, ModificationDate ASC) WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON); GO CREATE NONCLUSTERED INDEX [IX_Property_PublisherId] ON [dbo].[Property]([PublisherId] ASC) WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON); GO CREATE NONCLUSTERED INDEX [IX_Property_RealEstateTypeId] ON [dbo].[Property]([RealEstateTypeId] ASC) WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON) GO CREATE INDEX FIX_Property_StatusCode_Online ON [dbo].[Property](StatusCode) WHERE StatusCode = 1 WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON) GO CREATE INDEX FIX_Property_StatusCode_Offline ON [dbo].[Property](StatusCode) WHERE StatusCode = 0 WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON) GO CREATE INDEX FIX_Property_Domain_Urbania ON [dbo].[Property](Domain) WHERE Domain = 1 WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON) GO CREATE INDEX FIX_Property_Domain_Adondevivir ON [dbo].[Property](Domain) WHERE Domain = 2 WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON) GO GO CREATE NONCLUSTERED INDEX [IX_GeoLocation_PropertyId_ModificationDate] ON [dbo].[GeoLocation] (PropertyId ASC, [ModificationDate] DESC) WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON); GO CREATE NONCLUSTERED INDEX [IX_GeoLocation_PropertyId_Address] ON [dbo].[GeoLocation] (PropertyId ASC, [Address] ASC) WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON); GO CREATE UNIQUE NONCLUSTERED INDEX IX_GeoLocation_ModificationDate ON [dbo].[GeoLocation] (ModificationDate DESC) WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON) GO CREATE NONCLUSTERED INDEX [IX_GeoLocation_Ubigeo] ON [dbo].[GeoLocation]([Ubigeo] ASC) WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON) GO CREATE UNIQUE NONCLUSTERED INDEX [IX_GeoLocation_PropertyId] ON [dbo].[GeoLocation]([PropertyId] ASC) WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON) GO CREATE SPATIAL INDEX SIX_GeoLocation_Coordinate ON [dbo].[GeoLocation](Coordinate) GO CREATE INDEX FIX_GeoLocation_Domain_Urbania ON [dbo].[GeoLocation](Domain) WHERE Domain = 1 WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON) GO CREATE INDEX FIX_GeoLocation_Domain_Adondevivir ON [dbo].[GeoLocation](Domain) WHERE Domain = 2 WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON) GO GO CREATE NONCLUSTERED INDEX [IX_Multimedia_PropertyId_Order] ON [dbo].[Multimedia] (PropertyId ASC, [Order] ASC) WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON); GO CREATE NONCLUSTERED INDEX [IX_Multimedia_PropertyId] ON [dbo].[Multimedia]([PropertyId] ASC) WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON); GO CREATE NONCLUSTERED INDEX [IX_Multimedia_Order] ON [dbo].[Multimedia]([Order] ASC) WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON); GO CREATE NONCLUSTERED INDEX [PK_Multimedia_Property] ON [dbo].[Multimedia]([Id] ASC, [PropertyId] ASC); GO CREATE INDEX FIX_Multimedia_MultimediaType_Image ON [dbo].[Multimedia](MultimediaType) WHERE MultimediaType = 1 WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON) GO GO CREATE NONCLUSTERED INDEX [IX_PropertyFeature_PropertyId_FeatureId] ON [dbo].[PropertyFeature] (PropertyId ASC, [FeatureId] ASC) WITH( SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, FILLFACTOR = 90, ONLINE = ON); GO CREATE NONCLUSTERED INDEX [IX_PropertyFeature_FeatureId] ON [dbo].[PropertyFeature]([FeatureId] ASC); GO CREATE NONCLUSTERED INDEX [IX_PropertyFeature_PropertyId] ON [dbo].[PropertyFeature]([PropertyId] ASC); GO CREATE NONCLUSTERED INDEX [IX_PropertyFeature-FeatureId] ON [dbo].[PropertyFeature]([Id] ASC, [FeatureId] ASC); GO CREATE NONCLUSTERED INDEX [IX_PropertyFeature_Property] ON [dbo].[PropertyFeature]([Id] ASC, [PropertyId] ASC); GO CREATE NONCLUSTERED INDEX [IX_Operation_PropertyId] ON [dbo].[Operation]([PropertyId] ASC); GO CREATE NONCLUSTERED INDEX [IX_Operation_OperationTypeId] ON [dbo].[Operation]([OperationTypeId] ASC); GO CREATE NONCLUSTERED INDEX [IX_Price_OperationId] ON [dbo].[Price]([OperationId] ASC); GO CREATE NONCLUSTERED INDEX [IX_Price_Operation] ON [dbo].[Price]([Id] ASC, [OperationId] ASC);
更新: 我使用的 Azure SQL Server 服務層是標準 S0(10 個 DTU)
處於標準層 (S0) 會導致您的查詢受到顯著限制,從而影響總執行時間。以下是批次中所有 5 條語句的時間,在 Sentry One 計劃資源管理器中查看:
如您所見,大多數查詢的持續時間都比 CPU 時間長得多。這通常意味著查詢正在等待某些資源。查看中間查詢,我們可以在 XML 中看到這些等待統計資訊:
<WaitStats> <Wait WaitType="SOS_SCHEDULER_YIELD" WaitTimeMs="939" WaitCount="20" /> <Wait WaitType="RESOURCE_GOVERNOR_IDLE" WaitTimeMs="858" WaitCount="61" /> </WaitStats>
查詢基本上花費了整個持續時間等待在 CPU 上調度。您可以在SQL Skills 等待類型庫中查找這些等待的詳細資訊。
查詢事件等待很長時間才能編譯,如“QueryPlan”元素中的統計資訊所示:
<QueryPlan ... CompileTime="711" CompileCPU="67" ... >
所有 5 條語句都有相似的特徵(高資源等待,低 CPU)。
可以對查詢進行一些改進。例如,由於使用了表變數,
@properties
導致中間查詢的估計錯誤,從一開始就導致不理想的計劃選擇(#temp
在這種情況下,表可能會更好)。但是,如果沒有所有的等待,這整批查詢將在不到一秒的時間內執行。因此,除非您有更多可用硬體,否則調整查詢和索引將無濟於事。