У меня есть таблица данных SQL со следующей структурой:
CREATE TABLE Data(
Id uniqueidentifier NOT NULL,
Date datetime NOT NULL,
Value decimal(20, 10) NULL,
RV timestamp NOT NULL,
CONSTRAINT PK_Data PRIMARY KEY CLUSTERED (Id, Date)
)
Число различных идентификаторов варьируется от 3000 до 50000.
Размер таблицы варьируется до более миллиарда строк.
Один идентификатор может занимать от нескольких строк до 5% таблицы.
Единственный наиболее выполненный запрос в этой таблице:
SELECT Id, Date, Value, RV
FROM Data
WHERE Id = @Id
AND Date Between @StartDate AND @StopDate
Теперь мне нужно реализовать пошаговый поиск данных по подмножеству идентификаторов, включая обновления.
Затем я использовал схему запроса, в которой вызывающая сторона предоставляет конкретную версию строки, извлекает блок данных и использует максимальное значение версии строки возвращаемых данных для последующего вызова.
Я написал эту процедуру:
CREATE TYPE guid_list_tbltype AS TABLE (Id uniqueidentifier not null primary key)
CREATE PROCEDURE GetData
@Ids guid_list_tbltype READONLY,
@Cursor rowversion,
@MaxRows int
AS
BEGIN
SELECT A.*
FROM (
SELECT
Data.Id,
Date,
Value,
RV,
ROW_NUMBER() OVER (ORDER BY RV) AS RN
FROM Data
inner join (SELECT Id FROM @Ids) Ids ON Ids.Id = Data.Id
WHERE RV > @Cursor
) A
WHERE RN <= @MaxRows
END
Где @MaxRows
будет варьироваться от 500 000 до 2 000 000, в зависимости от того, как клиент будет запрашивать свои данные.
Я пробовал разные подходы:
- Индексирование по (Id, RV):
CREATE NONCLUSTERED INDEX IDX_IDRV ON Data(Id, RV) INCLUDE(Date, Value);
Используя индекс, запрос ищет строки, где RV = @Cursor
для каждого Id
из них @Ids
, читает следующие строки, затем объединяет результат и сортирует.
Эффективность зависит от относительного положения @Cursor
стоимости.
Если он близок к концу данных (по заказу RV), запрос выполняется мгновенно, а если нет, то запрос может занять до нескольких минут (никогда не позволяйте ему выполняться до конца).
проблема с этим подходом заключается в том, что @Cursor
либо в конце данных, и сортировка не является болезненной (даже не требуется, если запрос возвращает меньше строк @MaxRows
), либо она находится позади, и запрос должен сортировать @MaxRows * LEN(@Ids)
строки.
- Индексирование по RV:
CREATE NONCLUSTERED INDEX IDX_RV ON Data(RV) INCLUDE(Id, Date, Value);
Используя индекс, запрос ищет строку, где RV = @Cursor
затем читает каждую строку, отбрасывая незапрошенные идентификаторы, пока не достигнет @MaxRows
.
Эффективность зависит от% запрашиваемых идентификаторов ( LEN(@Ids) / COUNT(DISTINCT Id)
) и их распределения.
Больше запрашиваемого Id% означает меньше отброшенных строк, что означает более эффективное чтение, меньшее количество запрашиваемых Id% означает больше отброшенных строк, что означает больше чтений для того же количества результирующих строк.
Проблема с этим подходом состоит в том, что если запрошенные идентификаторы содержат только несколько элементов, возможно, потребуется получить весь индекс, чтобы получить нужные строки.
- Использование отфильтрованного индекса или индексированных представлений
CREATE NONCLUSTERED INDEX IDX_RVClient1 ON Data(Id, RV) INCLUDE(Date, Value)
WHERE Id IN (/* list of Ids for specific client*/);
Или
CREATE VIEW vDataClient1 WITH SCHEMABINDING
AS
SELECT
Id,
Date,
Value,
RV
FROM dbo.Data
WHERE Id IN (/* list of Ids for specific client*/)
CREATE UNIQUE CLUSTERED INDEX IDX_IDRV ON vDataClient1(Id, Rv);
Этот метод обеспечивает совершенно эффективные планы индексирования и выполнения запросов, но имеет недостатки: 1. На практике мне придется реализовать динамический SQL для создания индексов или представлений и изменить процедуру запроса для использования правильного индекса или представления. 2. Мне придется поддерживать один индекс или представление существующим клиентом, включая хранилище. 3. Каждый раз, когда клиенту нужно будет изменить свой список запрашиваемых идентификаторов, мне нужно будет удалить индекс или просмотреть и воссоздать его.
Кажется, я не могу найти метод, который бы соответствовал моим потребностям.
Я ищу лучшие идеи для реализации постепенного извлечения данных. Эти идеи могут подразумевать переработку запрашивающей схемы или схемы базы данных, хотя я бы предпочел лучший подход к индексированию, если он есть.
источник
Value
столбец. @crokusek: Заказ по RV, ID вместо RV только не увеличит рабочую нагрузку сортировки без какой-либо выгоды, я не понимаю причины вашего комментария. Из того, что я прочитал, RV должен быть уникальным, если не вставлять данные конкретно в этот столбец, чего нет в приложении.Ответы:
Одним из решений для клиентского приложения является запоминание максимального значения для
rowversion
каждого идентификатора. Пользовательский тип таблицы изменится на:Затем запрос в процедуре можно переписать для использования
APPLY
шаблона (см. Мои статьи SQLServerCentral, часть 1 и часть 2 - требуется бесплатный вход в систему). Ключ к хорошей производительности здесьORDER BY
- это позволяет избежать неупорядоченной предварительной выборки при объединении вложенных циклов. ЭтоRECOMPILE
необходимо для того, чтобы оптимизатор мог видеть мощность табличной переменной во время компиляции (возможно, в результате получился желательный параллельный план).Вы должны получить план запроса после выполнения, как этот (примерный план будет последовательным):
источник
MAX(RV)
per Id (или систему подписки, где внутреннее приложение запоминает все пары Id / RV), и я использую этот шаблон для другого клиента. Другое решение состояло в том, чтобы заставить клиента всегда получать все идентификаторы (что делает проблему индексации тривиальной). Это все еще не покрывает особую потребность вопроса: добавочный поиск подмножества идентификаторов только с одним глобальным счетчиком, предоставленным клиентом.Если возможно, я бы переделал таблицу. Если у нас может быть VersionNumber в качестве инкрементного целого числа без пробелов, то задачей получения следующего фрагмента является полностью тривиальное сканирование диапазона. Все, что нам нужно, это следующий индекс:
Конечно, нам нужно убедиться, что VersionNumber начинается с единицы и не имеет пробелов. Это легко сделать с ограничениями.
источник
VersionNumber
? В любом случае, я не вижу, как это поможет с вопросом, не могли бы вы уточнить?Что бы я сделал:
В этом случае ваш PK должен быть полем идентификации «Суррогатный ключ», который автоматически увеличивается.
Поскольку вы уже в миллиардах, было бы лучше пойти с BigInt.
Давайте назовем это DataID .
Это будет:
Настройте ваш новый BigInt PK ( DataID ) для использования Clustered-Index:
Это будет:
Создайте некластеризованный индекс вокруг (Date, Id).
Это будет:
Создайте некластеризованный индекс на (RV, ID).
Это будет:
источник