Optimieren Sie den Index für eine Zeilentabelle mit 2.135.044.521

10

Ich habe ein E / A-Problem mit einem großen Tisch.

Allgemeine Statistiken

Die Tabelle weist die folgenden Hauptmerkmale auf:

  • Umgebung: Azure SQL-Datenbank (Stufe ist P4 Premium (500 DTUs))
  • Zeilen: 2.135.044.521
  • 1.275 gebrauchte Partitionen
  • Clustered und partitionierter Index

Modell

Dies ist die Tabellenimplementierung:

CREATE TABLE [data].[DemoUnitData](
    [UnitID] [bigint] NOT NULL,
    [Timestamp] [datetime] NOT NULL,
    [Value1] [decimal](18, 2) NULL,
    [Value2] [decimal](18, 2) NULL,
    [Value3] [decimal](18, 2) NULL,
    CONSTRAINT [PK_DemoUnitData] PRIMARY KEY CLUSTERED 
    (
        [UnitID] ASC,
        [Timestamp] ASC
    )
)
GO

ALTER TABLE [data].[DemoUnitData] WITH NOCHECK ADD CONSTRAINT [FK_DemoUnitData_Unit] FOREIGN KEY([UnitID])
REFERENCES [model].[Unit] ([ID])
GO

ALTER TABLE [data].[DemoUnitData] CHECK CONSTRAINT [FK_DemoUnitData_Unit]
GO

Die Partitionierung hängt damit zusammen:

CREATE PARTITION SCHEME [DailyPartitionSchema] AS PARTITION [DailyPartitionFunction] ALL TO ([PRIMARY])

CREATE PARTITION FUNCTION [DailyPartitionFunction] (datetime) AS RANGE RIGHT
FOR VALUES (N'2017-07-25T00:00:00.000', N'2017-07-26T00:00:00.000', N'2017-07-27T00:00:00.000', ... )

Servicequalität

Ich denke, die Indizes und Statistiken werden jede Nacht durch schrittweise Neuerstellung / Reorganisation / Aktualisierung gut gepflegt.

Dies sind die aktuellen Indexstatistiken der am häufigsten verwendeten Indexpartitionen:

Partitionsstatistik

Dies sind die aktuellen Statistik-Eigenschaften der am häufigsten verwendeten Partitionen:

Statistiken

Problem

Ich führe eine einfache Abfrage mit hoher Frequenz für die Tabelle aus.

SELECT [UnitID]
    ,[Timestamp]
    ,[Value1]
    ,[Value2]
    ,[Value3]
FROM [data].[DemoUnitData]
WHERE [UnitID] = 8877 AND [Timestamp] >= '2018-03-01' AND [Timestamp] < '2018-03-13'
OPTION (MAXDOP 1)

exce count

Der Ausführungsplan sieht folgendermaßen aus: https://www.brentozar.com/pastetheplan/?id=rJvI_4TtG

Mein Problem ist, dass diese Abfragen eine extrem hohe Anzahl von E / A-Operationen erzeugen, was zu einem Engpass bei PAGEIOLATCH_SHWartezeiten führt.

top wartet

Frage

Ich habe gelesen, dass PAGEIOLATCH_SHWartezeiten oft mit nicht gut optimierten Indizes zusammenhängen. Gibt es Empfehlungen für mich, wie ich E / A-Vorgänge reduzieren kann? Vielleicht durch Hinzufügen eines besseren Index?


Antwort 1 - bezogen auf Kommentar von @ S4V1N

Der veröffentlichte Abfrageplan stammt aus einer Abfrage, die ich in SSMS ausgeführt habe. Nach Ihrem Kommentar recherchiere ich zum Serververlauf. Die vom Dienst ausgeführte Abfrage sieht etwas anders aus (EntityFramework-bezogen).

(@p__linq__0 bigint,@p__linq__1 datetime2(7),@p__linq__2 datetime2(7)) 

SELECT 1 AS [C1], [Extent1] 
   .[Timestamp] AS [Timestamp], [Extent1] 
   .[Value1] AS [Value1], [Extent1] 
   .[Value2] AS [Value2], [Extent1] 
   .[Value3] AS [Value3]  
FROM [data].[DemoUnitData] AS [Extent1]  
WHERE ([Extent1].[UnitID] = @p__linq__0)  
AND ([Extent1].[Timestamp] >= @p__linq__1)  
AND ([Extent1].[Timestamp] < @p__linq__2) OPTION (MAXDOP 1) 

Auch der Plan sieht anders aus:

https://www.brentozar.com/pastetheplan/?id=H1fhALpKG

oder

https://www.brentozar.com/pastetheplan/?id=S1DFQvpKz

Und wie Sie hier sehen können, wird unsere DB-Leistung von dieser Abfrage kaum beeinflusst.

Top SQL

Antwort 2 - bezogen auf die Antwort von @Joe Obbish

Zum Testen der Lösung habe ich Entity Framework durch einen einfachen SqlCommand ersetzt. Das Ergebnis war eine erstaunliche Leistungssteigerung!

Der Abfrageplan ist jetzt der gleiche wie in SSMS, und die logischen Lese- und Schreibvorgänge fallen auf ~ 8 pro Ausführung.

Die gesamte E / A-Last sinkt auf fast 0! I / O-Drop

Es erklärt auch, warum ich einen großen Leistungsabfall bekomme, nachdem ich den Partitionsbereich von monatlich auf täglich geändert habe. Das Fehlen der Partitionseliminierung führte dazu, dass mehr Partitionen gescannt werden mussten.

Steffen Mangold
quelle
2
Mit Blick auf den Ausführungsplan scheint diese Abfrage überhaupt nicht so problematisch zu sein. Sie hat nur die erforderlichen Partitionen mit einer geringen Anzahl von Lesevorgängen gescannt und stattdessen keine Wartezeiten für pageiolatch_sh (sos_sched ..) gemeldet. Was verständlich ist, weil Sie sowieso keine physischen Lesungen hatten. Werden diese kumulativen Wartezeiten oder eine bestimmte Zeitspanne in Anspruch genommen? Vielleicht ist das Problem doch eine andere Frage.
S4V1N
Ich habe eine detaillierte Antwort auf Sie @ S4V1N oben gepostet
Steffen Mangold

Antworten:

7

Möglicherweise können Sie die PAGEIOLATCH_SHWartezeiten für diese Abfrage reduzieren, wenn Sie die vom ORM generierten Datentypen ändern können. Die TimestampSpalte in Ihrer Tabelle hat einen Datentyp von, DATETIMEaber die Parameter @p__linq__1und @p__linq__2Datentypen von DATETIME2(7). Dieser Unterschied ist der Grund, warum der Abfrageplan für die ORM-Abfragen so viel komplizierter ist als der erste Abfrageplan, den Sie mit fest codierten Suchfiltern veröffentlicht haben. Einen Hinweis darauf erhalten Sie auch im XML:

<ScalarOperator ScalarString="GetRangeWithMismatchedTypes([@p__linq__1],NULL,(22))">

Mit der ORM-Abfrage können Sie keine Partitionseliminierung erzielen. Sie erhalten mindestens einige logische Lesevorgänge für jede Partition, die in der Partitionsfunktion definiert ist, selbst wenn Sie nur nach einem Tag Daten suchen. Innerhalb jeder Partition erhalten Sie eine Indexsuche, sodass es nicht lange dauert, bis SQL Server zur nächsten Partition übergeht, aber möglicherweise summiert sich das gesamte E / A auf.

Ich habe eine einfache Reproduktion gemacht, um sicher zu sein. Innerhalb der Partitionsfunktion sind 11 Partitionen definiert. Für diese Abfrage:

DECLARE @p__linq__0 bigint = 2000;
DECLARE @p__linq__1 datetime2(7) = '20180103';
DECLARE @p__linq__2 datetime2(7) = '20180104';

SELECT 1 AS [C1]
, [Extent1].[Timestamp] AS [Timestamp]
, [Extent1].[Value1] AS [Value1]
FROM [DemoUnitData] AS [Extent1]  
WHERE ([Extent1].[UnitID] = @p__linq__0)  
AND ([Extent1].[Timestamp] >= @p__linq__1)  
AND ([Extent1].[Timestamp] < @p__linq__2)
OPTION (MAXDOP 1) ;

So sieht IO aus:

Tabelle 'DemoUnitData'. Scananzahl 11, logische Lesevorgänge 40

Wenn ich die Datentypen korrigiere:

DECLARE @p__linq__0 bigint = 2000;
DECLARE @p__linq__1 datetime = '20180103';
DECLARE @p__linq__2 datetime = '20180104';

SELECT 1 AS [C1]
, [Extent1].[Timestamp] AS [Timestamp]
, [Extent1].[Value1] AS [Value1]
FROM [DemoUnitData] AS [Extent1]  
WHERE ([Extent1].[UnitID] = @p__linq__0)  
AND ([Extent1].[Timestamp] >= @p__linq__1)  
AND ([Extent1].[Timestamp] < @p__linq__2)
OPTION (MAXDOP 1) ;

E / A werden durch Partitionseliminierung reduziert:

Tabelle 'DemoUnitData'. Scananzahl 2, logische Lesevorgänge 8

Joe Obbish
quelle