SQL Server:我应该如何构建索引以改善查找时间?

时间:2016-12-04 12:41:31

标签: sql-server database indexing database-design database-optimization

我有一个存储城市IP地址范围的表,此表中有数百万条记录。我确信很多处理IP地址的人都有类似我的表(我在本例中简化了我的表格):

CREATE TABLE [dbo].[IPRangeByCity]
(
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [IPIntegerStart] [bigint] NOT NULL,
    [IPIntegerEnd] [bigint] NOT NULL,
    [Country] [nvarchar](150) NOT NULL,
    [City] [nvarchar](150) NULL

    CONSTRAINT [pk_IPRangeByCity] 
        PRIMARY KEY CLUSTERED([ID] ASC),
) ON [PRIMARY]
 GO

现在我不保存,更新或删除此表中的任何记录。我只读了这张桌子。当我从这个表中读取时,我获取一个IPv4地址,将其转换为整数形式,并使用IPv4地址的整数形式,我在该整数的IP地址范围之间查找该城市。

例如,假设IPv4地址为" 187.245.227.116"。

" 187.245.227.116"转换为整数3153453940.然后我运行以下select语句来查找与此IP地址关联的城市:

select * from IPRangeByCity 
where 3153453940 between IPIntegerStart and IPIntegerEnd

我的问题是,如果我只使用上面的select语句从这个表中读取,我应该如何构造索引以改善select语句的查找时间?

如果我将此表的索引设置为" IPIntegerStart"列,我的选择语句可能是一个很好的索引。例如:

CONSTRAINT [pk_IPRangeByCity] PRIMARY KEY CLUSTERED([IPIntegerStart] ASC)

但是,我不太确定。根据我的select语句,有人知道为我的表设置my的最佳索引是什么?它应该是聚簇索引还是非聚簇索引?它应该是多列索引(即具有IPIntegerStart和IPIntegerEnd列的索引)吗?任何帮助,将不胜感激。感谢。

编辑:我可以在我的表格中创建主键。在此示例中,我将主键设置为ID和身份密钥。但我可以将主键更改为任何列,只要我的select语句运行得很快,这就是我所关心的。

2 个答案:

答案 0 :(得分:1)

编辑:在了解了几个细节后,之前的回答并不值得。看来您最初的想法可能是正确的:

alter table dbo.IPRangeByCity add constraint [PK_IPRangeByCity]
primary key (IPIntegerStart);

但是,您还需要IPIntegerEnd上的非聚集索引。以下是一些解释。

首先,范围与Id列之间没有重叠是假的,您可以将主键替换为指定的主键。 PK默认是聚类的,因此它可以更快地进行搜索和扫描。

其次,正如Martin Smith在评论中正确指出的那样,使用between谓词或类似逻辑的直接查询会发生大型索引扫描。但是,我认为这不是问题,因为没有范围重叠,这意味着任何IP地址都可以属于最多1个范围。因此,搜索查询可以重写如下:

select top (1) r.*
from dbo.IPRangeByCity r
where 3153453940 between r.IPIntegerStart and r.IPIntegerEnd
order by r.IPIntegerStart desc;

这样,它应该总是足够快,因为聚集索引扫描将在找到第一个合适的行后停止,或者将被IPIntegerEnd切断。请注意,这是纯粹的推测,您应该根据与您的作品相媲美的数据量来检查它。

尽管如此,我还是不会放弃类似杰克道格拉斯的非规范化方法(但不能说我理解它)。我甚至不会放弃创建所有可能的IPv4地址的完整列表并搜索它的可能性 - 它实际上并不像听起来那么荒谬。最终,这一切都取决于细节。

答案 1 :(得分:0)

我的测试结果的更新,可能是最终的答案。我特别希望这对@MartinSmith和@RogerWolf有好处,他给了我很多帮助。非常感谢Martin Smith和Roger Wolf!

请注意我的数据中没有IP范围重叠。此外,我的数据中有几百万行。

我使用Roger Wolf的方法测试了聚集索引,非聚簇索引等的一系列变体。对于该方法,将IPIntegerStart列设置为聚簇索引对性能的影响最大。

我也测试了其他变体,例如:

1)同时使IPIntegerStart和IPIntegerEnd成为聚簇索引。性能与仅使IPIntegerStart成为聚簇索引相当。

2)使IPIntegerStart和IPIntegerEnd成为非聚集索引,但将ID(标识)列保留为聚簇主键。性能比Roger Wolf的重写解决方案慢大约8倍,很可能是因为IPIntegerStart不是聚集索引。

3)使IPInteger启动聚簇索引和IPIntegerEnd非聚集索引。使IPIntegerEnd成为非聚集索引对性能没有显着影响。同样,重要的是IPIntegerStart被设置为聚集索引。

故事的寓意是,由于我设置了聚集和非聚集索引的不同变体,最终最重要的是将IPIntegerStart设置为聚簇索引对加快速度的影响最大。

现在按照dba.stackexchange.com/a/14896/3690(最初由杰克道格拉斯编写)的规定,提出马丁史密斯的建议:

4)杰克道格拉斯的方法的一般概念是桌子被分成不同的部分(杰克道格拉斯在他的写作中称之为“颗粒”)。我将表格拆分为4个段而不是3个。表存储的数据越多,如果要加快查找速度,就可以添加的段越多。请注意,您添加的段越多,您在select语句中执行的联合越多 - 如果您有太多段,我不确定其他联合最终是否会增加更多开销。 Jack Douglas的方法产生了最快的查找速度,比将IPIntegerStart设置为聚簇索引的基本方法快了约3到4倍。

所以,这里是3种不同方法的总结:

方法1)使用ID(身份密钥)作为聚集索引:最慢的方法

方法2)Roger Wolf将聚簇索引设置为IPIntegerStart,并将IPIntegerEnd设置为非聚集索引的方法:比方法1快约6至7倍)

方法3)Martin Smith的建议或杰克道格拉斯将表格分割成“颗粒”的方法:比方法2快3到4倍)