TSQLのパフォーマンス-値の最小値と最大値の間のJOIN


10

私が保存している2つのテーブルがあります。

  • IP範囲-国ルックアップテーブル
  • 異なるIPからのリクエストのリスト

IPはbigints として保存され、ルックアップのパフォーマンスが向上しました。

これはテーブル構造です:

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

テーブルには多くのレコードがあります。で約20万件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 /3(残念ながら、問題を再現するために多くのレコードを挿入することはできないと思いますが、これはうまくいけばアイデアが出ます)。

私は(明らかに)SQLのパフォーマンス/最適化の専門家ではないので、私の質問は次のとおりです。この構造/クエリをパフォーマンス面で改善できる明らかな方法はありますか?


2
IPアドレスを複数の国にマッピングできますか?そうでない場合は、PKをちょうどに絞り込むことができますbegin_num。私もA BETWEEN B AND Cかなり頻繁に参加する必要があり、面倒なRBAR参加なしでこれを達成する方法があるかどうか知りたいです。
Jon of All Trades

1
それはあなたの質問には少し外れていますが、テキストと数値が何らかの理由で同期しなくなる可能性を防ぐために、計算列を作成begin_ipしてend_ip永続化することを検討します。
Jon of All Trades

@ w0lf:重複する範囲はありip2country (begin_num, end_num)ますか?
ypercubeᵀᴹ

@JonofAllTrades通常、1つのIPは1つの国に属している必要があるため、give me the first record that has a begin_num < ip in asc order of begin_num(間違っている場合は修正してください)のようなクエリのアイデアは有効であり、パフォーマンスを向上させることができます。
クリスティアンルパスク

1
@ w0lf:最初はでスキャンしbegin_num、次にend_numそのセット内をスキャンして1つのレコードしか見つからないため、このような場合、サーバーは基本的にこれを実行しているようです。
Jon of All Trades

回答:


3

追加のインデックスが必要です。 あなたのフィドルの例では、私が追加しました:

CREATE UNIQUE INDEX ix_IP ON Request(CategoryID, IP)

これは要求テーブルをカバーし、クラスター化インデックススキャンの代わりにインデックスシークを取得します。

それがどのようにそれを改善するかを見て、私に知らせてください。そのインデックスのスキャンは確かに安くはないので、それはかなり役立つと思います。


理由はわかりませんが、結果は異なるようです(SQLFiddle)
Cristian Lupascu

@ w0lf:どちらもランダムなデータをテーブルに挿入しているため、(おそらく)異なっています。
ypercubeᵀᴹ

@ypercube確かにそれが原因です。私は最近非常に多くのことを行ったので、データがランダムであることを忘れました。ごめんなさい。
クリスティアンルパスク

2

強引なアプローチは常にあります。IPマップを爆発させる可能性があります。既存のマップに対して数値テーブルを結合して、IPアドレスごとに1つのレコードを作成します。これは、フィドルのデータに基づく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

各テーブルにはいくつの行がありますか?私は私のDBで問題の規模を再現し、インデックスを追加せずに解決しようと思います:)
Vince Pergolizzi

IP2Countryで約20万件、Requestで数百万件(近い将来には数千万件)になる可能性があります。インデックスなしでそれを解決すると、「今年のDBA」の称号に値すると思います:)
Cristian Lupascu
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.