【问题标题】:Can I get better performance out of this loop in 2012 Microsoft SQL Server?我可以在 2012 Microsoft SQL Server 中从这个循环中获得更好的性能吗?
【发布时间】:2015-02-10 03:42:57
【问题描述】:

我正在编写一个循环,它从一个表中获取一个点(美国州也是已知的),并查看该点与哪个县相交(#INPUT 拥有所有点地理,[dbo].[counties] 拥有所有县地理)。

这是查询:

DECLARE @RN_BEGIN INT
DECLARE @RN_END INT

SET @RN_BEGIN = 1
SELECT @RN_END = MAX([RN]) FROM #INPUT

DECLARE @STATE CHAR(2)
DECLARE @GEO GEOGRAPHY

WHILE @RN_BEGIN <= @RN_END

BEGIN

SELECT @ID_NUMBER = [ID_NUMBER] FROM #INPUT WHERE [RN] = @RN_BEGIN
SELECT @STATE = [STATE] FROM #INPUT WHERE [RN] = @RN_BEGIN
SELECT @GEO = [Geo] FROM #INPUT WHERE [RN] = @RN_BEGIN

INSERT INTO #OUTPUT
SELECT
@ID_NUMBER, 
CONCAT([statefp], [countyfp]) AS [FIPSTCNTY] 
FROM [dbo].[counties] 
WHERE @GEO.STIntersects([Geo]) = 1 AND [statefp] = @STATE

SET @RN_BEGIN = @RN_BEGIN + 1

END

对于表#INPUT(约100 万行):我在[ID_NUMBER] 上有一个聚集索引,在[RN] 上有一个非聚集索引(不包括任何列),在@987654328 上有一个空间索引@(这是点列)。

对于表 #OUTPUT(完成后应该是大约 100 万行):我在 '[ID_NUMBER]` 上有一个聚集索引

对于表[dbo].[counties](~3000 行):我在 ID 字段上有一个聚集索引(这是我导入它时引入的方式),[statefp] 上有一个非聚集索引(同时包括[geo][countyfp]),以及 [Geo] 上的空间索引(这是县列)。

所以我对你们所有人的问题是,是否有任何我遗漏的明显索引,或者可能是解决这个问题的新方法(不是循环)?我知道循环很慢(特别是当它必须循环大约 100 万次迭代时),所以我希望以一种或另一种方式加速这个查询。

非常感谢任何建议/cmets。谢谢。

【问题讨论】:

  • 什么是@GEO.STIntersects([Geo])
  • @Dave.Gugg STIntersects (geography data type) 如果地理实例与另一个地理实例相交,则返回 1。如果不是,则返回 0。
  • 好的,有道理。我以前从未使用过地理...我认为您的答案是正确的。
  • @GarethD 感谢您在下面提出的建议。我最近一直在循环思考事情,完全错过了那个加入。我会通过缩小[dbo].[counties](即仅佛罗里达州的县)和#INPUT 更小(即仅佛罗里达州的纬度/经度点)来提高性能吗?

标签: sql-server performance loops while-loop geo


【解决方案1】:

很确定你可以通过使用 JOIN 来实现这一点,而无需循环:

INSERT INTO #OUTPUT (ID_NUMBER, FIPSTCNTY)
SELECT  i.ID_NUMBER,
        FIPSTCNTY = CONCAT(c.statefp, c.countyfp)
FROM    dbo.counties AS c
        INNER JOIN #INPUT AS i
            ON i.GEO.STIntersects(c.Geo) = 1
            AND i.State = c.State;

关于索引,如果没有看到执行计划就很难说,但是如果您在启用了“包含实际执行计划”的 SSMS 中运行此查询,那么它会提示缺少索引,这不是一门精确的科学,而是是一个很好的起点。


在这一点之下应该被视为一个脚注——我不提倡在可以避免的地方使用光标

我不能强调以上内容,我不喜欢使用光标和下一个人一样,但是,因为人们只使用默认选项 他们的名声比他们应得的还要糟糕,而且带有临时表的复杂 WHILE 循环通常比正确声明的性能更差 光标。正是这一点我觉得有必要解决,因为这似乎是一个很常见的误解。

在您的情况下,您没有修改数据,而只是向前读取,所以我将游标声明为 LOCAL STATIC READ_ONLY FORWARD_ONLY 确保仅使用我需要的功能对其进行初始化:

DECLARE @STATE CHAR(2),
        @GEO GEOGRAPHY,
        @ID_NUMBER INT;

DECLARE InputCursor CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
    SELECT  State, Geo, ID_NUmber
    FROM    #Input;

OPEN InputCursor;
FETCH NEXT FROM InputCursor INTO @State, @Geo, @ID_NUMBER;

WHILE @@FETCH_STATUS = 0
BEGIN
    INSERT INTO #OUTPUT
    SELECT  @ID_NUMBER, CONCAT([statefp], [countyfp]) AS [FIPSTCNTY] 
    FROM    [dbo].[counties] 
    WHERE   @GEO.STIntersects([Geo]) = 1 
    AND     [statefp] = @STATE;

    FETCH NEXT FROM InputCursor INTO @State, @Geo, @ID_NUMBER;
END

CLOSE InputCursor;
DEALLOCATE InputCursor;

【讨论】:

  • 我也会添加LOCAL 选项(因为默认行为取决于default to local cursor 数据库选项)。游标仅在此 SP 中使用,并确保在范围结束并且所有引用都丢失时将隐式释放它。 (或手动关闭并释放光标)。
【解决方案2】:

代替

SELECT @ID_NUMBER = [ID_NUMBER] FROM #INPUT WHERE [RN] = @RN_BEGIN
SELECT @STATE = [STATE] FROM #INPUT WHERE [RN] = @RN_BEGIN
SELECT @GEO = [Geo] FROM #INPUT WHERE [RN] = @RN_BEGIN

试试

SELECT @ID_NUMBER = [ID_NUMBER],
       @STATE = [STATE], 
       @GEO = [Geo] 
FROM #INPUT 
WHERE [RN] = @RN_BEGIN

【讨论】:

  • 从来不知道你能做到这一点。从现在开始,肯定会将其合并到我的代码中。
【解决方案3】:

我认为您可以通过以下方式消除循环:

INSERT INTO #OUTPUT
SELECT i.[ID_Number], CONCAT([statefp], [countyft]) as [FIPSTCNTY] 
FROM [dbo].[counties] c INNER JOIN #INPUT i ON i.[state] = c.[state]
WHERE i.[GEO].STIntersects(c.[GEO]) = 1

我认为成本很高的另一件事是 STIntersects 子句。我不知道 SQL 是否同时使用这两个索引。您也许可以像此人在这里所做的那样解决它:

slow spatial predicates (STContains, STIntersects, STWithin, ...)

【讨论】:

    【解决方案4】:

    您应该能够完全消除循环。我认为您的整个查询块看起来可以替换为:

    INSERT INTO #OUTPUT
    SELECT DISTINCT i.[ID_NUMBER], 
        CONCAT(c.[statefp], c.[countyfp]) AS [FIPSTCNTY] 
    FROM [dbo].[counties] c
    INNER JOIN #INPUT i
        ON  i.[Geo].STIntersects(c.[Geo]) = 1
        AND i.[STATE] = c.[statefp]
    

    显然,运行这样的事情来确保你得到你想要的:

    SELECT DISTINCT TOP 1000 i.[ID_NUMBER], 
        CONCAT(c.[statefp], c.[countyfp]) AS [FIPSTCNTY] 
    FROM [dbo].[counties] c
    INNER JOIN #INPUT i
        ON  i.[Geo].STIntersects(c.[Geo]) = 1
        AND i.[STATE] = c.[statefp]
    

    根据您的数据以及[counties]#INPUT 之间的关系,您可能不需要DISTINCT 限定符。如果您知道不需要它,或者(显然)如果您知道需要重复项,则将其删除会更快。最好为插入表指定字段名称,例如INSERT INTO #OUTPUT ([ID_NUMBER], [FIPSTCNTY]),但我无法知道这些列的名称是什么。但是,如果其中任何一个是多对一的,您可能需要它来避免密钥违规。

    确保几何列上有空间索引。如果您也生成执行计划,您应该得到关于 SSMS 中缺失索引的合理建议。您可能还需要尝试加入。我不记得索引之间的关系如何。我已经好几年没有使用地理空间或几何学了。

    【讨论】:

      【解决方案5】:

      将 SQL Server STIntersects 函数与我的 geography 字段一起使用时,我遇到了类似的性能问题,其中包含(有时很复杂)POLYGON 形状。

      添加空间索引并没有什么不同,但这是我想出的解决方案。

      在我的表中,我添加了四个新的数据库字段,用于存储该记录的 POLYGON 覆盖的最小和最大经度和纬度值。

      要填充这些字段,我们需要让 SQL Server 检查 POLYGON 形状。振作起来,这不漂亮...

      UPDATE [County]
      SET 
          County_Min_Longitude = cast(geometry::STGeomFromWKB([County_Polygon].STAsBinary(), [County_Polygon].STSrid).MakeValid().STEnvelope().STPointN(1).STX as numeric(12, 5)) ,
          County_Min_Latitude = cast(geometry::STGeomFromWKB([County_Polygon].STAsBinary(), [County_Polygon].STSrid).MakeValid().STEnvelope().STPointN(1).STY as numeric(12, 5)),
          County_Max_Longitude = cast(geometry::STGeomFromWKB([County_Polygon].STAsBinary(), [County_Polygon].STSrid).MakeValid().STEnvelope().STPointN(3).STX as numeric(12, 5)),
          County_Max_Latitude = cast(geometry::STGeomFromWKB([County_Polygon].STAsBinary(), [County_Polygon].STSrid).MakeValid().STEnvelope().STPointN(3).STY as numeric(12, 5))
      

      运行此查询后,我们的县记录将全部包含原始 POLYGON 以及它的边界。但这对我们有什么帮助呢?

      现在,当我们有一个点,并且想知道它是否与我们的每个多边形相交时,我们可以加入一些额外的搜索条件。

      以下是(大致)您的代码将更改为的内容:

      DECLARE 
          @longitude NUMERIC(15, 6) = 23.1238,
          @latitude NUMERIC(15, 6) = -5.3473
      
      INSERT INTO #OUTPUT
      SELECT @ID_NUMBER, 
             CONCAT([statefp], [countyfp]) AS [FIPSTCNTY] 
      FROM [County] 
      WHERE @longitude > [County_Longitude_Min]
      AND @longitude < [County_Longitude_Max]
      AND @latitude > [County_Latitude_Min]
      AND @latitude < [County_Latitude_Max]
      AND @GEO.STIntersects([County_Polygon]) = 1 AND [statefp] = @STATE
      

      使用此代码,SQL Server 可以非常快速消除我们的点绝对不存在的任何 [County] 记录,然后它会在我们的经度和纬度时使用 STIntersects确实位于这个 POLYGON 所在的区域内。

      例如,如果我想知道某个位置是否位于英格兰德文郡,那么快速检查该点是否位于此矩形内会更有效,如果是,则 然后使用STIntersects查看该职位是否真的在这个县内。

      在我的内部应用程序中,它检查一个点是否位于 330,000 个 POLYGON 之一中,添加这四个额外字段将速度从 45 秒提高到 1 秒以下。

      【讨论】:

        猜你喜欢
        • 2014-08-23
        • 1970-01-01
        • 2016-03-20
        • 2021-05-10
        • 2020-05-05
        • 2023-03-07
        • 1970-01-01
        • 2010-09-18
        • 2016-05-09
        相关资源
        最近更新 更多