Сценарий: в таблице с монотонно растущим ключом запрос по самым новым значениям получает сильно заниженную оценку числа строк. Какой механизм статистики это объясняет?
Это проблема восходящего ключа: новые значения вышли за верхнюю границу гистограммы статистики. Оптимизатор не видит их фактическое распределение и оценивает селективность по специальному правилу или устаревшим данным, из-за чего может выбрать неудачный план.
Стоимость плана нужно оценивать без полного чтения всех строк. Поэтому оптимизаторы используют компактные статистические структуры, прежде всего гистограммы распределения и сведения о числе различных значений.
Такой подход хорошо работает для уже наблюдавшегося диапазона. Однако монотонные ключи постоянно получают новые значения, и статистика начинает отставать от фактического состояния таблицы.
Типичный пример — идентификатор, дата создания или номер последовательности. Статистика была построена, когда максимальное значение равнялось 1 000 000, а затем появились строки со значениями от 1 000 001 до 1 500 000.
Запрос к новому диапазону может быть действительно селективным, но оптимизатор не имеет обычной гистограммы для этих значений. Заниженная оценка приводит к выбору nested loops, индексных обращений к множеству страниц или маленького размера памяти под сортировку и соединение.
Гистограмма обычно описывает диапазоны до известной верхней границы. Значения за этой границей называются восходящими или новыми значениями. Их реальная плотность ещё не отражена в статистике, поэтому оценка строится эвристически; конкретное правило зависит от СУБД и версии оптимизатора.
Важно отличать проблему восходящего ключа от простого отсутствия обновления статистики. Даже автоматическое обновление может запускаться не сразу после вставок, а после достижения порога изменений. Кроме того, обновление с выборкой может неточно оценить редкий или резко изменившийся хвост распределения.
Диагностика включает проверку даты и диапазона статистики, максимального значения ключа, фактического и оценочного числа строк в плане. Особенно показательно систематическое занижение оценок именно для новых значений.
В SQL Server минимальный пример выглядит так:
Если статистика была собрана до появления этого диапазона, план может оценить число строк значительно ниже фактического. Обновление статистики, при необходимости с более точной выборкой, позволяет оптимизатору увидеть новый диапазон; иногда полезна и корректировка стратегии обслуживания статистики.
Компромисс состоит в стоимости обслуживания. Частое обновление статистики потребляет CPU и дисковый ввод-вывод, а полное сканирование повышает точность, но может быть слишком дорогим для большой таблицы. Нельзя без проверки просто принуждать индексный план: после исправления оценок оптимальным может оказаться сканирование или другой порядок соединения.
В журнале событий новые записи постоянно добавлялись по времени. Запрос за последние десять минут возвращал сотни тысяч строк, но план оценивал их как несколько десятков и выбирал вложенные циклы с повторными обращениями к большой таблице.
Рассматривались три варианта. Принудительный индексный план быстро устранял один симптом, но делал запрос хрупким при изменении объёма данных. Увеличение частоты автоматического обновления статистики уменьшало окно устаревания, но создавало дополнительную нагрузку. Обновление статистики по расписанию с контролем максимального значения и выборочной проверкой планов оказалось более устойчивым.
После обслуживания статистики оценка приблизилась к фактическому объёму, оптимизатор выбрал пакетное соединение и перестал выполнять сотни тысяч повторных обращений. Решение было принято не по наличию индекса, а по устранению ошибки оценки кардинальности.
Не всегда. Если новые строки добавляются после обновления, проблема снова возникнет. Приближённая выборка может плохо описать редкий хвост, а автоматическое обновление запускается по порогу изменений, который зависит от СУБД и характеристик таблицы. Нужно проверять диапазон, дату статистики, режим выборки и разницу между оценкой и фактом.
Индекс помогает быстро находить строки, но оптимизатор сначала должен оценить, сколько строк будет найдено и во сколько обойдётся доступ к ним. Если он считает результат маленьким, то может выбрать дешёвый для малой выборки nested loops, хотя фактически тот выполнит огромное число операций. Индекс не исправляет неверную кардинальность автоматически.
Да. Если новые значения на самом деле соответствуют большой доле таблицы, точная статистика покажет низкую селективность диапазона. Тогда полное или последовательное сканирование может быть дешевле множества случайных обращений через индекс. Цель обслуживания статистики — не заставить оптимизатор использовать индекс, а дать ему достоверные данные для выбора плана.