在搜索之间加快速度

时间:2016-08-05 13:08:09

标签: sql-server tsql

我正在使用MaxMind免费数据库进行IP查找。我将数据转换为下表:

CREATE TABLE [dbo].[GeoBlocks](
    [StartIPNum] [varchar](50) NULL,
    [EndIPNumb] [varchar](50) NULL,
    [LocationNum] [varchar](50) NULL,
    [PostalCode] [varchar](50) NULL,
    [Latitude] [varchar](50) NULL,
    [Longitude] [varchar](50) NULL)

此查找表中有大约3.5M条记录。

我的目标是通过查找IP介于StartIPNum和EndIPNum之间的记录来确定IP(十进制格式)的LocationNum

我的存储过程如下所示:     参数:@DecimalIP bigint

select GeoBlocks.StartIPNum ,@DecimalIP as DecimalIp 
     ,GeoBlocks.Postalcode ,GeoBlocks.Latitude as Latitude
     ,GeoBlocks.Longitude as Longitude
     from GeoBlocks
    where @DecimalIP between GeoBlocks.StartIPNum  and GeoBlocks.EndIPNumb

我在StartIPNum和EndIPNum上创建了唯一索引。

但是,当我运行它时,SQL Server会对查询的Where部分执行表扫描。此查询需要650-750毫秒。 (我服务器上的大多数查询需要0-2ms)

如何加快此查询?

添加了样本数据:

StartIPNum  EndIPNumb   LocationNum PostalCode  Latitude    Longitude
1350218632  1350218639  2782113                  48.2000    16.3667
1350218640  1350218655  2782113                  48.2000    16.3667
1350218656  1350218687  2782113                  48.2000    16.3667
1350218688  1350218751  2782113                  48.2000    16.3667
1350218752  1350218783  2782113                  48.2000    16.3667

2 个答案:

答案 0 :(得分:1)

<强>更新

总结各种评论中散布的信息:

  1. IP地址列是VarChar(50)个字符串,包含没有左边填充的十进制值。这些列的索引将按字母顺序排序,而不是数字排序,即“10”&lt; “2”。 (使用左边填充,排序也将在数字上正确:“10”&gt;“02”。)

  2. WHERE子句(where @DecimalIP between GeoBlocks.StartIPNum and GeoBlocks.EndIPNumb)使用混合数据类型。 @DecimalIPBIGINT,而两列是VarChar(50)。 SQL通过实现数据类型优先级方案来处理混合数据类型之间的操作。 (Ref。)这会导致每行中的IP地址从字符串转换为BIGINT值,因此比较以数字方式完成,“预期”结果以相当大的成本返回。在这种情况下,索引(除了)之外都是无用的。

  3. 将列更改为BIGINT将允许使用索引来提高性能并确保以数字方式而不是按字母顺序进行比较。包含StartIPNumEndIPNumb列的单个索引将大大提高性能。请注意,如果不允许重叠的地址范围,则索引在StartIPNum上实际上是唯一的,并且可以替换为StartIPNum上的索引,EndIPNumb作为包含的列表现。

  4. 原始答案

    如果您使用点分表示的IPV4地址,例如“192.168.0.42”,您可以使用此UDF将字符串转换为BIGINT值:

    create function [dbo].[IntegerIPV4Address]( @IPV4Address VarChar(16) )
      returns BigInt
      with SchemaBinding
      begin
      declare @Dot1 as Int = CharIndex( '.', @IPV4Address );
      declare @Dot2 as Int = CharIndex( '.', @IPV4Address, @Dot1 + 1 );
      declare @Dot3 as Int = CharIndex( '.', @IPV4Address, @Dot2 + 1 );
      return Cast( Substring( @IPV4Address, 0, @Dot1 ) as BigInt ) * 0x1000000 +
        Cast( Substring( @IPV4Address, @Dot1 + 1, @Dot2 - @Dot1 - 1 ) as BigInt ) * 0x10000 +  
        Cast( Substring( @IPV4Address, @Dot2 + 1, @Dot3 - @Dot2 - 1 ) as BigInt ) * 0x100 +
        Cast( Substring( @IPV4Address, @Dot3 + 1, Len( @IPV4Address ) * 1 ) as BigInt );
      end
    

    您可以存储整数值,也可以根据函数结果在计算列上创建索引。请注意,您需要更改查询以引用WHERE子句中的整数列。

    如果将值存储为整数,则以下函数会将它们转换回规范化字符串,其中地址的每个部分都是三位数。这些值可用于比较,因为它们将按字母顺序和数字顺序排序。

    create function [dbo].[NormalizedIPV4Address]( @IntegerIPV4Address as BigInt )
      returns VarChar(16)
      with SchemaBinding -- Deterministic function.
      begin
      declare @BinaryAddress as VarBinary(4) = Cast( @IntegerIPV4Address as VarBinary(4) );
      return Right( '00' + Cast( Cast( Substring( @BinaryAddress, 1, 1 ) as Int ) as VarChar(3) ), 3 ) +
        '.' + Right( '00' + Cast( Cast( Substring( @BinaryAddress, 2, 1 ) as Int ) as VarChar(3) ), 3 ) +
        '.' + Right( '00' + Cast( Cast( Substring( @BinaryAddress, 3, 1 ) as Int ) as VarChar(3) ), 3 ) +
        '.' + Right( '00' + Cast( Cast( Substring( @BinaryAddress, 4, 1 ) as Int ) as VarChar(3) ), 3 )
      end
    

    您可以对表中的字符串值进行往返,以使它们全部变为“规范化”形式,以便通过使用这两个函数对它们进行正确排序。这不是一个理想的解决方案,因为它要求所有未来的插入和更新都要标准化,但目前可能有所帮助。

答案 1 :(得分:0)

我猜这个索引没有正确设置。你可以像这样改进它:

  1. 进入 SQL Server Management Studio ,然后打开一个新的查询窗口
  2. 从菜单中选择:查询 - >包含实际执行计划(Ctrl + M)
  3. 输入您的查询并执行
  4. 现在将执行查询,并将显示执行计划。如果索引不好,它会向您显示缺少哪个索引以及您可以执行的操作。

    它将显示创建索引所需的精确SQL语句。 只需复制+粘贴该语句并执行它,然后您的索引就可以正常工作。