Dieses Problem ist im Allgemeinen schwer zu lösen, aber wir können dem Optimierer einige Maßnahmen bei der Auswahl eines Plans empfehlen. Dieses Skript erstellt eine Tabelle mit 10.000 Zeilen mit einer bekannten Pseudozufallsverteilung von Zeilen, um Folgendes zu veranschaulichen:
CREATE TABLE dbo.SomeDateTable
(
Id INTEGER IDENTITY(1, 1) PRIMARY KEY NOT NULL,
StartDate DATETIME NOT NULL,
EndDate DATETIME NOT NULL
);
GO
SET STATISTICS XML OFF
SET NOCOUNT ON;
DECLARE
@i INTEGER = 1,
@s FLOAT = RAND(20120104),
@e FLOAT = RAND();
WHILE @i <= 10000
BEGIN
INSERT dbo.SomeDateTable
(
StartDate,
EndDate
)
VALUES
(
DATEADD(DAY, @s * 365, {d '2009-01-01'}),
DATEADD(DAY, @s * 365 + @e * 14, {d '2009-01-01'})
)
SELECT
@s = RAND(),
@e = RAND(),
@i += 1
END
Die erste Frage ist, wie diese Tabelle indiziert wird. Eine Option besteht darin, zwei Indizes für die DATETIME
Spalten bereitzustellen , sodass der Optimierer mindestens auswählen kann, ob nach StartDate
oder gesucht werden soll EndDate
.
CREATE INDEX nc1 ON dbo.SomeDateTable (StartDate, EndDate)
CREATE INDEX nc2 ON dbo.SomeDateTable (EndDate, StartDate)
Natürlich bedeuten die Ungleichungen in beiden StartDate
und EndDate
, dass nur eine Spalte in jedem Index eine Suche in der Beispielabfrage unterstützen kann, aber dies ist ungefähr das Beste, was wir tun können. Wir könnten in Betracht ziehen, die zweite Spalte in jedem Index zu INCLUDE
einem Schlüssel zu machen, aber wir könnten andere Abfragen haben, die eine Gleichheitssuche für die führende Spalte und eine Ungleichheitssuche für die zweite Spalte durchführen können. Auf diese Weise erhalten wir möglicherweise auch bessere Statistiken. Wie auch immer...
DECLARE
@StartDateBegin DATETIME = {d '2009-08-01'},
@StartDateEnd DATETIME = {d '2009-10-15'},
@EndDateBegin DATETIME = {d '2009-08-05'},
@EndDateEnd DATETIME = {d '2009-10-22'}
SELECT
COUNT_BIG(*)
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.StartDate BETWEEN @StartDateBegin AND @StartDateEnd
AND sdt.EndDate BETWEEN @EndDateBegin AND @EndDateEnd
Bei dieser Abfrage werden Variablen verwendet, sodass der Optimierer im Allgemeinen die Selektivität und Verteilung errät, was zu einer geschätzten Kardinalität von 81 Zeilen führt . Tatsächlich erzeugt die Abfrage 2076 Zeilen, eine Diskrepanz, die in einem komplexeren Beispiel wichtig sein könnte.
In SQL Server 2008 SP1 CU5 oder höher (oder R2 RTM CU1) können wir die Parametereinbettungsoptimierung nutzen , um bessere Schätzungen zu erhalten, indem wir sie einfach OPTION (RECOMPILE)
zur SELECT
obigen Abfrage hinzufügen . Dies führt zu einer Kompilierung kurz vor der Ausführung des Stapels, sodass SQL Server die tatsächlichen Parameterwerte "sehen" und für diese optimieren kann. Mit dieser Änderung wird die Schätzung auf 468 Zeilen verbessert (obwohl Sie den Laufzeitplan überprüfen müssen, um dies zu sehen). Diese Schätzung ist besser als 81 Zeilen, aber immer noch nicht so nah. Die durch das Ablaufverfolgungsflag 2301 aktivierten Modellierungserweiterungen können in einigen Fällen hilfreich sein, jedoch nicht bei dieser Abfrage.
Das Problem besteht darin, dass sich die durch die beiden Bereichssuchen qualifizierten Zeilen überlappen. Eine der vereinfachenden Annahmen in der Kalkulations- und Kardinalitätsschätzungskomponente des Optimierers ist, dass Prädikate unabhängig sind. Wenn also beide eine Selektivität von 50% haben, wird angenommen, dass das Ergebnis der Anwendung beider 50% von 50% = 25% der Zeilen ergibt ). Wenn diese Art von Korrelation ein Problem darstellt, können wir sie häufig mit mehrspaltigen und / oder gefilterten Statistiken umgehen. Bei zwei Bereichen mit unbekannten Start- und Endpunkten wird dies unpraktisch. Hier müssen wir manchmal darauf zurückgreifen, die Abfrage in ein Formular umzuschreiben, das zufällig zu einer besseren Schätzung führt:
SELECT COUNT(*) FROM
(
SELECT
sdt.Id
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.StartDate BETWEEN @StartDateBegin AND @StartDateEnd
INTERSECT
SELECT
sdt.Id
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.EndDate BETWEEN @EndDateBegin AND @EndDateEnd
) AS intersected (id)
OPTION (RECOMPILE)
Dieses Formular führt zu einer Laufzeitschätzung von 2110 Zeilen (gegenüber 2076 tatsächlichen). Es sei denn, Sie haben TF 2301 aktiviert. In diesem Fall sehen die fortgeschritteneren Modellierungstechniken den Trick durch und liefern genau die gleiche Schätzung wie zuvor: 468 Zeilen.
Eines Tages erhält SQL Server möglicherweise native Unterstützung für Intervalle. Wenn dies mit einer guten statistischen Unterstützung einhergeht, haben Entwickler möglicherweise Angst davor, solche Abfragepläne etwas weniger zu optimieren.