我正在使用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
答案 0 :(得分:1)
<强>更新强>:
总结各种评论中散布的信息:
IP地址列是VarChar(50)
个字符串,包含没有左边填充的十进制值。这些列的索引将按字母顺序排序,而不是数字排序,即“10”&lt; “2”。 (使用左边填充,排序也将在数字上正确:“10”&gt;“02”。)
WHERE
子句(where @DecimalIP between GeoBlocks.StartIPNum and GeoBlocks.EndIPNumb
)使用混合数据类型。 @DecimalIP
是BIGINT
,而两列是VarChar(50)
。 SQL通过实现数据类型优先级方案来处理混合数据类型之间的操作。 (Ref。)这会导致每行中的IP地址从字符串转换为BIGINT
值,因此比较以数字方式完成,“预期”结果以相当大的成本返回。在这种情况下,索引(除了)之外都是无用的。
将列更改为BIGINT
将允许使用索引来提高性能并确保以数字方式而不是按字母顺序进行比较。包含StartIPNum
和EndIPNumb
列的单个索引将大大提高性能。请注意,如果不允许重叠的地址范围,则索引在StartIPNum
上实际上是唯一的,并且可以替换为StartIPNum
上的索引,EndIPNumb
作为包含的列表现。
原始答案:
如果您使用点分表示的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)
我猜这个索引没有正确设置。你可以像这样改进它:
现在将执行查询,并将显示执行计划。如果索引不好,它会向您显示缺少哪个索引以及您可以执行的操作。
它将显示创建索引所需的精确SQL语句。 只需复制+粘贴该语句并执行它,然后您的索引就可以正常工作。