2つのテーブルを保管しています。
ルックアップのパフォーマンスを向上させるために、IPはbigint
sとして保存されました。
これはテーブル構造です:
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 _IP2Country
とRequest
には数百万個あるため、クエリにはしばらく時間がかかります。
実行プランを見ると、最もコストのかかる部分は、インデックスPK_IP2Countryに対するクラスター化インデックスシークであり、これは何度も実行されます(リクエストの行数)。
また、私が少し奇妙に感じるのはleft join ip2country ic on r.IP between ic.begin_num and ic.end_num
部分(ルックアップを実行するためのより良い方法があるかどうかわからない)。
テーブル構造、いくつかのサンプルデータ、およびクエリはSQLFiddleで利用できます: http://www.sqlfiddle.com/#!3/a463e/ (残念ながら、多くのレコードを挿入できるとは思わない問題を再現しますが、これでうまくいくと思います)。
私は(明らかに)SQLのパフォーマンス/最適化の専門家ではないので、私の質問は、この構造/クエリをパフォーマンス面で改善できる明らかな方法がありませんか?
追加のインデックスが必要です。 あなたのFiddle私が追加した例:
CREATE UNIQUE INDEX ix_IP ON Request(CategoryID, IP)
これは、要求テーブルをカバーし、クラスター化インデックススキャンの代わりにインデックスシークを取得します。
それがどのようにそれを改善するかを見て、私に知らせてください。そのインデックスのスキャンは安くはないので、それはかなり役立つと思います。
強引なアプローチは常にあります。IPマップを爆発させる可能性があります。既存のマップに対して数値テーブルを結合して、IPアドレスごとに1つのレコードを作成します。これは、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
に対して比較的少ない更新を行う場合にのみ意味があります。
他の誰かがより良い解決策を持っていることを願っています!
これを試して:
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