Полезен ли оператор буфера для удаления из кластерного хранилища columns?

28

Я тестирую удаление данных из кластерного индекса columnstore.

Я заметил, что в плане выполнения есть большой нетерпеливый оператор спула:

введите описание изображения здесь

Это завершается следующими характеристиками:

  • Удалено 60 миллионов строк
  • 1.9 GiB TempDB используется
  • Время выполнения 14 минут
  • Серийный план
  • 1 повторная привязка на катушке
  • Ориентировочная стоимость сканирования: 364,821

Если я обманываю оценщика в недооценке, я получаю более быстрый план, который избегает использования TempDB:

введите описание изображения здесь

Ориентировочная стоимость сканирования: 56,901

(Это примерный план, но цифры в комментариях верны.)

Интересно, что спул снова исчезает, если я очищаю дельта-магазины, выполняя следующее:

ALTER INDEX IX_Clustered ON Fact.RecordedMetricsDetail REORGANIZE WITH (COMPRESS_ALL_ROW_GROUPS = ON);

Похоже, что спул вводится только тогда, когда в дельта-хранилищах больше порога страниц.

Чтобы проверить размер дельта-хранилищ, я запускаю следующий запрос для проверки строчных страниц таблицы:

SELECT  
  SUM([in_row_used_page_count]) AS in_row_used_pages,
  SUM(in_row_data_page_count) AS in_row_data_pages
FROM sys.[dm_db_partition_stats] as pstats
JOIN sys.partitions AS p
ON pstats.partition_id = p.partition_id
WHERE p.[object_id] = OBJECT_ID('Fact.RecordedMetricsDetail');

Есть ли какая-либо реальная выгода для итератора катушки в первом плане? Я должен предположить, что это предназначено для повышения производительности, а не для защиты Хэллоуина, потому что его присутствие не соответствует.

Я тестирую это на 2016 CTP 3.1, но я вижу то же самое поведение на 2014 SP1 CU3.

Я опубликовал скрипт, который генерирует схему и данные и поможет вам продемонстрировать проблему здесь .

Вопрос в основном из любопытства по поводу поведения оптимизатора в данный момент, так как у меня есть обходной путь к проблеме, которая вызвала вопрос (большая катушка заполнена TempDB). Я сейчас удаляю, используя вместо этого переключение разделов.

Джеймс Л
источник
2
Если я попробую, OPTION (QUERYRULEOFF EnforceHPandAccCard)катушка исчезнет. Я полагаю, что HP может быть "Защита Хэллоуина". Однако затем попытка использовать этот план с USE PLANподсказкой не удалась (как и попытка использовать план из OPTIMIZE FOR обходного пути)
Martin Smith
Спасибо @MartinSmith. Есть идеи, что AccCardбудет? Восходящая колонка кардинальности может быть?
Джеймс Л
1
@JamesLupolt Нет, я не мог придумать ничего особенно убедительного для меня. Может быть, Аккаунт Накоплен или Доступ?
Мартин Смит

Ответы:

22

Есть ли какая-либо реальная выгода для итератора катушки в первом плане?

Это зависит от того, что вы считаете «правдоподобным», но ответ в соответствии с моделью затрат - «да». Конечно, это правда, потому что оптимизатор всегда выбирает самый дешевый план, который он находит.

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

DELETE Fact.RecordedMetricsDetail
WHERE MeasurementTime < DATEADD(day,-1,GETUTCDATE())
OPTION (RECOMPILE);

Ориентировочная стоимость этого плана составляет 771 734 единицы :

Оригинальный план

Почти все затраты связаны с удалением кластерного индекса, поскольку ожидается, что удаление приведет к большому количеству случайных операций ввода-вывода. Это просто общая логика, которая применяется ко всем модификациям данных. Например, предполагается, что неупорядоченный набор модификаций индекса b-дерева приводит к в значительной степени случайному вводу / выводу с соответствующей высокой стоимостью ввода / вывода.

Планы с изменением данных могут включать сортировку для представления строк в порядке, обеспечивающем последовательный доступ, именно по этим причинам стоимости. В этом случае влияние усугубляется, потому что таблица разделена. Очень разделенный, на самом деле; ваш сценарий создает 15 000 из них. Случайные обновления очень секционированной таблицы стоят особенно дорого, так как цена переключения секций (наборов строк) в середине потока также высока.

Последний важный фактор, который следует учитывать, заключается в том, что простой запрос на обновление выше (где «обновление» означает любую операцию по изменению данных, включая удаление) отвечает требованиям оптимизации, называемой «совместное использование набора строк», где один и тот же внутренний набор строк используется как для сканирования, так и для сканирования. Обновление таблицы. План выполнения по-прежнему показывает два отдельных оператора, но, тем не менее, используется только один набор строк.

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

Важным следствием является то, что логика калькуляции для оператора обновлений не учитывает это преимущество упорядочения (содействие последовательному вводу / выводу или другие оптимизации), когда базовым объектом является хранилище столбцов. Это связано с тем, что изменения хранилища столбцов не выполняются на месте; они используют дельта-магазин. Следовательно, модель затрат отражает разницу между обновлениями набора общих строк в b-деревьях и хранилищами столбцов.

Тем не менее, в особом случае (очень!) Многораздельного хранилища столбцов может сохраняться преимущество сохраненного упорядочения, поскольку выполнение всех обновлений одного раздела перед переходом к следующему может быть выгодным с точки зрения ввода / вывода. ,

Здесь используется стандартная логика затрат для хранилищ столбцов, поэтому план, который сохраняет порядок разделов (но не порядок внутри каждого раздела), стоит меньше. Мы можем видеть это в тестовом запросе, используя недокументированный флаг трассировки 2332, чтобы потребовать отсортированный ввод для оператора обновления. Это устанавливает для DMLRequestSortсвойства значение true при обновлении и вынуждает оптимизатор создать план, который содержит все строки для одного раздела перед переходом к следующему:

DELETE Fact.RecordedMetricsDetail
WHERE MeasurementTime < DATEADD(day,-1,GETUTCDATE())
OPTION (RECOMPILE, QUERYTRACEON 2332);

Ориентировочная стоимость этого плана намного ниже и составляет 52,5174 единицы:

DMLRequestSort = истинный план

Это снижение стоимости связано с более низкой оценочной стоимостью ввода-вывода при обновлении. Представленный Spool не выполняет никакой полезной функции, за исключением того, что он может гарантировать вывод в порядке разбиения, как того требует обновление с помощью DMLRequestSort = true(последовательное сканирование индекса хранилища столбцов не может обеспечить эту гарантию). Стоимость самой катушки считается относительно низкой, особенно по сравнению с (возможно, нереальным) снижением стоимости при обновлении.

Решение о том, требовать ли упорядоченный ввод для оператора обновления, принимается очень рано при оптимизации запросов. Эвристика, использованная в этом решении, никогда не была задокументирована, но может быть определена методом проб и ошибок. Кажется, что размер любого магазина дельты - вход к этому решению. После того, как выбор сделан, выбор является постоянным для компиляции запроса. Никакая USE PLANподсказка не будет успешной: цель плана либо заказала входные данные для обновления, либо нет.

Есть другой способ получить недорогой план для этого запроса без искусственного ограничения оценки количества элементов. Достаточно низкая оценка, чтобы избежать Spool, вероятно, приведет к тому, что DMLRequestSort будет ложным, что приведет к очень высокой оценочной стоимости плана из-за ожидаемого случайного ввода-вывода. Альтернативой является использование флага трассировки 8649 (параллельный план) в сочетании с 2332 (DMLRequestSort = true):

DELETE Fact.RecordedMetricsDetail
WHERE MeasurementTime < DATEADD(day,-1,GETUTCDATE())
OPTION (RECOMPILE, QUERYTRACEON 2332, QUERYTRACEON 8649);

Это приводит к плану, который использует параллельное сканирование в пакетном режиме для каждого раздела и сохраняющий порядок (объединение) обмен Gather Streams:

Заказано Удалить

В зависимости от эффективности упорядочения разделов на вашем оборудовании во время выполнения, это может быть лучшим из трех. Тем не менее, большие модификации не являются хорошей идеей в хранилище столбцов, поэтому идея переключения разделов почти наверняка лучше. Если вы можете справиться с длительным временем компиляции и изворотливым выбором планов, часто встречающимся с разделенными объектами - особенно, когда число разделов велико.

Объединение многих относительно новых функций, особенно вблизи их пределов, является отличным способом получить плохие планы выполнения. Глубина поддержки оптимизатора имеет тенденцию улучшаться с течением времени, но использование 15 000 разделов хранилища столбцов, вероятно, всегда будет означать, что вы живете в интересные времена.

Пол Уайт говорит, что GoFundMonica
источник