Шаг первый для вопросов, связанных с производительностью, состоит в том, чтобы проанализировать структуру таблицы / индекса и пересмотреть планы запросов. Вы не предоставили эту информацию, поэтому я собираюсь составить свою собственную и перейти оттуда.
Я собираюсь предположить, что у вас есть куча, ~ 10M строк (12 872 738 для меня):
DECLARE @MaxRowCount bigint = 10000000,
@Offset bigint = 0;
DROP TABLE IF EXISTS #ExampleTable;
CREATE TABLE #ExampleTable
(
ID bigint NOT NULL,
Name varchar(50) COLLATE DATABASE_DEFAULT NOT NULL
);
WHILE @Offset < @MaxRowCount
BEGIN
INSERT INTO #ExampleTable
( ID, Name )
SELECT ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL )),
ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL ))
FROM master.dbo.spt_values SV
CROSS APPLY master.dbo.spt_values SV2;
SET @Offset = @Offset + ROWCOUNT_BIG();
END;
Если я выполню запрос, предоставленный через #ExampleTable
, это займет около 4 секунд и даст мне план запроса:
Это не очень хороший план запросов, но вряд ли он ужасен. Работа с статистикой запросов в реальном времени показывает, что оценки количества элементов были не более чем на единицу, что хорошо.
Давайте дадим огромное количество предметов в нашем списке IN
(5000 предметов от 1-5000). Компиляция плана заняла 4 секунды:
Я могу получить свой номер до 15000 элементов, прежде чем обработчик запросов перестанет его обрабатывать, безизменение в плане запроса (компиляция занимает всего 6 секунд). Выполнение обоих запросов занимает около 5 секунд на моем компьютере.
Это, вероятно, хорошо для аналитических рабочих нагрузок или для хранилищ данных, но для OLTP-подобных запросов мы определенно превысили наш идеальный лимит времени.
Давайте посмотрим на некоторые альтернативы. Возможно, мы можем сделать некоторые из них в комбинации.
- Мы могли бы кэшировать список
IN
во временной таблице или табличной переменной. - Мы могли бы использовать оконную функцию для вычисления количества
- Мы могли бы кэшировать наш CTE во временной таблице или табличной переменной
- Если на достаточно высокой версии SQL Server,используйте пакетный режим
- Измените индексы на своей таблице, чтобы сделать это быстрее.
Особенности рабочего процесса
Если это рабочий процесс OLTP, то нам нужно что-то, чтобыстро независимо от того, сколько у нас пользователей. Таким образом, мы хотим минимизировать перекомпиляцию и хотим, чтобы поиск индекса осуществлялся везде, где это возможно. Если это аналитическое или складское хранилище, то перекомпиляция и сканирование, вероятно, подойдут.
Если нам нужен OLTP, то параметры кэширования, вероятно, не обсуждаются. Временные таблицы всегда будут принудительно перекомпилироваться, а переменные таблиц в запросах, которые основаны на правильной оценке, требуют принудительной перекомпиляции. Альтернатива может состоять в том, чтобы другая часть вашего приложения поддерживала постоянную таблицу с разбивкой по страницам или фильтрами (или обоими), а затем объединяла этот запрос с этим.
Если один и тот же пользователь просматривает много страниц, то его кэширование, вероятно, все еще стоит того, даже в OLTP, но убедитесь, что вы измеряете влияние многих одновременно работающих пользователей.
Независимо от того,рабочего процесса, обновление индексов, вероятно, в порядке (если ваши рабочие процессы не будут сильно мешать ведению вашего индекса).
Независимо от рабочего процесса, пакетный режим будет вашим другом.
Независимо от рабочего процесса,оконные функции (особенно с индексами и / или пакетным режимом), вероятно, будут лучше.
Пакетный режим и оценщик мощности по умолчанию
Мы довольно последовательно получаем плохие оценки мощности (и полученные планы) сунаследованная оценка кардинальности и выполнение в рядном режиме. Принудительная оценка кардинальности по умолчанию помогает с первым, а пакетный режим помогает со вторым.
Если вы не можете обновить свою базу данных, чтобы использовать новый кардинальный оценщик оптом, вам нужно включить его дляваш конкретный запрос. Для этого вы можете использовать следующую подсказку: OPTION( USE HINT( 'FORCE_DEFAULT_CARDINALITY_ESTIMATION' ) )
, чтобы получить первый. Во-вторых, добавьте объединение в CCI (не нужно возвращать данные): LEFT OUTER JOIN dbo.EmptyCciForRowstoreBatchmode ON 1 = 0
- это позволяет SQL Server выбирать оптимизации в пакетном режиме. Эти рекомендации предполагают достаточно новую версию SQL Server.
Что такое CCI, не имеет значения;нам нравится держать пустой для согласованности, который выглядит следующим образом:
CREATE TABLE dbo.EmptyCciForRowstoreBatchmode
(
__zzDoNotUse int NULL,
INDEX CCI CLUSTERED COLUMNSTORE
);
Лучший план, который я мог получить, не изменяя таблицу, состоял в том, чтобы использовать оба из них. С теми же данными, что и раньше, это выполняется в <1 с. </p>
WITH TempResult AS
(
SELECT ID,
Name,
COUNT( * ) OVER ( ) MaxRows
FROM #ExampleTable
WHERE ID IN ( <<really long LIST>> )
)
SELECT TempResult.ID,
TempResult.Name,
TempResult.MaxRows
FROM TempResult
LEFT OUTER JOIN dbo.EmptyCciForRowstoreBatchmode ON 1 = 0
ORDER BY TempResult.Name OFFSET ( @PageNum - 1 ) * @PageSize ROWS FETCH NEXT @PageSize ROWS ONLY
OPTION( USE HINT( 'FORCE_DEFAULT_CARDINALITY_ESTIMATION' ) );