Эффективно фильтровать большой набор с дизъюнкциями

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
);

Это дает мне план запроса следующим образом:

  • ВЫБРАТЬ
    • Фильтр
      • верхний
        • Sequence Project (вычислить скаляр)
          • сегмент
            • Поиск индекса

Это выглядит довольно хорошо. Около 80% -90% затрат приходится на операцию поиска индекса, которая является идеальной.

Есть ли лучшие стратегии для реализации такого поиска?

Я не хочу разгрузить дополнительную фильтрацию на клиенте, потому что в некоторых случаях набор результатов из «фиксированной» части может быть 100 или 1000 с. Клиент также будет отвечать за сортировку и разбиение по страницам, что может быть слишком много для клиента.

Джозеф Дейгл
источник
Можно ли поместить ваш подзапрос во временную таблицу или табличную переменную и построить таким образом? С моими большими таблицами меня иногда ужаливают подзапросы. Покрывающие индексы только унесут вас.
Валькирия
@ Валькирия, которая кажется невероятно неэффективной. Также учтите, что варианты этого запроса (разные параметры и разные необязательные выражения where), вероятно, будут выполняться несколько раз в секунду в течение всего дня и должны возвращать результаты в среднем менее чем за 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. Это добавляется ко всем незагрязненным индексам, чтобы они могли ссылаться на правильную кластерную запись

Преимущества:

  • Добавляет новые строки в конец таблицы
  • не допускать записи дополнительных столбцов фильтра дважды (один раз в кластеризованном и один раз на листе индекса для включения)
  • большинство из вас все еще будет искать кластерный индекс с большим или меньшим количеством файлеров.
  • затем добавьте другой некластеризованный индекс для большинства популярных пар столбцов
Matt
источник
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 (или редакции для разработчиков).

По сути, вы разбили свою таблицу на несколько разделов и определили критерии (функции) для вашего диапазона дат?

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

AlexTheDeveloper
источник
-1

Если клиенты фильтруют почти одинаково снова и снова, вы можете создать индекс для этих запросов.

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

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

Таким образом, большинство «более распространенных» запросов могут выполняться быстро.

Рууд ван де Битен
источник