我有一个存储城市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语句运行得很快,这就是我所关心的。
答案 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倍)