2016-08-05 42 views
0

我正在使用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) 

此查找表中大约有350万条记录。

我的目标是通过找出其中的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服务器对查询的Where部分执行表扫描。这个查询需要650-750ms。 (我的服务器上的大多数查询需要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 
+1

如果您使用的是IPV4地址,则可以将它们转换为'BIGINT'值并使用适当的索引。按字母顺序排序的字符串中的“小数”(?!)值可能无法满足您的要求。请提供一些示例数据?提示:使用合适的软件(MySQL,Oracle,DB2,...)和版本(例如, '的SQL服务器2014'。语法和功能的差异往往会影响答案。 – HABO

+0

我使用sql-server和tsql进行了标记。 SQLServer是2014 –

+0

@habo我建议发表评论的第一部分作为答案。我认为你是真实的。 – Bohemian

回答

1

更新

总结散布各种意见之间的信息:

  1. 的IP地址列是含而不留十进制值VarChar(50)串填充。这些列上的索引将按字母顺序排序,而不是数字,即“10”<“2”。 (随着左填充的排序将是正确的数值,以及: “10” - > “02”。)

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

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

原来的答案

如果在点表示法使用IPv4地址,例如“192.168.0。42" ,您可以将字符串转换成BIGINT值这个UDF:

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 

你可以在你的表往返的字符串值,使他们正确地排序,通过使用这两个功能让他们都成为“正常化”的形式。这不是一个理想的解决方案,因为它要求所有将来的插入和更新都要进行标准化,但它可能对此刻有所帮助。

+0

转换为bigint不是问题。我可以将表格结构更改为bigint,并将所有数据重新导入bigint。但是,我不完全理解的是,您是否建议为每个IP地址创建一条记录。现在,每条记录代表一系列bigint IP地址。这意味着4,294,967,296条记录,而不是现在的3,500,000条记录。 (我是TSQL的新手!) –

+0

不,你仍然可以使用地址范围。您还可以创建包含_both_“StartIPNum”和“EndIPNumb”的单个索引。这应该会对性能产生影响。 (麻木?) – HABO

0

我猜索引设置不正确。你可以提高它像这样:

  1. 走进SQL Server Management Studio中,并从菜单中打开一个新的查询窗口
  2. 选择:查询 - >包括实际的执行计划(按Ctrl + M)
  3. 键入您的查询并执行它

现在查询将会执行,并且执行计划将被显示。如果索引不好,它会告诉你一个提示,指出哪些索引缺失以及你可以做什么。

它会显示您需要创建索引精确的SQL语句。 只需复制并粘贴该语句并执行它,然后您的索引应该工作。

+0

谢谢HABO。我没有执行你提供的代码,但采纳了你的建议。 1。我改变了表格结构,所以IP现在是bigint。 2.我创建了一个以startipnum和endipnum作为索引字段的聚集索引。现在,大约需要750毫秒的查询需要大约150毫秒。感谢大家的投入! –

+0

150ms仍然太慢。顺便说一句,我不是哈博;) –