Einige SQL Server-Geodatenabfragen dauern viel, viel länger als andere


7

Wir haben eine einfache SQL Server-Tabelle mit Geodaten, die folgendermaßen aussieht:

CREATE TABLE [dbo].[Factors](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [StateCode] [nvarchar](2) NOT NULL,
    [GeoLocation] [geography] NULL,
    [Factor] [decimal](18, 6) NOT NULL,
 CONSTRAINT [PK_dbo.Factors] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)

Wir haben momentan mehr als 100.000 Zeilen darin, aber das wird voraussichtlich auf Millionen anwachsen.

Wir führen darauf Abfragen aus, die folgendermaßen aussehen:

declare @state nvarchar(2) = 'AL'
declare @point geography = geography::STGeomFromText('POINT(-86.19146040 32.38225770)', 4326)

select top 3 
    Lat,
    Lon,
    Factor, 
    GeoLocation.STDistance(@point) as Distance
from dbo.Factors
where StateCode = @state and GeoLocation.STDistance(@point) is not null
order by Distance

Hier ist das bisschen komisch. Die Daten in dieser Tabelle sind unvollständig: Wir haben sie zum Beispiel für die südlichen Teile eines Bundesstaates, aber nicht für den gesamten Bundesstaat. Wenn der Punkt, nach dem wir suchen, innerhalb einiger hundert Meter von Punkten liegt, für die wir Daten haben (z. B. aus dem südlichen Teil des Bundesstaates), gibt die Abfrage eine Untersekunde zurück. Wenn es jedoch beispielsweise 100 Kilometer vom nächsten Datenpunkt entfernt ist (z. B. wenn der Zielpunkt aus dem nördlichen Teil des Bundesstaates stammt), dauert die Rückgabe der Abfrage etwa 3 Minuten. In beiden Fällen geben Abfragepläne an, dass sie mit einem Scan des Geodatenindex beginnen. Daher ist es nicht das Problem, das manchmal auftritt, dass SQL Server nicht herausfinden kann, dass der betreffende Index verwendet werden soll.

Ich gehe davon aus, dass dies etwas mit der Darstellung des Geoindex zu tun hat.

CREATE SPATIAL INDEX IX_Factors_Spatial 
ON [dbo].[Factors] (GeoLocation)
USING GEOGRAPHY_AUTO_GRID 
    WITH (
            CELLS_PER_OBJECT = 16, 
            PAD_INDEX = OFF, 
            STATISTICS_NORECOMPUTE = OFF, 
            SORT_IN_TEMPDB = OFF, 
            DROP_EXISTING = OFF, 
            ONLINE = OFF, 
            ALLOW_ROW_LOCKS = ON, 
            ALLOW_PAGE_LOCKS = ON);

Aber ich weiß nicht, dass ich die Details gut genug verstehe, um das Problem in den Griff zu bekommen.

Irgendwelche Vorschläge, wie Sie dieses Problem beheben können?


2
Verwenden Sie immer lokale Variablen oder rufen Sie eine gespeicherte Prozedur auf, in der diese Variablen übergeben werden?
Erik Darling

Und wie versuchst du das Einfügen? Tun Sie dies über SSMS oder eine Anwendung?
Austin

Es kann auch eine gute Idee sein, diese Berechnungen in einem Programm und nicht in einer Datenbank auszuführen.
Austin

1
@Austin - Ich bin mir bei deiner ersten Frage nicht sicher. Wir versuchen, die Daten zu lesen, nicht einzufügen. (Wenn wir es einfügen, fügen wir es während unseres Seeding-Prozesses mit Entity Framework ein, nachdem wir die Daten aus einer Basis-CSV-Datei gelesen haben.) Für Ihre zweite ist dies genau das, wofür Geodatenindizes gedacht sind. Und wenn sie funktionieren, funktionieren sie wirklich ziemlich gut, mit viel weniger Speicherbedarf und schneller, als ich von Hand codieren könnte, da bin ich mir ziemlich sicher.
Ken Smith

2
@ KenSmith - Ich denke, Erik versucht zu verstehen, ob ein möglicherweise schlechter Plan generiert wird, der die langsamen Abfragen verursacht. Wenn Sie Tests auf einem Nichtproduktionssystem durchführen können, dbcc freeproccacheführen Sie eine Abfrage für den nördlichen Teil eines Bundesstaates aus, um festzustellen, ob dies schnell geht.
Max Vernon

Antworten:


6

Kurze Antwort: Vergleichen Sie die tatsächlichen Ausführungspläne für die schnellen und langsamen Varianten und Sie werden sich selbst sehen.

Wenn das Angegebene @pointnahe an den Punkten in der Tabelle liegt, helfen die im räumlichen Index verwendeten Tessellationen tatsächlich dabei, die meisten Zeilen zu schließen, und es sind nur wenige Suchvorgänge des Index erforderlich.

Wenn das Gegebene @pointweit von einem Punkt in der Tabelle entfernt ist, muss die Engine effektiv alle Zeilen lesen. Es wird 100K-mal nach einem Index gesucht, was langsam ist.

Wenn Sie den räumlichen Index deaktivieren, wird die Leistung der Abfrage für alle gegeben @point. Es ist langsamer als Ihre schnelle Variante, wenn der Index nützlich ist, aber es ist schneller als Ihre langsame Variante, wenn der Index schädlich ist.

Weitere Informationen zu den internen Strukturen eines solchen Index finden Sie unter Übersicht über räumliche Indizes, falls Sie dies noch nicht getan haben.

Beispieltestdaten

CREATE TABLE [dbo].[Factors](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [GeoLocation] [geography] NULL,
    [Factor] [decimal](18, 6) NOT NULL,
 CONSTRAINT [PK_Factors] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

Generieren Sie ~ 100.000 Zeilen in einem Bereich von ~ 20 km x ~ 20 km.

DECLARE @MinLat float = -38.180184;
DECLARE @MaxLat float = -38.000000;
DECLARE @MinLon float = 145.000000;
DECLARE @MaxLon float = 145.227707;
DECLARE @PointCount int = 317;

WITH 
x AS
(
    SELECT TOP (@PointCount)
        ROW_NUMBER() OVER (ORDER BY [object_id]) AS rn
    FROM sys.all_objects
)
INSERT INTO [dbo].[Factors]
    ([GeoLocation]
    ,[Factor])
SELECT
    geography::Point(
         @MinLat + (TLat.rn-1) * (@MaxLat - @MinLat) / (@PointCount-1)
        ,@MinLon + (TLon.rn-1) * (@MaxLon - @MinLon) / (@PointCount-1)
        ,4326) AS GeoLocation
    ,0 AS Factor
FROM
    x AS TLat CROSS JOIN x AS TLon
ORDER BY TLat.rn, TLon.rn;

Erstellen Sie einen räumlichen Standardindex

CREATE SPATIAL INDEX [IX_GeoLocation] ON [dbo].[Factors]
(
    [GeoLocation]
)USING  GEOGRAPHY_AUTO_GRID 
WITH (
CELLS_PER_OBJECT = 16, 
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]

Testabfragen

@point1liegt in der Nähe anderer Punkte in der Tabelle. @point2ist weit weg von anderen Punkten in der Tabelle.

declare @point1 geography = geography::Point(-38.000000, 145.000000, 4326);
declare @point2 geography = geography::Point(+38.000000, 145.000000, 4326);

select top 3 
    Factor, 
    GeoLocation.STDistance(@point1) as Distance
from dbo.Factors
where GeoLocation.STDistance(@point1) is not null
order by Distance
option(recompile);


select top 3 
    Factor, 
    GeoLocation.STDistance(@point2) as Distance
from dbo.Factors
where GeoLocation.STDistance(@point2) is not null
order by Distance
option(recompile);

Ausführungspläne und IO

Index aktiviert

IO. Das beste Ergebnis ist schnell (7 ms, 171 Lesevorgänge). Das untere Ergebnis ist langsam (5.693 ms, 234.662 Lesevorgänge).

Index aktiviert E / A.

Schnell.

Index schnell aktiviert

Langsam.

Index langsam aktiviert

Index deaktiviert

IO. Beide Abfragen haben die gleiche Anzahl von Lesevorgängen (601) und die gleiche Dauer (~ 1700 ms).

Index deaktiviert E / A.

Der Plan ist für beide Abfragen gleich:

Index deaktiviert Plan

Das Scannen von 100.000 Zeilen ist schneller als das Suchen von 100.000 Zeilen.


Ich weiß nicht, wie ich das Problem lösen soll, ob es eine Möglichkeit gibt, das Beste aus beiden Welten herauszuholen und irgendwie automatisch zu entscheiden, ob ich den Index verwenden soll oder nicht.

Sie können versuchen, den Begrenzungsrahmen (min / max lat / lon) zu berechnen und die Logik basierend darauf zu ändern, ob sich der angegebene Punkt innerhalb des Begrenzungsrahmens befindet.

Das Interessanteste passiert in dieser integrierten Funktion mit Tabellenwert für die geodätische Tesselation, und ich sehe keine Feinabstimmung.

Bei räumlichen Indizes hängt vieles von Ihrer Datenverteilung ab.

In einigen Fällen sind Sie möglicherweise mit zwei separaten einfachen Standardindizes für Längen- und Breitengrade besser dran, wenn Sie wissen, dass Ihre Daten dicht sind und Sie die Suche auf einen schmalen Streifen oder einen kleinen Bereich beschränken können (gegebener Punkt + - wenige km).


Dies war eine sehr detaillierte Antwort, und sie scheint mir richtig zu sein, auch wenn sie mir nicht gefällt :-). Ich werde verschiedene Lösungen untersuchen, einschließlich des Ausfüllens von Daten (mit einem -1-Faktor oder ähnlichem) mit einer niedrigen Auflösung für Punkte, für die wir keine echten Daten haben.
Ken Smith
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.