Продуктивність TSQL - ПРИЄДНАЙТЕ за значенням МІЖ та макс


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

Вам потрібен додатковий індекс. У вашому прикладі Fiddle я додав:

CREATE UNIQUE INDEX ix_IP ON Request(CategoryID, IP)

Що охоплює таблицю запитів і отримує пошук за індексом замість кластерного сканування індексу.

Подивіться, як це покращує це, і дайте мені знати. Я здогадуюсь, що це допоможе трохи, оскільки сканування цього індексу, я впевнений, не з дешевих.


Не знаю чому, але результати здаються різними (у SQLFiddle)
Крістіан Лупаску,

@ w0lf: вони різні (проблема), оскільки ви обидва вставляєте випадкові дані в таблиці.
ypercubeᵀᴹ

@ypercube, безумовно, це причина. Я зробив так багато речей останнім часом, що забув, що дані були випадковими. Вибачте.
Крістіан Лупаску

2

Завжди є грубий підхід: ви можете підірвати свою IP-карту. Приєднайтеся до таблиці чисел у відповідності до наявної карти, щоб створити один запис на IP-адресу. Це лише 267K записів на основі даних 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

дякую, я спробував ваш підхід, але, здається, він дорожчий за початковий запит
Крістіан Лупаску,

Скільки рядків у кожній таблиці? Я хотів би відтворити масштаб вашої проблеми на моїй БД і спробувати вирішити, не додаючи індекс :)
Vince Pergolizzi

близько 200 000 в IP2Країні та кілька мільйонів (можливо, десятки мільйонів найближчим часом) у Запит. Я думаю, якщо вирішити це без індексів, ти заслуговуєш назви "DBA року" :)
Крістіан Лупаску
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.