Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Добрый день коллеги.
Мы продолжаем разговор о использовании query_hash и query_plan_hash для оптимизации использования процедурного кэша и уменьшения нагрузки на процессор. В предыдущем блоге мы выполнили постановку задачи и вышли на использование этих свойств запроса (https://blogs.technet.com/b/sqlruteam/archive/2014/11/09/sql_5f00_server_5f00_query_5f00_hash_5f00_and_5f00_query_5f00_plan_5f00_hash_5f00_part_5f00_1.aspx).
Для более глубокого понимания описываемых свойств рассмотрим упрощенную архитектуру системы компиляции запросов (кода Transact-SQL).
Ниже приведена блок-схема. Для упрощения часть ее компонентов пропущена.
Последовательность превращения кода, написанного на Transact-SQL, в план выполнения следующая.
1. Текст запроса (функция, триггер, хранимая процедура) попадает на вход Parser-а. Задача которого проста - выполнить синтаксический контроль введенного текста и нормализацию текста (удалить ненужные пробелы и форматировать во внутренний (нормальный) формат. На этом этапе из кода запроса рассчитывается sql_handle (внутренний идентификатор однозначно идентифицирующий этот код). sql_handle рассчитывается исходя из исходного (ненормализованного) текста запроса и поэтому такой же запрос, но с дополнительным символом "пробел" или оператором GO в одном и том же тексте, будет трактоваться как новый запрос, и для него будет рассчитан свой sql_handle, и создана еще одна запись в процедурном кэше.
Ниже приведены два абсолютно одинаковых запроса, отличающиеся наличием лишнего символа "пробел". Обратите внимание на то, что sql_handle у них разные, а вот query_hash и query_paln_hash одинаковы.
select *
from [Person].[Person] pp
inner join [Sales].[SalesPerson] ssp
on pp.BusinessEntityID=ssp.BusinessEntityID
where FirstName= 'Michael'
go
select *
from [Person].[Person] pp
inner join [Sales].[SalesPerson] ssp
on pp.BusinessEntityID=ssp.BusinessEntityID
where FirstName= 'Michael'
2. Нормализованный текст запроса попадает на Algebrizer. Его задача - превратить все ваши "измышления" на языке T-SQL в дерево разбора (Bound Tree), описывающее алгоритм выполнения запроса (кстати для View на этом все и заканчивается и дальнейшая обработка (оптимизация) не производится). Также, на данном этапе производится извлечение из кода запроса литералов. Цель этой операции - обеспечить возможность использования Auto Parameterization (Автопараметризации) для уменьшения количества планов выполнения. Ниже приведенные запросы будут иметь разные sql_handle (поскольку побайтово тексты запросов разные), но одинаковые query_hash и query_plan_hash поскольку дерево разбора и алгоритмы их выполнения одинаковы.
select *
from [Person].[Person] pp
inner join [Sales].[SalesPerson] ssp
on pp.BusinessEntityID=ssp.BusinessEntityID
where pp.BusinessEntityID = 274
go
select *
from [Person].[Person] pp
inner join [Sales].[SalesPerson] ssp
on pp.BusinessEntityID=ssp.BusinessEntityID
where pp.BusinessEntityID = 275
3. На этапе оптимизации решаются более сложные задачи.
Первая из них - это отыскание тривиального плана. Часть запросов достаточно просты и нет необходимости их оптимизировать, например, простейшие операторы INSERT, UPDATE, DELETE, SELECT не использующие никаких условий (предикатов). Для таких запросов хранятся готовые планы выполнения.
Вторая задача - это стандартизация. Это сложная задача по приведению разных конструкций, дающих один и тот же результат, к одной стандартной конструкции. На этом этапе сервер преобразует код T-SQL в некоторые стандартные (предварительно заготовленные разработчиками) формы деревьев, для того чтобы уменьшить возможное количество кандидатов на планы выполнения.
Например:
- соединение таблиц через оператор JOIN ... ON это одно и то же, что соединение через запятую и оператор WHERE,
- использование операторов WHERE ... BETWEEN ... AND ... дает тот же результат, что и операторы WHERE ... >=... и ...<=,
- использование оператор WHERE ... IN(....) дает тот же результат, что и операторы WHERE ... OR ...
- и т.д.
select *
from [Person].[Person] pp
inner join [Sales].[SalesPerson] ssp
on pp.BusinessEntityID=ssp.BusinessEntityID
where FirstName= 'Michael'
go
select *
from [Person].[Person] pp, [Sales].[SalesPerson] ssp
where pp.BusinessEntityID=ssp.BusinessEntityID and FirstName= 'Michael'
Обратите внимание, что несмотря на то, что тексты запросов абсолютно разные, query_hash (структуры деревьев разбора) также разные, а query_plan_hash одинаковы, благодаря встроенному "интеллекту" оптимизатора, "понимающему", что (по алгоритму выполнения) это один и тот же запрос.
Еще пример с операторами DISTINCT и GROUP BY.
select distinct FirstName
from [Person].[Person] pp
inner join [Sales].[SalesPerson] ssp
on pp.BusinessEntityID=ssp.BusinessEntityID
go
select FirstName
from [Person].[Person] pp
inner join [Sales].[SalesPerson] ssp
on pp.BusinessEntityID=ssp.BusinessEntityID
group by FirstName
Есть еще ряд задач решаемых в ходе оптимизации, но для их описания необходима целая серия статей.
Итак мы рассмотрели принципы работы системы компиляции запросов и связь их с query_hash и query_plan_hash. В следующих статьях мы сформулируем ряд рекомендаций по оптимизации использования процедурного кэша на основе полученных знаний.
Александр Каленик, Senior Premier Field Engineer (PFE), MSFT (Russia)



