Kinerja TSQL - BERGABUNG dengan nilai ANTARA min dan maks

10

Saya memiliki dua meja tempat saya menyimpan:

  • tabel rentang IP - negara pencarian
  • daftar permintaan yang berasal dari IP yang berbeda

IP disimpan sebagai bigintuntuk meningkatkan kinerja pencarian.

Ini adalah struktur tabel:

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
)

Saya ingin mendapatkan rincian permintaan per negara, jadi saya melakukan permintaan berikut:

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

Saya memiliki banyak catatan di tabel: sekitar 200.000 IP2Countrydan beberapa juta Request, sehingga permintaan membutuhkan waktu.

Melihat rencana eksekusi, bagian yang paling mahal adalah Pencarian Indeks Clustered pada indeks PK_IP2Country, yang dieksekusi berkali-kali (jumlah baris dalam Permintaan).

Juga, sesuatu yang saya merasa sedikit aneh adalah left join ip2country ic on r.IP between ic.begin_num and ic.end_numbagian (tidak tahu apakah ada cara yang lebih baik untuk melakukan pencarian).

Struktur tabel, beberapa sampel data, dan kueri tersedia dalam SQLFiddle: http://www.sqlfiddle.com/#!3/a463e/3 (sayangnya saya tidak berpikir saya bisa memasukkan banyak catatan untuk mereproduksi masalah, tapi ini semoga memberi ide).

Saya (jelas) bukan ahli dalam kinerja / optimasi SQL, jadi pertanyaan saya adalah: Apakah ada cara yang jelas di mana struktur / kueri ini dapat ditingkatkan berdasarkan kinerja yang saya lewatkan?

Cristian Lupascu
sumber
2
Bisakah alamat IP memetakan ke beberapa negara? Jika tidak, Anda dapat mempersempit PK Anda menjadi adil begin_num. Saya juga harus A BETWEEN B AND Csering bergabung , dan saya ingin tahu apakah ada cara untuk mencapai ini tanpa bergabung dengan RBAR yang membosankan.
Jon of All Trades
1
Ini sedikit di luar topik untuk pertanyaan Anda, tapi saya akan mempertimbangkan membuat begin_ipdan end_ipbertahan kolom terhitung, untuk mencegah kemungkinan teks dan angka keluar dari sinkronisasi entah bagaimana.
Jon of All Trades
@ w0lf: apakah ada rentang yang tumpang tindih ip2country (begin_num, end_num)?
ypercubeᵀᴹ
@JonofAllTrades biasanya satu IP harus dimiliki oleh satu negara, jadi saya pikir ide Anda tentang permintaan seperti give me the first record that has a begin_num < ip in asc order of begin_num(koreksi saya jika saya salah) dapat menjadi valid dan meningkatkan kinerja.
Cristian Lupascu
1
@ w0lf: Kesan saya adalah pada dasarnya itulah yang dilakukan server dalam kasus seperti ini, karena pertama kali memindai begin_num, kemudian memindai end_numdalam set itu dan hanya menemukan satu catatan.
Jon of All Trades

Jawaban:

3

Anda memerlukan indeks tambahan. Dalam contoh Fiddle Anda, saya menambahkan:

CREATE UNIQUE INDEX ix_IP ON Request(CategoryID, IP)

Yang mencakup Anda untuk tabel permintaan dan mendapatkan pencarian indeks alih-alih pemindaian indeks berkerumun.

Lihat bagaimana meningkatkannya dan beri tahu saya. Saya kira itu akan membantu sedikit karena pemindaian pada indeks itu saya yakin tidak murah.

JNK
sumber
Saya tidak tahu mengapa, tetapi hasilnya tampaknya berbeda (dalam SQLFiddle)
Cristian
@ w0lf: mereka berbeda (mungkin) karena Anda berdua memasukkan data acak ke dalam tabel.
ypercubeᵀᴹ
@ ypercube pasti itu penyebabnya. Saya telah melakukan banyak hal belakangan ini sehingga saya lupa bahwa data itu acak. Maaf.
Cristian Lupascu
2

Selalu ada pendekatan brute-force: Anda bisa meledak peta IP Anda. Gabung tabel angka dengan peta Anda yang ada untuk membuat satu catatan per alamat IP. Itu hanya catatan 267K berdasarkan data Fiddle Anda, tidak ada masalah sama sekali.

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

Ini akan membuat pencarian menjadi lebih sederhana, dan semoga lebih cepat. Ini hanya masuk akal jika Anda membuat pembaruan relatif sedikit ip2country, tentu saja.

Saya harap orang lain memiliki solusi yang lebih baik!

Jon dari Semua Perdagangan
sumber
Seluruh kumpulan data akan menghasilkan lebih dari 5 miliar catatan, jadi saya tidak berpikir saya akan melakukannya. Namun ini adalah ide yang bagus; Saya yakin ini layak dalam banyak kasus serupa. +1
Cristian Lupascu
0

Coba ini:

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
Vince Pergolizzi
sumber
terima kasih, saya telah mencoba pendekatan Anda, tetapi tampaknya lebih mahal daripada permintaan awal
Cristian Lupascu
Berapa banyak baris yang Anda miliki di setiap tabel? Saya ingin mereproduksi skala masalah Anda pada DB saya dan mencoba menyelesaikannya tanpa menambahkan indeks :)
Vince Pergolizzi
sekitar 200.000 di IP2Country dan beberapa juta (mungkin puluhan juta dalam waktu dekat) di Request. Saya pikir jika Anda menyelesaikannya tanpa indeks, Anda layak mendapat gelar "DBA tahun ini" :)
Cristian Lupascu