Nous avons une simple table SQL Server avec des données géospatiales qui ressemblent à ceci:
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
)
Nous avons quelque chose comme 100k + rangées dedans en ce moment, mais cela devrait atteindre des millions de personnes.
Nous exécutons des requêtes qui ressemblent à ceci:
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
Voici le peu bizarre cependant. Les données de cette table sont obsolètes: nous l'avons pour les parties sud d'un état, par exemple, mais pas pour l'ensemble de l'état. Si le point que nous recherchons est dans quelques centaines de mètres de points, nous avons des données pour (par exemple, à partir de la partie sud de l'état), la requête renvoie les sous-seconde. Mais si c'est le cas, disons, à 100 kilomètres du point de données le plus proche (par exemple, si le point cible est de la partie nord de l'état), la requête prendra jusqu'à 3 minutes environ de retourner. Dans les deux cas, les plans de requête indiquent qu'ils commencent par une numérisation de l'indice géospatial, ce n'est donc pas le problème qui se produit parfois, que SQL Server ne peut pas comprendre qu'il est censé utiliser l'index en question.
Mon hypothèse est que cela a quelque chose à voir avec la manière dont l'indice géospatial est disposé.
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);
Mais je ne sais pas que je groche les détails assez bien pour mettre mon doigt sur le problème.
Toute suggestion pour approcher de la dépannage de cela?
Réponse courte: comparez les plans d'exécution réels pour les variantes rapides et lentes et vous verrez vous-même.
Quand les données @point
est proche des points de la table, les tessellations utilisées dans l'indice spatial contribuent effectivement à rejeter la plupart des rangées et que peu de recherches de l'indice sont nécessaires.
Quand les données @point
est loin de tout point de la table, le moteur doit effectivement lire toutes les lignes. Il cherche un index 100k fois, ce qui est lent.
Si vous désactivez l'index spatial, vous verrez que la performance de la requête devient la même pour tout @point
. Il sera plus lent que votre variante rapide lorsque l'index est utile, mais ce sera plus rapide que votre variante lente lorsque l'indice est nocif.
Voir Aperçu des index spatiaux Si vous n'avez pas déjà pour les détails de base sur les structures internes de cet indice.
Exemple de données de test
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]
Générez ~ 100K lignes dans une zone de ~ 20 km de ~ 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;
Créer une index spatiale par défaut
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]
Tester les requêtes
@point1
est proche d'autres points de la table. @point2
est loin des autres points de la table.
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);
Index activé
Io. Le résultat supérieur est rapide (7 ms, 171 lis). Le résultat inférieur est lent (5 693 ms, 234 662 lit).
Rapide.
Ralentir.
Index désactivé
Io. Les deux requêtes ont le même nombre de lectures (601) et de même durée (~ 1700ms).
Le plan est le même pour les deux requêtes:
Il est plus rapide de numériser des lignes 100K, que de rechercher 100K fois.
Je ne sais pas comment résoudre le problème, s'il existe un moyen de tirer le meilleur parti des deux mondes et de décider automatiquement d'utiliser l'index ou non.
Vous pouvez essayer de calculer la boîte à bornes (MIN/Max Lat/Lon) et modifier la logique sur la base de si le point donné se trouve dans la zone de sélection.
La chose la plus intéressante se produit dans cette fonction de valorisation de la table de tesselation géodésique intégrée et je ne vois pas comment l'ajuster.
Avec des index spatiaux, beaucoup dépend de votre distribution de données.
Dans certains cas, vous risquez peut-être mieux avec deux indices standard simples distincts sur la latitude et la longitude si vous savez que vos données sont denses et que vous pouvez limiter la recherche à une bande étroite ou à une petite zone (point indiqué + - quelques km).