Производительность TSQL - ПРИСОЕДИНЯЙТЕСЬ к значению МЕЖДУ min и max

10

У меня есть две таблицы, в которых я храню:

  • диапазон 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 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-адрес нескольким странам? Если нет, вы можете сузить свой ПК до просто begin_num. Мне также приходится присоединяться A BETWEEN B AND Cдовольно часто, и мне любопытно узнать, есть ли способ добиться этого без утомительных присоединений RBAR.
Джон на все руки
1
Это немного не по теме для вашего вопроса, но я бы подумал о создании begin_ipи end_ipсохранении вычисляемых столбцов, чтобы не допустить какой-либо синхронизации текста и чисел.
Джон на все руки
@ w0lf: есть ли перекрывающиеся диапазоны в ip2country (begin_num, end_num)?
ypercubeᵀᴹ
@JonofAllTrades обычно один IP должен принадлежать одной стране, поэтому я думаю, что ваша идея запроса типа give me the first record that has a begin_num < ip in asc order of begin_num(поправьте меня, если я ошибаюсь) могла бы быть правильной и повысить производительность.
Кристиан Лупаску
1
@ w0lf: У меня сложилось впечатление, что именно так и поступает сервер в подобном случае, потому что он сначала просматривает begin_num, а затем просматривает end_numвнутри этого набора и находит только одну запись.
Джон на все руки

Ответы:

3

Вам нужен дополнительный индекс. В вашем примере со скрипкой я добавил:

CREATE UNIQUE INDEX ix_IP ON Request(CategoryID, IP)

Который охватывает вас для таблицы запросов и получает поиск по индексу вместо сканирования кластерного индекса.

Посмотрите, как это улучшит, и дайте мне знать. Я предполагаю, что это очень поможет, так как сканирование по этому индексу, я уверен, не дешево.

JNK
источник
Я не знаю почему, но результаты кажутся разными (в SQLFiddle)
Кристиан Лупаску
@ w0lf: они разные (вероятно), потому что вы оба вставляете случайные данные в таблицы.
ypercubeᵀᴹ
@ypercube, конечно, в этом причина. В последнее время я сделал так много вещей, что забыл, что данные случайные. Сожалею.
Кристиан Лупаску
2

Всегда есть подход грубой силы: вы можете взорвать свою карту IP. Соедините таблицу чисел с вашей существующей картой, чтобы создать одну запись для каждого IP-адреса. Это только 267 тысяч записей на основе ваших данных Fiddle, никаких проблем.

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Конечно, это имеет смысл, только если вы делаете относительно мало обновлений .

Я надеюсь, что у кого-то есть лучшее решение!

Джон на все руки
источник
Весь набор данных даст более 5 миллиардов записей, поэтому я не думаю, что сделаю это. Но, тем не менее, это хорошая идея; Я уверен, что это возможно во многих подобных случаях. +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
Винс Перголиззи
источник
спасибо, я попробовал ваш подход, но он кажется дороже, чем первоначальный запрос
Кристиан Лупаску
Сколько строк у вас в каждой таблице? Я хотел бы воспроизвести масштаб вашей проблемы на моей БД и попытаться решить без добавления индекса :)
Винс Перголицци
около 200 000 в IP2Country и несколько миллионов (возможно, десятки миллионов в ближайшем будущем) в запросе. Я думаю, что если вы решите это без индексов, вы заслужите звание «DBA года» :)
Кристиан Лупаску