Ефективно фільтруйте великий набір за допомогою відключень


9

Скажімо, у мене єдина таблиця

CREATE TABLE Ticket (
    TicketId int NOT NULL,
    InsertDateTime datetime NOT NULL,
    SiteId int NOT NULL,
    StatusId tinyint NOT NULL,
    AssignedId int NULL,
    ReportedById int NOT NULL,
    CategoryId int NULL
);

У цьому прикладі TicketIdє Первинний ключ.

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

  1. Запит завжди виконуватиме фільтр діапазону на InsertDateTime
  2. Запит буде завжди ORDER BY InsertDateTime DESC
  3. Запит відображатиме результати на сторінці

Користувач може додатково фільтрувати будь-який з інших стовпців. Вони можуть фільтрувати на жодному, одному або багатьох. І для кожного стовпця користувач може вибрати з набору значень, які застосовуватимуться як диз'юнкція. Наприклад:

SELECT
    TicketId
FROM (
    SELECT
        TicketId,
        ROW_NUMBER() OVER(ORDER BY InsertDateTime DESC) as RowNum
    FROM Ticket
    WHERE InsertDateTime >= '2013-01-01' AND InsertDateTime < '2013-02-01'
      AND StatusId IN (1,2,3)
      AND (CategoryId IN (10,11) OR CategoryId IS NULL)
    ) _
WHERE RowNum BETWEEN 1 AND 100;

Тепер припустимо, що таблиця має 100 000 000 рядків.

Найкраще, що я можу придумати, - це індекс покриття, який включає кожен із "необов'язкових" стовпців:

CREATE NONCLUSTERED INDEX IX_Ticket_Covering ON Ticket (
    InsertDateTime DESC
) INCLUDE (
    SiteId, StatusId, AssignedId, ReportedById, CategoryId
);

Це дає мені план запитів наступним чином:

  • ВИБІРИ
    • Фільтр
      • Зверху
        • Послідовність проекту (Обчислити скаляр)
          • Сегмент
            • Шукати покажчик

Це здається досить непоганим. Близько 80% -90% витрат припадає на операцію Index Seek, що є ідеальним.

Чи є кращі стратегії здійснення такого типу пошуку?

Я не обов'язково хочу завантажувати додаткову фільтрацію для клієнта, оскільки в деяких випадках набір результатів із "фіксованої" частини може становити 100s або 1000s. Тоді клієнт також несе відповідальність за сортування та пейджинги, які можуть занадто багато працювати для клієнта.


Чи можна було б розмістити ваш підзапит у тимчасовій таблиці чи змінній таблиці та побудувати такий спосіб? Зі своїми більшими таблицями мене іноді заважають підзапити. Покриття індексів забирає вас поки що.
Валькірія

@Valkyrie, що здається неймовірно неефективним. Також врахуйте, що варіанти цього запиту (різні параметри та різні необов'язкові деклаументи), ймовірно, виконуватимуться кілька разів на секунду протягом усього дня та потребуватимуть повернення результатів у середньому менше ніж 100 мс. Ми це вже робимо, і це зараз добре. Я просто шукаю ідеї, як продовжувати покращувати продуктивність для масштабування.
Джозеф Дайгл

Скільки ви дбаєте про використання місця для зберігання?
Джон Сейгель

@JonSeigel це залежить від того, скільки ... але я хочу побачити будь-які пропозиції
Джозеф Дайгл

2
І який ваш підхід / запит, щоб отримати 2-ту сторінку результатів? RowNum BETWEEN 101 AND 200?
ypercubeᵀᴹ

Відповіді:


1

Якщо саме це робоче навантаження становить більшість запитів до таблиці, яку ви можете врахувати:

ALTER TABLE Ticket ADD CONSTRAINT PK_Ticket PRIMARY KEY NONCLUSTERED (TicketId);

CREATE UNIQUE CLUSTERED INDEX IX_Ticket_Covering ON Ticket (
    InsertDateTime ASC
);

Міркування:

  • чи можете ви використовувати datetime2 (SQL 2008+; гнучка точність)
  • буде InsertDateTime унікальним у вашій точності
  • якщо час не обмежений, унікальний sql додасть стовпчик прихованого уніфікатора типу int. Це додається до всіх незатиснутих індексів, щоб вони могли посилатися на правильну кластерну запис

Переваги:

  • Додає нові рядки до кінця таблиці
  • не допускати запису необов’язкових стовпців фільтрів двічі (один раз у кластері та один раз на аркуші індексу для включення)
  • більшість із вас все ще буде шукати індекс кластера з більш-менш файлами.
  • потім додайте інший некластеризований індекс для найбільш популярних пар стовпців

1

Я раніше використовував цю техніку. Таблиця була не настільки великою, але критерії пошуку були складнішими.

Це коротка версія.

CREATE PROC usp_Search
    (
    @StartDate  Date,
    @EndDate    Date,
    @Sites      Varchar(30) = NULL,
    @Assigned   Int = NULL, --Assuming only value possible
    @StartRow   Int,
    @EndRow     Int
    )
AS
DECLARE @TblSites   TABLE (ID Int)
IF @Sites IS NOT NULL
BEGIN
    -- Split @Sites into table @TblSites
END
SELECT  TicketId
FROM    (
        SELECT  TicketId,
                ROW_NUMBER() OVER(ORDER BY InsertDateTime DESC) as RowNum
        FROM    Ticket
                LEFT JOIN @TblSites
                    Ticket.SiteID = @TblSites.ID
        WHERE   InsertDateTime >= @StartDate 
                AND InsertDateTime < @EndDate
                AND (
                    @Assigned IS NULL 
                    OR AssignedId = @Assigned 
                    )
        ) _
WHERE   RowNum BETWEEN @StartRow AND @EndRow;

1

З огляду на ваші перші дві передумови, я б дивився на кластерний індекс на InsertDateTime.


0

чому ти не розглядаєш розділення? Він доступний в SQL 2008 вгору, але вимагає Enterprise Edition (або версії для розробника).

В основному ви розділите свою таблицю на кілька розділів, і ви визначите, критерії (функції) розділу, чи був би ви діапазоном дат?

https://www.simple-talk.com/sql/database-administration/gail-shaws-sql-server-howlers/


-1

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

Наприклад, клієнт фільтрує на SiteId та StatusId, ви можете створити додатковий індекс:

CREATE NONCLUSTERED INDEX IX_Ticket_InsertDateTime_SiteId_StatusId ON Ticket     
(InsertDateTime DESC,
 SiteId [ASC/DESC],
 StatusId [ASC/DESC] ) 
 INCLUDE ( ... );

Таким чином, більшість "більш поширених" запитів може працювати швидко.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.