Поганий підзапит із порівнянням дат


15

Використовуючи підзапит для пошуку загальної кількості всіх попередніх записів із відповідним полем, продуктивність на столі з жахливими записами на 50 тис. Без підзапиту запит виконується за кілька мілісекунд. З підзапитом час виконання - вище хвилини.

Для цього запиту результат повинен:

  • Включіть лише ті записи в заданий діапазон дат.
  • Включіть кількість усіх попередніх записів, не включаючи поточний запис, незалежно від діапазону дат.

Основна схема таблиці

Activity
======================
Id int Identifier
Address varchar(25)
ActionDate datetime2
Process varchar(50)
-- 7 other columns

Приклад даних

Id  Address     ActionDate (Time part excluded for simplicity)
===========================
99  000         2017-05-30
98  111         2017-05-30
97  000         2017-05-29
96  000         2017-05-28
95  111         2017-05-19
94  222         2017-05-30

очікувані результати

Для діапазону дат 2017-05-29до2017-05-30

Id  Address     ActionDate    PriorCount
=========================================
99  000         2017-05-30    2  (3 total, 2 prior to ActionDate)
98  111         2017-05-30    1  (2 total, 1 prior to ActionDate)
94  222         2017-05-30    0  (1 total, 0 prior to ActionDate)
97  000         2017-05-29    1  (3 total, 1 prior to ActionDate)

Записи 96 і 95 виключаються з результату, але включаються в PriorCountпідзапит

Поточний запит

select 
    *.a
    , ( select count(*) 
        from Activity
        where 
            Activity.Address = a.Address
            and Activity.ActionDate < a.ActionDate
    ) as PriorCount
from Activity a
where a.ActionDate between '2017-05-29' and '2017-05-30'
order by a.ActionDate desc

Поточний індекс

CREATE NONCLUSTERED INDEX [IDX_my_nme] ON [dbo].[Activity]
(
    [ActionDate] ASC
)
INCLUDE ([Address]) WITH (
    PAD_INDEX = OFF, 
    STATISTICS_NORECOMPUTE = OFF, 
    SORT_IN_TEMPDB = OFF, 
    DROP_EXISTING = OFF, 
    ONLINE = OFF, 
    ALLOW_ROW_LOCKS = ON, 
    ALLOW_PAGE_LOCKS = ON
)

Питання

  • Які стратегії можна використати для підвищення ефективності цього запиту?

Редагувати 1
У відповідь на питання, що я можу змінити в БД: я можу змінювати індекси, тільки не структуру таблиці.

Редагувати 2
Зараз я додав базовий індекс у Addressстовпчик, але це, схоже, не покращило. В даний час я знаходжу набагато кращі показники зі створенням темп-таблиці та вставкою значень без, PriorCountа потім оновленням кожного рядка з їх конкретними підрахунками.

Редагувати 3
Проблема, яку знайшов золотник індексу Джо Оббіш (прийнята відповідь). Після того, як я додав новий nonclustered index [xyz] on [Activity] (Address) include (ActionDate), час запитів зменшився від хвилини до менше секунди, не використовуючи тимчасову таблицю (див. Редагування 2).

Відповіді:


17

З визначенням індексу, яке у вас є IDX_my_nme, SQL Server зможе шукати використання ActionDateстовпця, але не Addressстовпця. Індекс містить усі стовпці, необхідні для покриття підзапиту, але він, ймовірно, не дуже вибірковий для цього підзапита. Припустимо, що майже всі дані таблиці мають ActionDateзначення раніше, ніж '2017-05-30'. Пошуки ActionDate < '2017-05-30'повернуть майже всі рядки з індексу, які додатково відфільтруються після вилучення рядка з індексу. Якщо ваш запит повертає 200 рядків, ви, ймовірно, робите майже 200 повних сканувань покажчикаIDX_my_nme , а це означає, що ви прочитаєте близько 50000 * 200 = 10 мільйонів рядків з індексу.

Можливо, пошук Addressвашого запиту буде набагато вибірковішим для вашого підзапиту, хоча ви не надали нам повної статистичної інформації про запит, тож це припущення з мого боку. Однак припустимо, що ви створюєте індекс на справедливій Addressі ваша таблиця має 10 к унікальних значень дляAddress . З новим індексом SQL Server потрібно буде лише шукати 5 рядків з індексу для кожного виконання підзапиту, тож ви зчитуєте близько 200 * 5 = 1000 рядків з індексу.

Я тестую проти SQL Server 2016, тому можуть бути незначні синтаксичні відмінності. Нижче наведено декілька зразкових даних, у яких я зробив подібні припущення для вищезгаданих щодо розподілу даних:

CREATE TABLE #Activity (
    Id int NOT NULL,
    [Address] varchar(25) NULL,
    ActionDate datetime2 NULL,
    FILLER varchar(100),
    PRIMARY KEY (Id)
);

INSERT INTO #Activity WITH (TABLOCK)
SELECT TOP (50000) -- 50k total rows
x.RN
, x.RN % 10000 -- 10k unique addresses
, DATEADD(DAY, x.RN / 100, '20160201') -- 100 rows per day
, REPLICATE('Z', 100)
FROM
(
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) x;

CREATE NONCLUSTERED INDEX [IDX_my_nme] ON #Activity
([ActionDate] ASC) INCLUDE ([Address]);

Я створив ваш індекс, як описано в питанні. Я перевіряю цей запит, який повертає ті самі дані, що і запитання:

select 
    a.*
    , ( select count(*) 
        from #Activity Activity
        where 
            Activity.[Address] = a.[Address]
            and Activity.ActionDate < a.ActionDate
    ) as PriorCount
from #Activity a
where a.ActionDate between '2017-05-29' and '2017-05-30'
order by a.ActionDate desc;

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

індексна котушка

Запит все ще швидко закінчується для мене. Можливо, ви не отримуєте оптимізацію котушки індексу у вашій системі або є щось інше щодо визначення таблиці чи запиту. Для освітніх цілей я можу використовувати недокументовану функцію OPTION (QUERYRULEOFF BuildSpool)для відключення котушки індексу. Ось як виглядає план:

поганий пошук індексу

Не обманюйте появу простої індексації. SQL Server зчитує майже 10 мільйонів рядків з індексу:

10М рядків від покажчика

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

CREATE NONCLUSTERED INDEX [IDX_my_nme_2] ON #Activity
([Address] ASC) INCLUDE (ActionDate);

План аналогічний попередньому:

індекс шукати

Однак з нового індексу SQL Server зчитує лише 1000 рядків з індексу. 800 рядків повертаються для підрахунку. Індекс можна визначити більш вибірковим, але це може бути досить добре залежно від вашого розповсюдження даних.

добрий пошук

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

SELECT t.*
FROM
(
    select 
        a.*
        , -1 + ROW_NUMBER() OVER (PARTITION BY [Address] ORDER BY ActionDate) PriorCount
    from #Activity a
) t
where t.ActionDate between '2017-05-29' and '2017-05-30'
order by t.ActionDate desc;

Цей запит робить одне сканування даних, але робить дорогий сорт і обчислює ROW_NUMBER()функцію для кожного рядка таблиці, тому відчувається, що тут виконується додаткова робота:

поганий сорт

Однак, якщо вам дуже подобається цей шаблон коду, ви можете визначити індекс, щоб зробити його більш ефективним:

CREATE NONCLUSTERED INDEX [IDX_my_nme] ON #Activity
([Address], [ActionDate]) INCLUDE (FILLER);

Це переміщує сорт до кінця, який буде значно дешевшим:

хороший сорт

Якщо нічого з цього не допомагає, вам потрібно буде додати більше інформації до питання, бажано, включаючи фактичні плани виконання.


1
Вибрана вами індексна котушка. Після того, як я додав новий nonclustered index [xyz] on [Activity] (Address) include (ActionDate), час запитів зменшився від хвилини до менше секунди. +10, якби міг. Спасибі!
Метро Смурф
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.