一些 SQL Server 地理空间查询比其他查询花费的时间要长得多

Ken*_*ith 7 sql-server spatial

我们有一个简单的 SQL Server 表,其中包含如下所示的地理空间数据:

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
)
Run Code Online (Sandbox Code Playgroud)

我们现在有大约 100k+ 行,但预计会增长到数百万。

我们对其运行查询,如下所示:

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
Run Code Online (Sandbox Code Playgroud)

不过,这有点奇怪。该表中的数据是参差不齐的:例如,我们得到了一个州南部的数据,但没有得到整个州的数据。如果我们搜索的点在我们获得数据的点的几百米内(例如,来自该州的南部),则查询返回亚秒级。但是,如果距离最近的数据点 100 公里(例如,如果目标点来自该州的北部),则查询最多需要 3 分钟左右才能返回。在这两种情况下,查询计划都表明它们是从地理空间索引的扫描开始的,所以这不是有时会发生的问题,SQL Server 无法确定它应该使用有问题的索引。

我的假设是它与地理空间索引的布局方式有关。

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);
Run Code Online (Sandbox Code Playgroud)

但我不知道我是否能很好地理解细节以解决问题。

有关如何解决此问题的任何建议?

Vla*_*nov 6

简短回答:比较快速和慢速变体的实际执行计划,您会看到自己。

当给定@point的点接近表中的点时,空间索引中使用的细分实际上有助于消除大多数行,并且只需要很少的索引查找。

当给定@point远离表中的任何点时,引擎实际上必须读取所有行。它寻求索引 100K 次,这很慢。

如果禁用空间索引,您将看到查询的性能对于任何给定的@point. 当索引有用时,它会比您的快速变体慢,但当索引有害时,它会比您的慢变体更快。

如果您还没有了解此类索引的内部结构的基本细节,请参阅空间索引概述

样本测试数据

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]
Run Code Online (Sandbox Code Playgroud)

在 ~20km x ~20km 区域内生成 ~100K 行。

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;
Run Code Online (Sandbox Code Playgroud)

创建默认空间索引

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]
Run Code Online (Sandbox Code Playgroud)

测试查询

@point1接近表中的其他点。 @point2远离表中的其他点。

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);
Run Code Online (Sandbox Code Playgroud)

执行计划和IO

启用索引

IO。最高结果很快(7 毫秒,171 次读取)。底部结果很慢(5,693 毫秒,234,662 次读取)。

索引启用 IO

快速地。

快速启用索引

减缓。

索引启用慢

索引已禁用

IO。两个查询具有相同的读取次数 (601) 和相同的持续时间 (~1700ms)。

索引禁用 IO

两个查询的计划相同:

索引禁用计划

扫描 100K 行比搜索 100K 次更快。


我不知道如何解决这个问题,如果有一种方法可以两全其美,并以某种方式自动决定是否使用索引。

您可以尝试计算边界框(最小/最大纬度/经度)并根据给定点是否在边界框内更改逻辑。

最有趣的事情发生在内置的 Geodetic Tesselation 表值函数中,我不知道如何对其进行微调。

空间索引很大程度上取决于您的数据分布。

在某些情况下,如果您知道数据很密集并且可以将搜索限制在窄条带或小区域(给定点 +- 几公里),则最好使用纬度和经度上的两个单独的简单标准索引。