TSQL performansı - BETWEEN min ve maks. Değerlerinde JOIN


10

İçinde depoladığım iki tablo var:

  • IP aralığı - ülke arama tablosu
  • farklı IP'lerden gelen isteklerin bir listesi

IP'ler, bigintarama performansını artırmak için s olarak saklandı .

Bu tablo yapı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
)

Ülke başına istek dökümü almak istiyorum, bu yüzden aşağıdaki sorguyu gerçekleştirmek:

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

Tablolarda kayıtları bir sürü var: yaklaşık 200.000 inç IP2Countryve birkaç milyon Request, bu yüzden sorgu biraz zaman alır.

Yürütme planına bakıldığında, en pahalı kısım, PK_IP2Country dizininde, birçok kez yürütülen (İstek'teki satır sayısı) bir Kümelenmiş Dizin Aramasıdır.

Ayrıca, biraz garip hissettiğim bir şey left join ip2country ic on r.IP between ic.begin_num and ic.end_numkısmı (aramayı gerçekleştirmenin daha iyi bir yolu olup olmadığını bilmiyorum).

Tablo yapısı, bazı örnek veriler ve sorgu SQLFiddle'da mevcuttur: http://www.sqlfiddle.com/#!3/a463e/3 (ne yazık ki sorunu yeniden oluşturmak için birçok kayıt ekleyebileceğimi sanmıyorum, ancak bu umarım bir fikir verir).

Ben (açıkçası) SQL performans / optimizasyon konusunda uzman değilim, bu yüzden sorum şu: Bu yapı / sorgunun eksik olduğum performans açısından akıllıca geliştirilebileceği herhangi bir belirgin yol var mı?


2
Bir IP adresi birden fazla ülkeyle eşleşebilir mi? Değilse, PK'nizi sadece daraltabilirsiniz begin_num. Ayrıca A BETWEEN B AND Coldukça sık katılmalıyım ve bunu sıkıcı RBAR birleşmeleri olmadan başarmanın bir yolu olup olmadığını merak ediyorum.
Tüm Ticaretten Jon

1
Sorunuzla ilgili biraz konu dışı, ancak metnin ve sayıların bir şekilde senkronizasyondan çıkma olasılığını önlemek için hesaplanmış sütunlar oluşturmayı begin_ipve end_ipdevam ettirmeyi düşünürdüm .
Tüm Ticaretten Jon

@ w0lf: Çakışan aralıklar var ip2country (begin_num, end_num)mı?
ypercubeᵀᴹ

@JonofAllTrades normalde bir IP tek bir ülkeye ait olmalıdır, bu yüzden sanırım give me the first record that has a begin_num < ip in asc order of begin_num(yanlışsam beni düzelt) gibi bir sorgu fikriniz geçerli olabilir ve performansı artırabilir.
Cristian Lupascu

1
@ w0lf: Benim izlenimlerim, temelde sunucunun böyle bir durumda yaptığı şeydir, çünkü önce tarar begin_num, sonra end_numbu kümenin içinde tarar ve sadece bir kayıt bulur.
Tüm Ticaretten Jon

Yanıtlar:


3

Ek bir dizine ihtiyacınız var. Fiddle örneğinize şunları ekledim:

CREATE UNIQUE INDEX ix_IP ON Request(CategoryID, IP)

Bu, istek tablosu için sizi kapsar ve kümelenmiş bir dizin taraması yerine bir dizin araması alır.

Bunun nasıl geliştiğini görün ve bana bildirin. Ben bu endeksi tarama ucuz değil eminim beri biraz yardımcı olacaktır tahmin ediyorum.


Neden bilmiyorum, ama sonuçlar farklı görünüyor (SQLFiddle'da)
Cristian Lupascu

@ w0lf: ikisi de farklı (muhtemelen) çünkü ikiniz de tablolara rastgele veri ekliyorsunuz.
ypercubeᵀᴹ

@ypercube kesinlikle nedeni budur. Son zamanlarda o kadar çok şey yaptım ki verilerin rastgele olduğunu unuttum. Afedersiniz.
Cristian Lupascu

2

Her zaman kaba kuvvet yaklaşımı vardır: IP haritanızı patlatabilirsiniz. Her IP adresi için bir kayıt oluşturmak üzere mevcut haritanıza karşı bir sayılar tablosuna katılın. Bu sadece Fiddle verilerinize dayanan 267K kayıttır, hiç sorun değil.

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

Bu, aramaları daha basit ve umarım daha hızlı hale getirir. Bu ip2country, elbette nispeten az güncelleme yaparsanız mantıklıdır .

Umarım başka birinin daha iyi bir çözümü vardır!


Tüm veri seti 5 milyardan fazla kayıt üretecek, bu yüzden yapacağımı sanmıyorum. Ama yine de bu güzel bir fikir; Eminim birçok benzer durumda uygulanabilir. +1
Cristian Lupascu

0

Bunu dene:

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

teşekkürler, yaklaşımınızı denedim, ancak ilk sorgudan daha pahalı görünüyor
Cristian Lupascu

Her tabloda kaç satır var?
Sorunumun ölçeğini DB'mde

IP2Country'de yaklaşık 200.000 ve Request'de birkaç milyon (yakın gelecekte muhtemelen on milyonlarca). Bence indeksler olmadan
çözerseniz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.