在两个 bigint 列之间搜索的查询的最佳索引策略

Bri*_*hek 4 index sql-server optimization clustered-index nonclustered-index

我在 MS-SQL 中有下表:

CREATE TABLE [dbo].[dbip_locations](
    [ip_from] [bigint] NOT NULL,
    [ip_to] [bigint] NOT NULL,
    [country_code] [nvarchar](64) NOT NULL,
    [region_name] [nvarchar](128) NOT NULL,
    [city_name] [nvarchar](128) NOT NULL,
    [latitude] [float] NOT NULL,
    [longitude] [float] NOT NULL
)
Run Code Online (Sandbox Code Playgroud)

ip_from 和 ip_to 列是根据 ipv4 地址计算的,如下所示:

return (
      convert(bigint, parsename(@ip, 1)) +
      convert(bigint, parsename(@ip, 2)) * convert(bigint, 256) +
      convert(bigint, parsename(@ip, 3)) * convert(bigint, 65536) +
      convert(bigint, parsename(@ip, 4)) * convert(bigint, 16777216)
    )
Run Code Online (Sandbox Code Playgroud)

然后,我使用使用上述计算转换为 bigint 的 ipv4 地址来搜索 ip 地址位于 ip_from 和 ip_to 列之间的行。

我总是会找到一行,虽然它不是由模式强制执行的,但这是数据中的现实。

这是查询:

SELECT TOP(1) [latitude], [longitude] FROM [dbo].[dbip_locations] WHERE @ip_int BETWEEN ip_from AND ip_to
Run Code Online (Sandbox Code Playgroud)

我目前在 ip_from 和 ip_to 列上都有两个非聚集的、非唯一的索引。查询执行得非常快,但我每秒执行大量此类查询,并且想知道是否可以通过使用不同的索引获得更好的性能?也许是聚集多列索引或使用唯一索引?

我可以使用更好的索引吗?

Dan*_*man 5

我认为具有 ip_from 和 ip_to 的复合索引对于此范围搜索将是最有效的。如果返回的唯一列是纬度和经度,则应将这些查询作为非集群中的非键列包含在内,以获得此特定查询的最佳性能。索引应该被聚集而不是你经常返回其他列。

由于您的查询TOP (1)没有指定ORDER BY,因此可能会在范围搜索内返回任何任意行。如果您每次都需要返回同一行,请使用列指定“ORDER BY”以唯一标识行。


ype*_*eᵀᴹ 5

在搜索将始终返回 1 行(或无)的假设下,即(ip_from, ip_to)间隔不重叠,查询可以变得更高效:

使用特定ORDER BY且仅ip_from用于搜索。在我们找到符合这个条件的第一行之后,我们也检查第二个条件:

SELECT [latitude], [longitude] 
FROM
    ( SELECT TOP (1) 
          [latitude], [longitude], ip_to 
      FROM [dbo].[dbip_locations] 
      WHERE ip_from <= @ip_int
      ORDER BY ip_from DESC
    ) AS t
WHERE @ip_int <= ip_to ;
Run Code Online (Sandbox Code Playgroud)

一个简单的索引就(ip_from)足够了,查询将执行单个索引查找,然后对表中的相关行进行另一次读取(在堆或 CI 上)。

如果您想要更好的性能,请在(ip_from) INCLUDE (ip_to, latitude, longitude).


我应该补充一点,上述复合索引也将有助于您的原始查询。查询还需要索引查找。那么,有什么区别呢?

可能至关重要的区别在于,即使使用简单索引,当它返回 0 结果时,该查询的性能也会好得多,即。当您搜索不在表中存储的任何间隔中的 IP 时。因为它将执行单索引查找,找到第一个匹配的行,然后检查第二个条件(通过INCLUDE列或堆/CI)并拒绝它。

您的查询必须执行索引查找,然后遍历索引的整个其余部分(因此平均为完整索引扫描的 1/2),结果却发现没有与第二个条件 about 匹配的行ip_to