Подтвердить что ты не робот

Когда курсор FAST_FORWARD имеет рабочую таблицу (и этого избежать)?

Фон

Я заметил, что при экспериментировании с запущенными полными запросами, что иногда оценочный план просто показывает "запрос на выборку"

Fetch

и фактический план показывает повторные выборки из сканирования кластерного индекса

Fetch Scan

в других случаях (например, при добавлении TOP к запросу) в оценочном плане показан этап "Население", который заполняет рабочую таблицу

Fetch and Populate

С фактическим планом, показывающим сканирование кластерного индекса, чтобы заполнить рабочую таблицу, повторное обращение к этой рабочей таблице.

Seeks

Вопрос

  • Какие критерии использует SQL Server при выборе одного подхода над другим?
  • Я был бы прав, думая, что первый метод (без дополнительного шага популяции рабочей таблицы) более эффективен?

(Бонусный вопрос: если кто-нибудь может объяснить, почему каждое сканирование в первом запросе считается 2-мя логическими чтениями, которые также могут быть достаточно просвещенными)

Дополнительная информация

Я нашел эту статью здесь, в которой объясняется, что курсоры FAST_FORWARD могут использовать динамический план или статический план. Первый запрос в этом случае, по-видимому, использует динамический план, а второй - статический план.

Я также обнаружил, что если я попробую

SET @C2 = CURSOR DYNAMIC TYPE_WARNING FOR SELECT TOP ...

Курсор получает неявное преобразование в курсор keyset, поэтому ясно, что конструкция TOP не поддерживается для динамических курсоров, возможно, по причинам, указанным в ответе Рубена. Все еще ищет окончательное объяснение этого.

Однако я также читал, что динамические курсоры имеют тенденцию медленнее, чем их статические аналоги (источник 1, источник 2), что кажется мне удивительным, учитывая, что статический сорт должен читать исходные данные, копировать его, а затем читать копию, а не просто читать источник данных. В статье, на которую я ссылался ранее, упоминается, что динамические курсоры используют markers. Кто-нибудь может объяснить, что это такое? Это просто код RID или ключ CI или что-то другое?

Script

SET STATISTICS IO OFF

CREATE TABLE #T ( ord INT IDENTITY PRIMARY KEY, total INT, Filler char(8000))

INSERT INTO #T (total) VALUES (37),(80),(55),(31),(53)

DECLARE @running_total INT, 
    @ord INT, 
    @total INT

SET @running_total = 0
SET STATISTICS IO ON
DECLARE @C1 AS CURSOR;
SET @C1 = CURSOR FAST_FORWARD FOR SELECT ord, total FROM #T ORDER BY ord;
OPEN @C1;
PRINT 'Initial FETCH C1'
FETCH NEXT FROM @C1 INTO @ord, @total ;
WHILE @@FETCH_STATUS = 0
BEGIN
  SET @running_total = @running_total + @total
  PRINT 'FETCH C1'
  FETCH NEXT FROM @C1 INTO @ord, @total ;
END

SET @running_total = 0
SET STATISTICS IO ON
DECLARE @C2 AS CURSOR;
SET @C2 = CURSOR FAST_FORWARD FOR SELECT TOP 5 ord, total FROM #T ORDER BY ord;
OPEN @C2;
PRINT 'Initial FETCH C2'
FETCH NEXT FROM @C2 INTO @ord, @total ;
WHILE @@FETCH_STATUS = 0
BEGIN
  SET @running_total = @running_total + @total
  PRINT 'FETCH C2'
  FETCH NEXT FROM @C2 INTO @ord, @total ;
END

PRINT 'End C2'
DROP TABLE #T 
4b9b3361

Ответ 1

Какие критерии использует SQL Server при выборе одного подхода над другим?

Это прежде всего решение, основанное на затратах. Цитата из статьи, с которой вы ссылались: "В ситуациях, когда динамический план выглядит многообещающим, сравнение затрат может быть эвристически пропущено. Это происходит в основном для чрезвычайно дешевых запросов, хотя детали являются эзотерическими".

Я был бы прав, думая, что первый метод (без дополнительного шага популяции рабочей таблицы) более эффективен?

Это зависит. Динамические и статические планы курсора имеют разные сильные и слабые стороны. Если все строки в конечном итоге будут затронуты, статический план, скорее всего, будет работать лучше. Подробнее об этом через мгновение.

Понятно, что конструкция TOP не поддерживается для динамических курсоров

Это верно. Все итераторы в плане динамического курсора должны иметь возможность сохранять и восстанавливать состояние, сканировать вперед и назад, обрабатывать одну строку ввода для каждой выходной строки и не блокировать. Верх, в общем, не удовлетворяет всем этим требованиям; класс CQScanTopNew не реализует необходимые методы Set/Get/Goto/Marker() и ReverseDirection() (среди прочих).

Я также читал, что динамические курсоры имеют тенденцию быть медленнее, чем их статические копии.

Это часто верно для курсоров Transact-SQL, где затрагивается большинство или весь набор курсоров. Существует стоимость, связанная с сохранением и восстановлением состояния динамического плана запросов. Если для каждого вызова обрабатывается одна строка, и все строки в конечном итоге затрагиваются, это экономия/восстановление накладных расходов максимизируется.

Статические курсоры имеют накладные расходы на создание копии набора (что может являться доминирующим фактором для большого набора), но затраты на изрядную стоимость поиска довольно малы. Ключи имеют более высокие издержки на изъятие строки, чем статические, поскольку они должны внешнее соединение обратно к исходным таблицам для получения нек-ликовых столбцов.

Динамические курсоры оптимальны при доступе относительно небольшой части набора, и/или поиск не является строкой по времени. Это типичный шаблон доступа во многих сценариях обычного курсора, а не те сообщения в блоге, которые обычно тестируются:)

Если бы кто-нибудь мог объяснить, почему каждое сканирование в первом запросе считается как 2 логических чтения, которые могут быть довольно просвещенными.

Это зависит от того, как состояние сохраняется для сканирования, и подсчитывается способ чтения.

В статье, на которую я ссылался ранее, упоминается, что динамические курсоры используют маркеры. Кто-нибудь может объяснить, что это такое? Это просто код RID или ключ CI или что-то другое?

Маркеры существуют для каждого итератора в плане динамического курсора, а не только для методов доступа. "Маркер" - это вся информация о состоянии, необходимая для перезапуска итератора плана в тот момент, когда он был остановлен. Для метода доступа ключевое слово RID или индекс (с уникальным идентификатором, если необходимо) является значительной частью этого, но не всей истории.

Ответ 2

Просто догадка, но, как правило, TOP-ORDER BY требует, чтобы SQL Server каким-то образом обрабатывал результат (результат сканирования индекса или даже весь результат в структуре temp или что-то среднее между ними).

Можно утверждать, что для курсоров это также необходимо даже при заказе с помощью первичного ключа (как в вашем примере), поскольку вы не можете позволить курсору TOP 5 неожиданно возвращать менее 5 строк, когда соответствующий SELECT вернет ровно 5 строк (или хуже: курсор возвращает более 5 строк).

Эта странная ситуация может теоретически произойти, когда в таблице удаляются или вставляются таблицы после того, как диапазон сканирования индекса уже определен для курсора, а вставки/удаления попадают в диапазон сканирования индекса, но вы еще не сделали этого выборка. Чтобы этого не произошло, они могут ошибаться в безопасности. (И они просто не оптимизировались для таблиц #temp.)

Вопрос: разрешает ли SQL Server FETCH FROM SELECT TOP n без предложения ORDER BY? (У меня нет экземпляра SQL Server.) Возможно, вам будет интересно узнать, какой план он вызывает.