web-dev-qa-db-ja.com

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

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

テーブルに多くのレコードがあります。約200,000 _IP2CountryRequestには数百万個あるため、クエリにはしばらく時間がかかります。

実行プランを見ると、最もコストのかかる部分は、インデックス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のパフォーマンス/最適化の専門家ではないので、私の質問は、この構造/クエリをパフォーマンス面で改善できる明らかな方法がありませんか?

10

追加のインデックスが必要です。 あなたのFiddle私が追加した例:

CREATE UNIQUE INDEX ix_IP ON Request(CategoryID, IP)

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

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

3
JNK

強引なアプローチは常にあります。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に対して比較的少ない更新を行う場合にのみ意味があります。

他の誰かがより良い解決策を持っていることを願っています!

2

これを試して:

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
0