TSQL性能-最小和最大之间的值JOIN


10

我有两个表存储在其中:

  • IP范围-国家/地区查询表
  • 来自不同IP的请求列表

IP存储为,bigint以提高查找性能。

这是表结构:

create table [dbo].[ip2country](
    [begin_ip] [varchar](15) NOT NULL,
    [end_ip] [varchar](15) NOT NULL,
    [begin_num] [bigint] NOT NULL,
    [end_num] [bigint] NOT NULL,
    [IDCountry] [int] NULL,
    constraint [PK_ip2country] PRIMARY KEY CLUSTERED 
    (
        [begin_num] ASC,
        [end_num] ASC
    )
)

create table Request(
    Id int identity primary key, 
    [Date] datetime, 
    IP bigint, 
    CategoryId int
)

我想按国家/地区细分请求,因此执行以下查询:

select 
    ic.IDCountry,
    count(r.Id) as CountryCount
from Request r
left join ip2country ic 
  on r.IP between ic.begin_num and ic.end_num
where r.CategoryId = 1
group by ic.IDCountry

我在表中有很多记录:大约200,000 in IP2Country和数百万in Request,因此查询需要一段时间。

从执行计划来看,最昂贵的部分是对索引PK_IP2Country的聚集索引寻求,该索引被执行多次(Request中的行数)。

另外,我对此感到有些奇怪的是该left join ip2country ic on r.IP between ic.begin_num and ic.end_num部分(不知道是否有更好的方法来执行查找)。

表结构,一些示例数据和查询在SQLFiddle中可用:http ://www.sqlfiddle.com/#!3 / a463e /3(不幸的是,我认为我不能插入很多记录来重现该问题,但这希望给出一个想法)。

我(显然)不是SQL性能/优化方面的专家,所以我的问题是:是否有任何明显的方法可以改善我所缺少的结构/查询的性能?


2
IP地址可以映射到多个国家吗?如果没有,您可以将PK缩小为just begin_num。我也必须A BETWEEN B AND C经常加入,我很好奇,如果没有繁琐的RBAR加入,是否有办法实现这一目标。
所有行业的乔恩(Jon of All Trades)

1
您的问题有点偏离主题了,但是我会考虑保留begin_ipend_ip保留计算出的列,以防止文本和数字以某种方式不同步的可能性。
所有行业的乔恩(Jon of All Trades)2012年

@ w0lf:中是否存在重叠范围ip2country (begin_num, end_num)
ypercubeᵀᴹ

@JonofAllTrades通常一个IP应该属于一个国家,所以我认为您的查询想法give me the first record that has a begin_num < ip in asc order of begin_num(如果我错了,请纠正我)可能是有效的并且可以提高性能。
克里斯蒂安·卢帕斯库

1
@ w0lf:我的印象是,在这种情况下,这基本上就是服务器的工作,因为它首先扫描by begin_num,然后end_num在该集合内进行扫描,仅找到一条记录。
所有行业的乔恩(Jon of All Trades)

Answers:


3

您需要其他索引。 在您的Fiddle示例中,我添加了:

CREATE UNIQUE INDEX ix_IP ON Request(CategoryID, IP)

它涵盖了请求表,并获得了索引查找,而不是聚集索引扫描。

看看如何改进它,并让我知道。我猜它会有所帮助,因为我确信对该索引进行扫描并不便宜。


我不知道为什么,但是结果似乎有所不同(在SQLFiddle中)
Cristian Lupascu 2012年

@ w0lf:它们是不同的(可能),因为您都在向表中插入随机数据。
ypercubeᵀᴹ

@ypercube肯定是原因。最近我做了很多事情,以至于我忘记了数据是随机的。抱歉。
克里斯蒂安·卢帕斯库

2

总是存在暴力手段:您可能会爆炸IP地图。将数字表与您现有的地图连接起来,以为每个IP地址创建一条记录。根据您的Fiddle数据而来的只有267K记录,完全没有问题。

CREATE TABLE IPLookup
  (
  IP  BIGINT PRIMARY KEY,
  CountryID  INT
  )
INSERT INTO IPLookup (IP, CountryID)
  SELECT
    N.Number, Existing.IDCountry
  FROM
    ip2country AS Existing
    INNER JOIN Numbers AS N ON N.Number BETWEEN Existing.begin_num AND Existing.end_num

这将使查找更简单,并且希望更快。当然,仅当您对进行相对较少的更新时ip2country,这才有意义。

希望其他人有更好的解决方案!


整个数据集将产生超过50亿条记录,所以我认为我不会这样做。但这仍然是一个好主意。我确信这在许多类似情况下都是可行的。+1
克里斯蒂安·卢帕斯库

0

尝试这个:

SELECT ic.IDCountry,
        COUNT(r.Id) AS CountryCount
FROM Request r
INNER JOIN (SELECT begin_num+NUMS.N [IP], IDCountry 
            FROM ip2country
            CROSS JOIN (SELECT TOP(SELECT ABS(MAX(end_num-begin_num)) FROM ip2country) ROW_NUMBER() OVER(ORDER BY sc.name)-1 [N]
                        FROM sys.columns sc) NUMS
            WHERE begin_num+NUMS.N <= end_num) ic
ON r.IP = ic.IP
WHERE r.CategoryId = 1
GROUP BY ic.IDCountry

谢谢,我已经尝试过您的方法,但是它似乎比初始查询要贵
Cristian Lupascu 2012年

每个表中有几行?我想在我的数据库上重现您的问题的规模,并尝试在不添加索引的情况下进行解决:)
Vince Pergolizzi 2012年

在IP2Country中约有200,000个请求,在Request中有数百万个(可能在不久的将来有数千万个)。我认为,如果您在没有索引的情况下解决问题,那么您应该得到“年度DBA”称号:)
Cristian Lupascu 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.