Здравствуйте, уважаемые читатели. В этой серии статей мы с вами рассмотрим один из способов оценки эффективности использования кэша планов запросов и три варианта параметризации SQL-запросов, для более эффективного использования кэша планов – простую автоматическую параметризацию, принудительную автоматическую параметризацию и ручную параметризацию запросов.
Для начала давайте в общих чертах посмотрим, как происходит выполнение SQL-запросов сервером Microsoft SQL. Процессор запросов (query processor), который и занимается выполнением SQL-запросов, поступивших на SQL-сервер, и выдачей их результатов клиенту, состоит из двух основных компонентов:
Поскольку инструкция SELECT не определяет точные шаги, которые SQL-сервер должен предпринять, чтобы выдать клиенту запрашиваемые им данные, то SQL-сервер должен сам проанализировать эту инструкцию и определить самый эффективный способ извлечения запрошенных данных. Сначала инструкция попадает в обработку к оптимизатору запросов, где выполняются следующие шаги, с использованием компонентов оптимизатора:
Далее полученный план выполнения запроса сохраняется в кэше планов. И исполнитель запросов на основе последовательности инструкций (шагов), указанных в плане выполнения, запрашивает у подсистемы хранилища требуемые данные, преобразует их в заданный для результирующего набора данных формат и возвращает клиенту.
Шаги, описанные для обработки инструкции SELECT, применяются также и к другим инструкциям SQL, таким как INSERT, UPDATE и DELETE. Для инструкций UPDATE и DELETE результатом создания плана выполнения запроса будет план по определению набора строк, которые должны быть изменены или удалены.
Таким образом в общих чертах происходит создание плана выполнения запроса. И если в дальнейшем SQL-серверу на исполнение поступает аналогичный запрос и план выполнения для данного запроса по-прежнему доступен в кэше планов, то исполнитель запросов выполняет запрос согласно ранее созданному плану выполнения.
В теории всё выглядит замечательно – для впервые пришедших на SQL-сервер запросов составляются оптимальные планы выполнения, которые в дальнейшем используются для исполнения аналогичных запросов, экономя тем самым процессорные мощности SQL-сервера для более важных задач. Однако на практике, для того чтобы использовать преимущества, предоставляемые кэшем планов выполнения запросов, необходимо соблюдение определенных условий. Но прежде чем мы рассмотрим эти условия, давайте выясним – зачем нужен анализ эффективности использования кэша и как мы можем посмотреть на его использование и узнать, в каких случаях он используется оптимально, а в каких – не очень.
Кэш планов (всех планов, не только планов запросов) располагается в оперативной памяти SQL-сервера и в рабочей среде может достигать размера от нескольких гигабайт до нескольких десятков гигабайт, в зависимости от количества доступной SQL-серверу оперативной памяти и текущего размера буферного пула SQL-сервера.
Посмотреть текущий размер всего кэша планов и кэша планов запросов можно с помощью следующего запроса:
Если в кэш планов поступает много одноразовых планов, которые никогда больше не будут использованы SQL-сервером, то кэш быстро разрастается до своего максимального размера и в дело вступает механизм очистки кэша, который начинает удалять из кэша наиболее старые и наименее ценные планы, высвобождая место под новые планы, которые в дальнейшем так же не будут использованы повторно. Таким образом, если кэш планов используется неэффективно, то помимо выделения под кэш оперативной памяти (которая могла бы быть использована под другие задачи сервера), SQL-сервер постоянно тратит некоторое количество процессорного времени на компиляцию новых планов запросов, на очистку кэша от старых планов и на размещение в нем новых планов запросов.
Для анализа кэша планов запросов мы будем использовать следующий запрос:
Примечание: Для работы запроса требуется уровень совместимости БД, в контексте которой будет выполняться запрос, не меньше 100 и SQL-сервер версии не ниже SQL Server 2008. Если ваша БД меньшего уровня совместимости, но имеющийся SQL-сервер - SQL Server 2008 или выше, то запрос будет работать, поскольку в начале запроса имеется инструкция, задающая контекст базы master для выполнения запроса.
Запрос довольно тяжеловесный и, в зависимости от размера кэша планов в рабочей среде, может выполняться от нескольких десятков секунд, до свыше десяти минут. Если какие-то из данных, собираемых запросом, вам не нужны (как, например, количество инструкций в пакетных запросах) или каких-то данных не хватает (например, статистики по физическим и логическим чтениям, которая в рамках данной статьи не нужна), то вы вольны модифицировать этот запрос исходя из собственных нужд.
Рассмотрим параметры и выходные данные запроса.
Параметр всего один – это переменная @QueryLike varchar(20). Она служит для поиска в кэше планов статистики для конкретного запроса, по указанному набору символов текста запроса. Если переменная не задана, то выводится статистика по всем планам запросов. Чуть ниже мы рассмотрим пример использования этого параметра.
Результатом выполнения запроса является заполненная временная таблица #plans_summary, содержащая следующие данные (столбцы) по статистике кэша планов:
Установим значение параметра @QueryLike в запросе для сбора статистики кэша планов:
И выполним предыдущий большой запрос. Сделав выборку из таблицы #plans_summary
мы получим следующий результат:
Значение query_hash для обоих запросов совпадает, несмотря на то, что в запросах разные условия в секции WHERE.
Примечания:
А теперь самая интересная часть статьи. Давайте посмотрим статистику использования кэша планов запросов на некотором сервере в некоторой боевой БД (DIRECTUM 5.0, простая автопараметризация).
После формирования таблицы #plans_summary из неё можно делать выборки интересующих нас данных в нужных разрезах.
Запрос:
Результат:
Список полей:
Что интересного можно увидеть в результатах этого запроса?
Время компиляции планов одноразовых запросов в несколько раз больше времени их выполнения - это не очень хорошо, но в данном случае не страшно, т.к. цифры всё равно небольшие, в пределах нескольких минут. Самых интересных показателей два - это Size Normal (MB) и TTL (hours). Одноразовые планы занимают больше половины всего кэша планов (размер кэша на момент выполнения запроса - 3,2Гб, что составляет 10% от объёма оперативной памяти сервера), а невысокое время жизни одноразовых запросов (меньше двух часов) говорит о том, что кэш забит под завязку и в нём идёт постоянная ротация планов - вытеснение старых планов и компиляция новых.
Несмотря на то, что планов-заглушек почти в 7,5 раз меньше, чем обычных планов, места в кэше планы-заглушки занимают примерно в пятьдесят раз меньше. Это объясняется тем, что планы-заглушки - это не полноценные планы, а по сути ссылки на автопараметризованные планы и поэтому они занимают заметно меньший объём.
Запрос:
Результат:
Поле TTL (hours) в этом запросе отсутствует, поскольку на повторно используемые планы выполнения не распространяется условие, что первым из кэша вытесняется самый старый план - кандидаты на вытеснение вычисляются по более сложной системе расчёта рейтинга планов, основывающейся на счётчике количества выполнений плана. Вообще для одноразовых планов применяется такая же схема поиска кандидатов на вытеснение, но у них счётчик равен единице, что приводит к тому, что у одноразовых планов выполнения запросов рейтинг и так невысок, меньше только у планов-заглушек.
В результатах данного запроса наблюдается обратная от одноразовых планов ситуация - общее время выполнения запросов значительно превышает время компиляции планов, что объясняется тем, что планы выполнения запросов в данном случае был скомпилированы один раз, а выполнены несколько раз. Объём повторно используемых планов для Adhoc-запросов занимает четверть всего кэша. И, что довольно интересно, процессорное время выполнения запросов почти в три раза превышает фактическое время, т.е. какая-то часть запросов, составляющих эту статистику, выполняется с распараллеливанием. Чуть ниже мы в этом убедимся более детально.
Необходимое примечание: Так уж получилось, что данная статистика собрана всего через 5 часов после перезагрузки SQL-сервера, и, следовательно, после полной очистки кэша планов. При более длительной работе SQL-сервера статистика по объёму занимаемого места в кэше значительно смещается от одноразовых планов к повторно используемым, а общее время выполнения начинает заметно превышать процессорное (сказывается тот факт, что далеко не все запросы распараллеливаются, да и не все распараллеливаемые запросы выполняются с использованием параллелизма. Плюс чтение тел документов из БД приводит к ожиданию данных от дисковой подсистемы).
В целом по итогам этой статистики видно, что планы для Adhoc-запросов занимают значительную (более 75%) часть кэша. Производительность SQL-сервера в данном случае от этого не слишком страдает, т.к. время, затрачиваемое сервером на компиляцию планов, всё же горазно меньше времени, затрачиваемого на их выполнение, но оперативная память расходуется неэффективно. Давайте посмотрим статистику по отношению занимаемой памяти к количеству идентичных и схожих запросов.
Планы для схожих запросов мы будем искать с помощью группировки по полю query_hash. Как уже было рассмотрено - это поле является хэш-суммой текста нормализованного запроса и содержит одинаковые значения для схожих запросов. Исключим из выборки планы, счётчик выполнения которых равен единице, т.к. нам сейчас интересна статистика по дубликатам.
Запрос:
Результат:
Первая строка - это суммарный итог по всей выборке, по этой же причине поле query_hash для первой строки равно NULL.
Как мы видим 85% всего кэша занимают планы выполнения всего для 239 запросов. Для этих запросов было скомпилировано 20 441 планов выполнения. При этом более половины всего кэша (вторая строка) занимают планы для одного единственного (без учета литералов) запроса, к тому же на компиляцию планов для этого запроса уходит в несколько раз больше времени, чем на их выполнение. Запрос из третьей строки, судя по процессорному времени, как раз и даёт основную статистику по распараллеливаемости. Давайте посмотрим более подробную статистику по этому запросу:
Итого по данному query_hash из 1424 планов всего 4 плана явно выполняются с параллелизмом. Возможно таких планов больше, но время, выигрываемое на распараллеливании, съедается расходами на другие операции и поэтому в данную выборку такие планы не попали. Если щёлкнуть по ссылке, указанной в поле query_plan, то можно посмотреть графическое отображение плана запроса и сам запрос:
Давайте теперь выведем список query_hash для тех запросов, планы выполнения для которых не только занимают место в кэше, но и на компиляцию которых времени уходит больше, чем на выполнение.
Запрос (с сортировкой по разнице между временем компиляции и выполнения):
Результат:
Дублирующиеся планы выполнения могут создаваться SQL-сервером не только для Adhoc, но и для параметризованных запросов.
Выполним следующий запрос:
Результат:
Как видно из результата, для некоторых параметризованных запросов планы выполнения дублируются, давая в сумме 508 планов выполнения, вместо 86.
Причин создания дублирующихся планов выполнения для параметризованных запросов может быть несколько:
Уважаемые читатели, в данной статье был рассмотрен один из способов того, как можно проанализировать эффективность использования кэша планов выполнения запросов. Надеюсь, что эта статья была для вас полезной. Если у вас есть какие-то вопросы, то задавайте их в комментариях, постараюсь на них ответить.
В следующей статье будут рассмотрены способы борьбы с неэффективным расходованием оперативной памяти и процессорных ресурсов SQL-сервером с помощью автоматической и ручной параметризации запросов.
Материалы, использованные при написании статьи:
Авторизуйтесь, чтобы написать комментарий