次のスキーマがあります:
この質問はStackOverflowにも投稿されましたが、プロジェクトの性質上、DB管理に重点を置いた専門家にも相談したいと思います。これが間違いならすみません
現在、テーブル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人を超えるユーザーについて話すと、多くのことになります。
これで私を助けてください。私はこの問題を解決するために3週間成功していませんが、多くの進歩がありました(平均で2分を消費する前)。
それが役立つ場合は、同じ量のレコードを使用してDBの「ダミー」デプロイバージョンにアクセスし、問題を直接テストして確認できます。
前もって感謝します...
================================================== ================================================== =INDEXES:
現在テーブルにあるインデックスは次のとおりです。
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);
UPDATE:使用しているAzure SQL Serverサービス層は標準S0(10 DTU)です
スタンダード層(S0)にいると、クエリが大幅に抑制され、全体のランタイムに影響します。これは、Sentry One Plan Explorerで表示される、バッチ内の5つのステートメントすべての時間です。
ご覧のとおり、ほとんどすべてのクエリの継続時間はCPU時間よりもはるかに長くなっています。これは、クエリがリソースを待機していることを意味します。中央のクエリを見てみると、XMLでこれらの待機統計を確認できます。
<WaitStats>
<Wait WaitType="SOS_SCHEDULER_YIELD" WaitTimeMs="939" WaitCount="20" />
<Wait WaitType="RESOURCE_GOVERNOR_IDLE" WaitTimeMs="858" WaitCount="61" />
</WaitStats>
クエリは基本的に、CPUでスケジュールされるのを待機していた期間全体を費やしました。これらの待機の詳細は SQLスキル待機タイプライブラリ で確認できます。
"QueryPlan"要素の統計から明らかなように、クエリイベントはコンパイルするのに長時間かかります。
<QueryPlan ... CompileTime="711" CompileCPU="67" ... >
5つのステートメントすべてに同様の特性(高いリソース待機、低いCPU)があります。
クエリに加えることができるいくつかの改善があります。たとえば、@properties
のテーブル変数を使用しているため、真ん中のクエリには不適切な見積もりがあり、最初から理想的でないプランが選択されます(#temp
テーブルは、この状況)。
ただし、すべての待機がなければ、このクエリのバッチ全体は1秒未満で実行されます。したがって、より多くのハードウェアを使用できるようになるまで、クエリとインデックスのチューニングは役に立ちません。