План запроса строится один раз. Закэшированный план выполнения запроса SQL подвел. «фактические» и «оценочные» планы выполнения

Оптимизация запросов в SQL Server 2005, статистика баз данных SQL Server 2005, CREATE STATISTICS, UPDATE STATISTICS, SET NOCOUNT ON, планы выполнения запросов, количество логических чтений (logical reads), хинты оптимизатора (optimizer hints), MAXDOP, OPTIMIZE FOR, руководства по планам выполнения (plan guides), sp_create_plan_guide

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

Отношение к оптимизации запросов у многих специалистов неоднозначное. С одной стороны, работа программного модуля Query Optimizer , который генерирует планы выполнения запросов, вызывает множество справедливых нареканий и в SQL Server 2000, и в SQL Server 2005. Query Optimizer часто выбирает не самые оптимальные планы выполнения запросов и в некоторых ситуациях проигрывает аналогичным модулям из Oracle и Informix . С другой стороны, ручная оптимизация запросов - процесс чрезвычайно трудоемкий. Вы можете потратить много времени на такую оптимизацию и, в конце концов, выяснить, что ничего оптимизировать не удалось: план, предложенный Query Optimizer изначально, оказался наиболее оптимальным (так бывает в большинстве случаев). Кроме того, может случиться так, что созданный вами вручную план выполнения запросов через какое-то время (после добавления новой информации в базу данных) окажется неоптимальным и будет снижать производительность при выполнении запросов.

Отметим также, что для выбора наилучших планов построения запросов Query Optimizer необходима правильная информация о статистике. Поскольку, по опыту автора, далеко не все администраторы знают, что это такое, расскажем о статистике подробнее.

Статистика - это специальная служебная информация о распределении данных в столбцах таблиц. Представим, например, что выполняется запрос, который должен вернуть всех Ивановых, проживающих в городе Санкт-Петербурге. Предположим, что у 90% записей в этой таблице одно и то же значение в столбце Город - "Санкт-Петербург" . Конечно, с точки зрения выполнения запроса вначале выгоднее выбрать в таблице всех Ивановых (их явно будет не 90%), а затем уже проверять значение столбца Город для каждой отобранной записи. Однако для того, чтобы узнать, как распределяются значения в столбце, нужно вначале выполнить запрос. Поэтому SQL Server самостоятельно инициирует выполнение таких запросов, а потом сохраняет информацию о распределении данных (которая и называется статистикой) в служебных таблицах базы данных.

Для баз данных SQL Server 2005 по умолчанию устанавливаются параметры AUTO_CREATE_STATISTICS и AUTO_UPDATE_STATISTICS . При этом статистика для столбцов баз данных будет создаваться и обновляться автоматически. Для самых больших и важных баз данных может получиться так, что операции по созданию и обновлению статистики могут мешать текущей работе пользователей. Поэтому для таких баз данных иногда эти параметры отключают, а операции по созданию и обновлению статистики выполняют вручную в ночное время. Для этого используются команды CREATE STATISTICS и UPDATE STATISTICS .

Теперь поговорим об оптимизации запросов.

Первое, что необходимо сделать, - найти те запросы, которые в первую очередь подлежат оптимизации. Проще всего это сделать при помощи профилировщика, установив фильтр на время выполнения запроса (фильтр Duration в окне Edit Filter (Редактировать фильтр), которое можно открыть при помощи кнопки Column Filters на вкладке Events Selection окна свойств сеанса трассировки). Например, в число кандидатов на оптимизацию могут попасть запросы, время выполнения которых составило более 5секунд. Кроме того, можно использовать информацию о запросах, которая предоставляется Database Tuning Advisor .

Затем нужно проверить, устанавлен ли для ваших соединений, хранимых процедур и функций параметр NOCOUNT . Установить его можно при помощи команды SET NOCOUNT ON . При установке этого параметра, во-первых, отключается возврат с сервера и вывод информации о количестве строк в результатах запроса (т. е. не отображается строка "N row(s) affected" на вкладке Messages (C ообщения) окна работы с кодом при выполнении запроса в Management Studio ). Во-вторых, отключается передача специального серверного сообщения DONE_IN_PROC , которое по умолчанию возвращается для каждого этапа хранимой процедуры. При вызове большинства хранимых процедур нужен только результат их выполнения, а количество обработанных строк для каждого этапа никого не интересует. Поэтому установка параметра NOCOUNT для хранимых процедур может серьезно повысить их производительность. Повышается скорость выполнения и обычных запросов, но в меньшей степени (до 10%).

После этого можно приступать к работе с планами выполнения запросов.

План выполнения запроса проще всего просмотреть из SQL Server Management Studio . Для того чтобы получить информацию об ожидаемом плане выполнения запроса, можно в меню Query (Запрос) выбрать команду Display Estimated Execution Plan (Отобразить ожидаемый план выполнения). Если вы хотите узнать реальный план выполнения запроса, можно перед его выполнением установить в том же меню параметр Include Actual Execution Plan (Включить реальный план выполнения). В этом случае после выполнения запроса в окне результатов в SQL Server Management Studio появится еще одна вкладка Execution Plan (План выполнения), на которой будет представлен реальный план выполнения запроса. При наведении указателя мыши на любой из этапов можно получить о нем дополнительную информацию (рис. 11.15).

Рис. 11.15. План выполнения запроса в SQL Server Management Studio

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

q вначале в окне Management Studio выполните команду SET STATISTICS IO ON . В результате после каждого выполнения запроса будет выводиться дополнительная информация. В ней нас интересует значение только одного параметра - Logical Reads . Этот параметр означает количество логических чтений при выполнении запросов, т. е. сколько операций чтения пришлось провести при выполнении данного запроса без учета влияния кэша (количество чтений и из кэша, и с диска). Это наиболее важный параметр. Количество физических чтений (чтений только с диска) - информация не очень представительная, поскольку зависит от того, были ли перед этим обращения к данным таблицам или нет. Статистика по времени также является величиной переменной и зависит от других операций, которые выполняет в это время сервер. А вот количество логических чтений - наиболее объективный показатель, на который в наименьшей степени влияют дополнительные факторы;

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

Хинтов оптимизатора в SQL Server 2005 предусмотрено много. Прочитать информацию о них можно в Books Online (в списке на вкладке Index (Индекс) нужно выбрать Query Hints [ SQL Server ] (Хинты запросов ), Join Hints (Хинты джойнов) или Table Hints [ SQL Server ] (Табличные хинты )). Чаще всего используются следующие хинты:

q NOLOCK , ROWLOCK , PAGLOCK , TABLOCK , HOLDLOCK , READCOMMITTEDLOCK , UPDLOCK , XLOCK - эти хинты используются для управления блокировками (см. разд. 11.5.7) ;

q FAST количество_строк - будет выбран такой план выполнения запроса, при котором максимально быстро будет выведено указанное количество строк (первых с начала набора записей). Если пользователю нужны именно первые записи (например, последние заказы), то для их максимально быстрой загрузки в окно приложения можно использовать этот хинт;

q FORCE ORDER - объединение таблиц при выполнении запроса будет выполнено точно в том порядке, в котором эти таблицы перечислены в запросе;

q MAXDOP (от Maximum Degree of Parallelism - максимальная степень распараллеливания запроса) - при помощи этого хинта указывается максимальное количество процессоров, которые можно будет использовать для выполнения запроса. Обычно этот хинт используется в двух ситуациях:

· когда из-за переключения между процессорами (context switching ) скорость выполнения запроса сильно снижается. Такое поведение было характерно для SQL Server 2000 на многопроцессорных системах;

· когда вы хотите, чтобы какой-то тяжелый запрос оказал минимальное влияние на текущую работу пользователей;

q OPTIMIZE FOR - этот хинт позволяет указать, что запрос оптимизируется под конкретное значение передаваемого ему параметра (например, под значение фильтра для WHERE );

q USE PLAN - это самая мощная возможность. При помощи такого хинта можно явно определить план выполнения запроса, передав план в виде строкового значения в формате XML . Хинт USE PLAN появился только в SQL Server 2005 (в предыдущих версиях была возможность явно определять планы выполнения запросов, но для этого использовались другие средства). План в формате XML можно написать вручную, а можно сгенерировать автоматически (например, щелкнув правой кнопкой мыши по графическому экрану с планом выполнения, представленному на рис. 11.15, и выбрав в контекстном меню команду Save Execution Plan As (Сохранить план выполнения как)).

В SQL Server 2005 появилась новая важная возможность, которая позволяет вручную менять план выполнения запроса без необходимости вмешиваться в текст запроса. Очень часто бывает так, что код запроса нельзя изменить: он жестко "прошит" в коде откомпилированного приложения. Чтобы справиться с этой проблемой, в SQL Server 2005 появилась хранимая процедура sp_create_plan_guide . Она позволяет создавать так называемые руководства по планам выполнения (plan guides ), которые будут автоматически применяться к соответствующим запросам.

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

q насколько часто в планах выполнения запроса встречается операция Table Scan (Полное сканирование таблицы). Вполне может оказаться, что обращение к таблице при помощи индексов будет эффективнее;

q используются ли в коде курсоры. Курсоры - очень простое средство с точки зрения синтаксиса программы, но чрезвычайно неэффективное с точки зрения производительности. Очень часто можно избежать применения курсоров, используя другие синтаксические конструкции, и получить большой выигрыш в скорости работы;

q используются ли в коде временные таблицы или тип данных Table . Создание временных таблиц и работа с ними требуют большого расхода ресурсов, поэтому по возможности нужно их избегать;

q кроме создания временных таблиц, значительного расхода системных ресурсов требует и изменение их структуры. Поэтому команды на изменение структуры временных таблиц должны сразу привлекать ваше внимание. Обычно есть возможность сразу создать временную таблицу со всеми необходимыми столбцами;

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

q если приложение передает на сервер команды EXECUTE , то имеет смысл подумать о том, чтобы заменить их на вызов хранимой процедуры sp_executesql . Она обладает преимуществами в производительности по сравнению с обычной командой EXECUTE ;

q повышения производительности иногда можно добиться, устранив необходимость повторной компиляции хранимых процедур и построения новых планов выполнения запросов. Нужно обратить внимание на применение параметров, постараться не смешивать в коде хранимой процедуры команды DML и DDL и следить за тем, чтобы параметры подключения SET ANSI_DEFAULTS , SET ANSI_NULLS , SET ANSI_PADDING , SET ANSI_WARNINGS и SET CONCAT_NULL_YIELDS_NULL не изменялись между запросами (любое изменение таких параметров приводит к тому, что старые планы выполнения считаются недействительными). Обычно проблема может возникнуть тогда, когда эти параметры устанавливаются на уровне отдельного запроса или в коде хранимой процедуры.

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

Наверное, каждый 1С-ник задавался вопросом "что быстрее, соединение или условие в ГДЕ?" или, например, "сделать вложенный запрос или поставить оператор В()"?

После чего 1С-ник идет на форум, а там ему говорят - надо смотреть план запроса. Он смотрит, и ничего не понимая, навсегда забрасывает идею оптимизации запросов через планы, продолжая сравнивать варианты простым замером производительности.

В результате, на машине разработчика запрос начинает просто летать, а затем в боевой базе при увеличении количества записей все умирает и начинаются жалобы в стиле "1С тормозит". Знакомая картинка, не правда ли?

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

Более того, я не считаю себя хорошим оптимизатором запросов, поэтому, в статье весьма вероятны фактологические косяки. Ну тут пусть гуру меня поправят в каментах. На то мы тут и сообщество, чтобы помогать друг-другу, верно?

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

Как работает компьютер

А начну я издалека. Дело в том, что компьютеры, к которым мы привыкли, они не такие уж и умные. Вы же наверняка помните первые уроки информатики, или младшие курсы ВУЗа? Помните сортировку массивов пузырьком там, или чтение файла построчно? Так вот, принципиально нового ничего не изобретено в современных реляционных СУБД.

Если на лабораторках вы считывали строчки из файла, а потом записывали их в другое место, то вы уже примерно представляете, как работает современная СУБД. Да, разумеется, там все намного (совсем намного) сложнее, но - циклы они и в Африке циклы, чтение диска все еще не стало быстрее чтения ОЗУ, а алгоритмы O(N) все еще медленнее алгоритмов O(1) при увеличении N.

Давайте представим, что к вам, простому 1С-нику пришел человек и говорит: "смотри, дружище, надо написать базу данных. Вот тут файл, в нем строчки какие-нибудь пиши. А потом оттуда читай". Представим, что отказаться вы не можете. Как бы вы решали эту задачу?

А решали бы вы ее точно так же, как решают ее ребята из Microsoft, Oracle, Postgres и 1С. Вы бы открыли файл средствами вашего языка программирования, прочитали бы оттуда строки и вывели бы их на экран. Никаких принципиально отличных алгоритмов, от тех, что я уже описал - мир не придумал.

Представьте, что у вас есть 2 файла. В одном записаны контрагенты, а в другом - договоры контрагентов. Как бы вы реализовывали операцию ВНУТРЕННЕЕ СОЕДИНЕНИЕ? Вот прямо в лоб, без каких-либо оптимизаций?

Контрагенты

Договоры

IDКонтрагента

НомерДоговора

Давайте сейчас для простоты опустим нюансы открывания файлов и чтения в память. Сосредоточимся на операции соединения. Как бы вы его делали? Я бы делал так:

Для Каждого СтрокаКонтрагент Из Контрагенты Цикл Для Каждого СтрокаДоговор Из Договоры Цикл Если СтрокаДоговор.IDКонтрагента = СтрокаКонтрагент.ID Тогда ВывестиРезультатСоединения(СтрокаКонтрагент, СтрокаДоговор); КонецЕсли; КонецЦикла; КонецЦикла;

В примере ф-я ВывестиРезультатСоединения просто выведет на экран все колонки из переданных строк. Ее код здесь не существенен.

Итак, мы видим два вложенных цикла. Внешний по одной таблице, а потом во внутреннем - поиск ключа из внешней простым перебором. А теперь, внезапно, если вы откроете план какого-нибудь запроса с СОЕДИНЕНИЕМ в любой из 1С-ных СУБД, то с довольно высокой вероятностью увидите там конструкцию "Nested Loops". Если перевести это с языка вероятного противника на наш, то получится "Вложенные циклы". То есть, в "плане запроса" СУБД вам объясняет, что вот тут, для "соединения" она применила алгоритм, описанный выше. Этот алгоритм способен написать любой школьник примерно 7-го класса. И мощные боевые СУБД мирового уровня применяют этот алгоритм совершенно спокойно. Ибо в некоторых ситуациях - он лучшее, что есть вообще.

И вообще, чего это я сразу с "соединения" начал. Давайте предположим, что вам нужно просто найти контрагента по наименованию. Как бы вы решали эту задачу? Вот есть у вас файл с контрагентами. Напишите алгоритм. Я напишу его вот так:

Для Каждого СтрокаКонтрагент Из Контрагенты Цикл Если СтрокаКонтрагент.Имя = "Иванов" Тогда ВывестиРезультат(СтрокаКонтрагент); КонецЕсли; КонецЦикла;

Нет, ну серьезно, а как еще его можно написать? А никак по сути. Если неизвестно в каком порядке лежат записи в таблице, то придется пересмотреть ее всю, как ни крути. На языке планов запроса это называется Scan. Сканирование. Полный просмотр данных и ничего больше.

Индексы

А как же мы можем ускорить поиск данных в таблице? Ну правда, всё время пересматривать всё - это же зло какое-то.

Вспомним картотеку в поликлинике или библиотеке. Как там выполняется поиск по фамилии клиента? В деревянных шкафчиках стоят аккуратные карточки с буквами от А до Я. И пациент "Пупкин" находится в шкафчике с карточкой "П". Просматривать подряд все прочие буквы нет необходимости. Если мы отсортируем данные в нашей таблице и будем знать, где у нас (под какими номерами строк) находятся записи на букву "П", то мы существенно приблизимся к быстродействию тетеньки из регистратуры. А это уже лучше, чем полный перебор, не так ли?

Так вот, слово "Индекс" в данном контексте означает (опять же, в переводе с языка вероятного противника) "Оглавление". Чтобы быстро найти главу в книге, вы идете в оглавление, находите там название главы, потом смотрите номер страницы и идёте сразу на эту страницу.

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

В виде кода это может выглядеть примерно так:

Индекс = Новый Соответствие; // бла-бла НомерЗаписи = Индекс["Иванов"] ВывестиРезультат(ТаблицаКонтрагентов[НомерЗаписи]);

Известно, что чудес не бывает, поэтому, память под Соответствие "Индекс", а также поиск в самом соответствии - это небесплатные операции. Но они намного дешевле, чем прямой перебор всех данных. Ах, да, это Соответствие придется постоянно поддерживать в актуальном состоянии при добавлении или изменении основных данных.

Теперь давайте подумаем, а как бы вы реализовывали сам этот индекс? Можно хранить записи в файле данных сразу в отсортированном виде. И все бы ничего, но, во-первых, искать надо каждый раз по разным полям, а во-вторых, если в уже заполненную от А до Я таблицу пользователь захочет вставить запись на букву М? А ведь он захочет, я вас уверяю.

Вспомним, как вообще ведется запись в файл.

Fseek(file, position); // переход к нужному адресу write(file, dataArray, dataLength); // запись dataLength байт из массива dataArray

Если адрес position указывает куда-то в середину файла, и на этом месте есть данные, то они затираются новыми. Если нужно вставить что-то в середину файла (и массива в памяти в том числе) то нужно в явном виде "подвинуть" все, что находится после position, освободив место, а уже потом писать новые данные. Как вы понимаете, "подвижка" данных это опять же циклы и операции ввода/вывода. То есть, не так уж быстро. Ничего в компьютере "само" не происходит. Все по команде.

Вернемся к индексу. Пользователь хочет вставить что-то в середину. Хочешь не хочешь, а придется двигать данные, либо исхитряться с хранением данных в "страницах", связанных между собой в список. Физически писать в конец, или в пустое место, но как будто в середину таблицы. И потом еще обновлять в оглавлении номера записей. Они же теперь сдвинулись и индекс показывает не туда куда нужно. Вы, наверное, слышали, что индексы в БД ускоряют поиск, но замедляют вставку и удаление. Теперь, вы знаете, почему это так.

Ну так вот, мы еще не решили проблему поиска по разным полям. Мы же не можем хранить данные в файле в разном порядке. Одному пользователю по имени, а другому, скажем - по дате. Причем одновременно. Как бы вы решали эту задачу? По-моему, решение очевидно - нужно хранить отдельно данные и отдельно оглавления, отсортированные по нужным полям. Т.е. в базе данные лежат, как придется, но рядышком мы создадим файлик, где записи отсортированы по имени. Это будет индекс по полю "Имя". А еще рядышком будет другой такой же файлик, но отсортированный по полю "Дата". Для экономии места мы будем хранить в индексах не все колонки основной таблицы, а только те, по которым выполнена сортировка (чтобы быстро тут искать, находить номер записи и моментально прыгать к ней, чтоб прочитать остальные данные).

Ребята, которые пишут взрослые СУБД тоже не придумали ничего лучше. Индексы в БД устроены именно так. Все данные из таблицы лежат отсортированными рядышком в отдельной сущности. По сути, индекс, это просто еще одна таблица. И места она занимает пропорционально размеру основной таблицы, что логично. Да, там еще есть разные ухищрения, типа сбалансированных деревьев и всякого такого, но смысл не сильно меняется.

Кстати, если записывать данные в основную таблицу сразу упорядоченными, то можно не делать отдельно хранимый индекс и считать индексом саму таблицу с данными. Здорово, правда? Такой индекс называют "кластерным". Логично, что поле, по которому отсортированы записи в таблице должно стараться монотонно нарастать. Вы же помните про вставку в середину, верно?

Планирование выполнения запроса

Представьте, что у вас таблица в пять миллионов записей. И есть у нее индекс. Надо быстренько найти запись со словом "Привет". А еще представьте, что у вас такая же таблица, но с тремя записями. И тоже надо найти "Привет". Какой способ поиска выбрать? Открыть файл индекса, пробежаться по нему двоичным поиском, найти нужный номер записи, открыть файл основной таблицы, перейти к записи по ее номеру, прочитать ее? Или запустить цикл от одного до трех, проверив каждую запись на соответствие условию? Современный компьютер циклы от одного до трех выполняет просто ужас, как быстро.

Чтобы принять решение, планировщик запроса должен понимать, с чем имеет дело. Он оперирует такой штукой, как статистика. Статистика включает в себя количество записей по таблицам, распределение данных по колонкам, селективность и прочее и прочее. Это все подсказки планировщику о том, какой способ сбора данных будет быстрее. Не самым быстрым из возможных, а хотя бы достаточно быстрым с некоторой вероятностью. И у планировщика ограничено время на принятие решения. мы же хотим быстро получить данные, а не ждать, пока он там планирует себе.

Вот тут уже я бы не стал браться за работу по написанию планировщика, не защитив предварительно диссертацию. Как он там работает и как умудряется делать это вполне сносно - не знаю. Поэтому, ограничимся документацией СУБД. Из нее следует, что на основании статистики планировщик строит несколько возможных вариантов пошагового выполнения запроса, а потом выбирает из них наиболее подходящий. Например, первый попавшийся. Тоже ведь эвристика, разве нет?

"Что мне сделать сначала" - думает планировщик: "обойти всю таблицу А, отобрав записи по условию, а потом соединить с таблицей Б вложенными циклами, или же найти индексом все подходящие записи таблицы Б, а уже потом пробежаться по таблице А"? Каждый из шагов имеет определенный вес или стоимость. Чем больше стоимость, тем сложнее выполнять. В плане запросов всегда написана стоимость каждого из шагов, которые выполнил движок СУБД, чтобы собрать результаты запроса.

Устройство оператора плана

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

Interface IQueryOperator { DataRow GetNextRow(); }

для тех кто не понял, что тут написано, поясню. Каждый оператор плана запросов имеет метод "ДайСледующуюЗапись". Движок СУБД дергает оператор за этот метод и при каждом таком дергании добавляет полученную запись к результату запроса. Например, оператор фильтрации записей на входе имеет всю таблицу, а на выходе - только те, которые удовлетворяют условию. Далее, выход этого оператора подается на оператор, например, ПЕРВЫЕ 100, а далее на оператор агрегации (СУММА или КОЛИЧЕСТВО), которые точно так же, внутри инкапсулируют всю обработку, а на выход выдают запись с результатом.

Схематично это выглядит так:

ВсеДанные ->Фильтр(Имя="Петров")->Первые(100)->Аггрегация(КОЛИЧЕСТВО)

Когда вы откроете план запроса, то увидите кубики, соединенные стрелочками. Кубики - это операторы. Стрелочки - направление потоков данных. Данные бегут по стрелочкам от одного оператора к другому, сливаясь в конце в результат запроса.

Каждый оператор имеет некие параметры: количество обработанных записей, стоимость, количество операций ввода/вывода, использование кэшей и прочее и прочее. Все это позволяет судить об эффективности выполнения запроса. Scan таблицы, пробежавший миллион записей и выдавший две на выходе - это не очень хороший план запроса. Но лучше планировщик ничего не нашел. У него не было индекса, чтобы поискать в нем. А может, наврала статистика и сказала, что в таблице три записи, а на самом деле туда успели написать миллион штук, но статистику не обновили. Все это предмет для разбирательства инженером, который изучает запрос.

План запроса - это пошаговый отладчик запроса. Вы пошагово смотрите, что именно, какой алгоритм (в буквальном смысле) применила СУБД, чтобы выдать результат. Примеры самих алгоритмов вы видели - они чрезвычайно сложны, ведь там есть циклы и условия. Даже порой несколько циклов вложены, вот ведь ужас. Важно понимать, какие процессы происходят внутри каждого оператора. Какой алгоритм применялся к массиву записей в процессе выполнения и сколько он работал.

Конкретные операторы, встречающиеся в планах запроса и их внутреннее устройство я планирую рассмотреть в следующей статье. Спасибо за то, что прочитали до конца!

Существует несколько способов получения плана выполнения, который использовать будет зависеть от ваших обстоятельств. Обычно вы можете использовать SQL Server Management Studio для получения плана, однако, если по какой-то причине вы не можете запустить свой запрос в SQL Server Management Studio, вам может оказаться полезным получить план через SQL Server Profiler или путем проверки кэш плана.

Метод 1 - Использование SQL Server Management Studio

SQL Server поставляется с несколькими опрятными функциями, которые позволяют легко выполнить план выполнения, просто убедитесь, что пункт меню «Включить фактический исполняемый план» (найденный в меню «Запрос») отмечен галочкой и запускает ваш запрос как обычно,

Если вы пытаетесь получить план выполнения для инструкций в хранимой процедуре, вы должны выполнить хранимую процедуру, например:

Exec p_Example 42

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

Отсюда вы можете проверить план выполнения в SQL Server Management Studio или щелкнуть правой кнопкой мыши по плану и выбрать «Сохранить план выполнения как...», чтобы сохранить план в файл в формате XML.

Способ 2 - Использование опций SHOWPLAN

Этот метод очень похож на метод 1 (на самом деле это то, что делает SQL Server Management Studio внутренне), однако я включил его для полноты или если у вас нет доступной SQL Server Management Studio.

Перед выполнением запроса запустите один следующих операторов. Оператор должен быть единственным оператором в пакете, т. Е. Вы не можете выполнять другой оператор одновременно:

SET SHOWPLAN_TEXT ON SET SHOWPLAN_ALL ON SET SHOWPLAN_XML ON SET STATISTICS PROFILE ON SET STATISTICS xml ON -- The is the recommended option to use

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

Как только вы закончите, вы можете отключить этот параметр со следующим утверждением:

SET < > OFF

Сравнение форматов плана выполнения

Если у вас нет сильных предпочтений, я рекомендую использовать параметр STATISTICS xml . Эта опция эквивалентна опции «Включить фактический план выполнения» в SQL Server Management Studio и предоставляет самую большую информацию в наиболее удобном формате.

  • SHOWPLAN_TEXT - Displays a basic text based estimated execution plan, without executing the query
  • SHOWPLAN_ALL - Displays a text based estimated execution plan with cost estimations, without executing the query
  • SHOWPLAN_XML - Displays an xml based estimated execution plan with cost estimations, without executing the query. This is equivalent to the "Display Estimated Execution Plan..." option in SQL Server Management Studio.
  • STATISTICS PROFILE - Executes the query and displays a text based actual execution plan.
  • STATISTICS XML - Executes the query and displays an xml based actual execution plan. This is equivalent to the "Include Actual Execution Plan" option in SQL Server Management Studio.

Способ 3 - Использование SQL Server Profiler

Если вы не можете запустить свой запрос напрямую (или ваш запрос не выполняется медленно при его непосредственном запуске - помните, что мы хотим, чтобы план запроса выполнялся плохо), вы можете сделать план с использованием трассировки Profiler SQL Server. Идея состоит в том, чтобы запустить ваш запрос, пока трассировка, которая захватывает один из событий «Showplan», запущена.

Обратите внимание, что в зависимости от нагрузки вы можете использовать этот метод в рабочей среде, однако вы должны, очевидно, проявлять осторожность. Механизмы профилирования SQL Server предназначены для минимизации влияния на базу данных, но это не означает, что влияние производительности any не будет. У вас может также возникнуть проблема с фильтрацией и определением правильного плана в вашей трассе, если ваша база данных находится под большим использованием. Вы должны, очевидно, проверить с вашим администратором базы данных, чтобы убедиться, что они довольны тем, что вы делаете это в своей драгоценной базе данных!

  1. Open SQL Server Profiler and create a new trace connecting to the desired database against which you wish to record the trace.
  2. Under the "Events Selection" tab check "Show all events", check the "Performance" -> "Showplan XML" row and run the trace.
  3. While the trace is running, do whatever it is you need to do to get the slow running query to run.
  4. Wait for the query to complete and stop the trace.
  5. To save the trace right click on the plan xml in SQL Server Profiler and select "Extract event data..." to save the plan to file in xml format.

План, который вы получаете, эквивалентен опции «Включить фактический план выполнения» в SQL Server Management Studio.

Способ 4. Проверка кэша запросов.

Если вы не можете выполнить свой запрос напрямую, и вы также не можете захватить трассировку профилировщика, вы можете получить оценочный план, проверив кеш-план SQL-запросов.

Мы проверяем кеш плана, запрашивая SQL Server DMVs . Ниже приведен базовый запрос, в котором будут перечислены все кэшированные планы запросов (как xml) вместе с их текстом SQL. В большинстве баз данных вам также необходимо будет добавить дополнительные условия фильтрации, чтобы отфильтровать результаты вплоть до планов, которые вас интересуют.

SELECT UseCounts, Cacheobjtype, Objtype, TEXT, query_plan FROM sys.dm_exec_cached_plans CROSS APPLY sys.dm_exec_sql_text(plan_handle) CROSS APPLY sys.dm_exec_query_plan(plan_handle)

Выполните этот запрос и щелкните на плане XML, чтобы открыть план в новом окне - щелкните правой кнопкой мыши и выберите «Сохранить план выполнения как...», чтобы сохранить план в файл в формате XML.

Заметки:

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

Вы не можете зафиксировать план выполнения для зашифрованных хранимых процедур.

«фактические» и «оценочные» планы выполнения

План выполнения фактический - это тот, где SQL Server фактически выполняет запрос, тогда как план выполнения оцененный SQL Server выполняет то, что он делает без выполнения запрос. Хотя логически эквивалентный, фактический план выполнения намного полезнее, поскольку он содержит дополнительные данные и статистику о том, что на самом деле произошло при выполнении запроса. Это важно при диагностике проблем, когда оценки SQL-серверов отключены (например, когда статистика устарела).

Как интерпретировать план выполнения запроса?

Это тема, достойная достаточно для бесплатного в своем собственном праве.

  • SQL Server 2008 - использование хеш-запросов и хэш-планов запроса
  • >

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

DECLARE @TraceID INT EXEC StartCapture @@SPID, @TraceID OUTPUT EXEC sp_help "sys.objects" /*<-- Call your stored proc of interest here.*/ EXEC StopCapture @TraceID

Пример StartCapture Определение

CREATE PROCEDURE StartCapture @Spid INT, @TraceID INT OUTPUT AS DECLARE @maxfilesize BIGINT = 5 DECLARE @filepath NVARCHAR(200) = N"C:\trace_" + LEFT(NEWID(),36) EXEC sp_trace_create @TraceID OUTPUT, 0, @filepath, @maxfilesize, NULL exec sp_trace_setevent @TraceID, 122, 1, 1 exec sp_trace_setevent @TraceID, 122, 22, 1 exec sp_trace_setevent @TraceID, 122, 34, 1 exec sp_trace_setevent @TraceID, 122, 51, 1 exec sp_trace_setevent @TraceID, 122, 12, 1 -- filter for spid EXEC sp_trace_setfilter @TraceID, 12, 0, 0, @Spid -- start the trace EXEC sp_trace_setstatus @TraceID, 1

Пример StopCapture Определение

CREATE PROCEDURE StopCapture @TraceID INT AS WITH XMLNAMESPACES ("http://schemas.microsoft.com/sqlserver/2004/07/showplan" as sql), CTE as (SELECT CAST(TextData AS VARCHAR(MAX)) AS TextData, ObjectID, ObjectName, EventSequence, /*costs accumulate up the tree so the MAX should be the root*/ MAX(EstimatedTotalSubtreeCost) AS EstimatedTotalSubtreeCost FROM fn_trace_getinfo(@TraceID) fn CROSS APPLY fn_trace_gettable(CAST(value AS NVARCHAR(200)), 1) CROSS APPLY (SELECT CAST(TextData AS XML) AS xPlan) x CROSS APPLY (SELECT T.relop.value("@EstimatedTotalSubtreeCost", "float") AS EstimatedTotalSubtreeCost FROM xPlan.nodes("//sql:RelOp") T(relop)) ca WHERE property = 2 AND TextData IS NOT NULL AND ObjectName not in ("StopCapture", "fn_trace_getinfo") GROUP BY CAST(TextData AS VARCHAR(MAX)), ObjectID, ObjectName, EventSequence) SELECT ObjectName, SUM(EstimatedTotalSubtreeCost) AS EstimatedTotalSubtreeCost FROM CTE GROUP BY ObjectID, ObjectName -- Stop the trace EXEC sp_trace_setstatus @TraceID, 0 -- Close and delete the trace EXEC sp_trace_setstatus @TraceID, 2 GO

Помимо методов, описанных в предыдущих ответах, вы также можете использовать бесплатный просмотрщик планов выполнения и инструмент оптимизации запросов План ApexSQL (с которым я недавно столкнулся).

Вы можете установить и интегрировать план ApexSQL в SQL Server Management Studio, поэтому планы выполнения можно просматривать непосредственно из SSMS.

Просмотр расчетных планов выполнения в Плане ApexSQL

  1. Нажмите кнопку Новый запрос в SSMS и вставьте текст запроса в текстовое окно запроса. Щелкните правой кнопкой мыши и выберите «Отображать примерный план выполнения» в контекстном меню.

  1. На диаграмме плана выполнения будет показана вкладка Планирование выполнения в разделе результатов. Затем щелкните правой кнопкой мыши план выполнения и в контекстном меню выберите вариант «Открыть в ApexSQL Plan».

  1. Предполагаемый план выполнения будет открыт в Плане ApexSQL и может быть проанализирован для оптимизации запросов.

Просмотр фактических планов выполнения в Плане ApexSQL

Чтобы просмотреть фактический план выполнения запроса, продолжайте со второго шага, упомянутого ранее, но теперь, как только появится оценочный план, нажмите кнопку «Фактический» на главной панели ленты в Плане ApexSQL.

После нажатия кнопки «Фактический» будет показан фактический план выполнения с подробным предварительным просмотром параметров затрат вместе с другими данными плана выполнения.

Более подробную информацию о просмотре планов выполнения можно найти, следуя этой ссылке .

Мой любимый инструмент для получения и глубокого анализа планов выполнения запросов - SQL Sentry Plan Explorer . Это гораздо удобнее, удобнее и полно для детального анализа и визуализации планов выполнения, чем SSMS.

Вот примерный снимок экрана, чтобы вы поняли, какая функциональность предлагается инструментом:

Это только один из видов, доступных в инструменте. Обратите внимание на набор вкладок в нижней части окна приложения, что позволяет вам получать различные типы представления плана выполнения и полезную дополнительную информацию.

Кроме того, я не заметил никаких ограничений в его бесплатной версии, которая не позволяет использовать ее ежедневно или заставляет вас покупать версию Pro в конечном итоге. Итак, если вы предпочитаете придерживаться бесплатной версии, вам ничего не запрещается.

UPDATE: (Thanks to Martin Smith ) Plan Explorer now is free! See http://www.sqlsentry.com/products/plan-explorer/sql-server-query-view for details.

Планы запросов можно получить в сеансе расширенных событий через событие query_post_execution_showplan . Вот пример сеанса XEvent:

/* Generated via "Query Detail Tracking" template. */ CREATE EVENT SESSION ON SERVER ADD EVENT sqlserver.query_post_execution_showplan(ACTION(package0.event_sequence,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack)), /* Remove any of the following events (or include additional events) as desired. */ ADD EVENT sqlserver.error_reported(ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.database_id,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack) WHERE (.(.,(4)) AND .(.,(0)))), ADD EVENT sqlserver.module_end(SET collect_statement=(1) ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.database_id,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack) WHERE (.(.,(4)) AND .(.,(0)))), ADD EVENT sqlserver.rpc_completed(ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.database_id,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack) WHERE (.(.,(4)) AND .(.,(0)))), ADD EVENT sqlserver.sp_statement_completed(SET collect_object_name=(1) ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.database_id,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack) WHERE (.(.,(4)) AND .(.,(0)))), ADD EVENT sqlserver.sql_batch_completed(ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.database_id,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack) WHERE (.(.,(4)) AND .(.,(0)))), ADD EVENT sqlserver.sql_statement_completed(ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.database_id,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack) WHERE (.(.,(4)) AND .(.,(0)))) ADD TARGET package0.ring_buffer WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=30 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF) GO

После создания сеанса (в SSMS) перейдите в Обозреватель объектов и перейдите в раздел Управление | Расширенные события | Сессии. Щелкните правой кнопкой мыши сеанс «GetExecutionPlan» и запустите его. Щелкните его правой кнопкой мыши и выберите «Watch Live Data».

Затем откройте новое окно запроса и запустите один или несколько запросов. Вот для AdventureWorks:

USE AdventureWorks; GO SELECT p.Name AS ProductName, NonDiscountSales = (OrderQty * UnitPrice), Discounts = ((OrderQty * UnitPrice) * UnitPriceDiscount) FROM Production.Product AS p INNER JOIN Sales.SalesOrderDetail AS sod ON p.ProductID = sod.ProductID ORDER BY ProductName DESC; GO

Через минуту или два вы увидите некоторые результаты на вкладке «GetExecutionPlan: Live Data». Выберите одно из событий query_post_execution_showplan в сетке, а затем щелкните вкладку «План запроса» под сеткой. Он должен выглядеть примерно так:

EDIT : The XEvent code and the screen shot were generated from SQL/SSMS 2012 w/ SP2. If you"re using SQL 2008/R2, you might be able to tweak the script to make it run. But that version doesn"t have a GUI, so you"d have to extract the showplan XML, save it as a *.sqlplan file and open it in SSMS. That"s cumbersome. XEvents didn"t exist in SQL 2005 or earlier. So, if you"re not on SQL 2012 or later, I"d strongly suggest one of the other answers posted here.

Начиная с SQL Server 2016+, для мониторинга производительности была представлена ​​функция Query Store. Он дает представление о выборе плана выполнения и производительности. Это не полная замена трассировки или расширенных событий, но поскольку она развивается от версии к версии, мы можем получить полностью функциональный магазин запросов в будущих выпусках SQL Server. Основной поток Query Store

  1. SQL Server existing components interact with query store by utilising Query Store Manager.
  2. Query Store Manager determines which Store should be used and then passes execution to that store (Plan or Runtime Stats or Query Wait Stats)
    • Plan Store - Persisting the execution plan information
    • Runtime Stats Store - Persisting the execution statistics information
    • Query Wait Stats Store - Persisting wait statistics information.
  3. Plan, Runtime Stats and Wait store uses Query Store as an extension to SQL Server.

    Enabling the Query Store : Query Store works at the database level on the server.

    • Query Store is not active for new databases by default.
    • You cannot enable the query store for the master or tempdb database.
    • Available DMV
  1. Collect Information in the Query Store : We collect all the available information from the three stores using Query Store DMV (Data Management Views).

    • Query Plan Store: Persisting the execution plan information and it is accountable for capturing all information that is related to query compilation.

      Runtime Stats Store: Persisting the execution statistics information and it is probably the most frequently updated store. These statistics represent query execution data.

      . У последнего также есть полезная бонусная функция выбора плана для конкретного оператора , а не всей партии. Вот как вы используете его для просмотра планов текущих запущенных операторов:

      SELECT p.query_plan FROM sys.dm_exec_requests AS r OUTER APPLY sys.dm_exec_text_query_plan(r.plan_handle, r.statement_start_offset, r.statement_end_offset) AS p

      Текстовый столбец в результирующей таблице, однако, не очень удобен по сравнению с столбцом XML. Чтобы иметь возможность щелкнуть результат, который будет открыт на отдельной вкладке в виде диаграммы, без необходимости сохранять его содержимое в файл, вы можете использовать небольшой трюк (помните, что вы не можете просто использовать CAST (... AS XML)), хотя это будет работать только для одной строки:

      SELECT Tag = 1, Parent = NULL, = query_plan FROM sys.dm_exec_text_query_plan(-- set these variables or copy values -- from the results of the above query @plan_handle, @statement_start_offset, @statement_end_offset) FOR xml EXPLICIT

1 msdevcon.ru #msdevcon

3 Олонцев Сергей SQL Server MCM, MVP Лаборатория Касперского

4 Structured Query Language

5 Пример запроса select pers.firstname, pers.lastname, emp.jobtitle, emp.nationalidnumber from HumanResources.Employee as emp inner join Person.Person as pers on pers.businessentityid = emp.businessentityid where pers.firstname = N"John" and emp.hiredate >= " "

6 Логическое дерево запроса Project pers.firstname, pers.lastname, emp.jobtitle, emp.nationalidnumber D A T A Filter Join pers.firstname = N"John" and emp.hiredate >= " " pers.businessentityid = emp.businessentityid Person.Person as pers Get Data Get Data HumanResources.Employee as emp

7 План запроса Показывает, как происходит исполнение T-SQL запроса на физическом уровне.

8 Несколько способов

9 DEMO Простой план Выбор всех данных из таблицы, как получить план запроса

11 Методы оператора Init() Метод Init() заставляет физический оператор инициализировать себя и подготовить все необходимые структуры данных. Физический оператор может получать много вызовов Init(), хотя обычно получает лишь один. GetNext() Метод GetNext() заставляет физический оператор получить первую или последующую строку данных. Физический оператор может получить много вызовов GetNext() или не получить ни одного. Метод GetNext() возвращает одну строку данных, а число его вызовов отображается значением ActualRows в выводе инструкции Showplan. Close() При вызове метода Close() физический оператор выполняет некоторые действия по очистке и закрывается. Физический оператор получает только один вызов Close().

12 Взаимодействие между операторами Operator 1 Operator 2 Operator 3

13 Взаимодействие между операторами 1. Request Row Operator 1 Operator 2 Operator 3

14 Взаимодействие между операторами 1. Request Row 2. Request Row Operator 1 Operator 2 Operator 3

15 Взаимодействие между операторами 1. Request Row 2. Request Row Operator 1 Operator 2 Operator 3 3. Send Row

16 Взаимодействие между операторами 1. Request Row 2. Request Row Operator 1 Operator 2 Operator 3 4. Send Row 3. Send Row

17 Взаимодействие между операторами 1. Request Row 2. Request Row Operator 1 Operator 2 Operator 3 4. Send Row 3. Send Row

18 DEMO Оператор TOP Или почему лучше оператор называть итератором

19 Таблиц не существует!

20 HoBT Page 1 Page 2 Page 3 Page 4 Row 1 Row 3 Row 5 Row 7 Row 2 Row 4 Row 6 Row 8

21 HoBT Page Page Page Page Page Page Page

22 DEMO Операторы доступа к данным Scan, Seek, Lookup

23 У кого в базе данных есть только одна таблица?

24 Nested Loops, Hash Join и Merge Join

25 Операторы соединения Nested Loops inner join, left outer join, left semi join, left anti semi join Merge Join inner join, left outer join, left semi join, left anti semi join, right outer join, right semi join, right anti semi join, union Hash Join все типы логических операций

26 DEMO Операторы соединения, сортировки и первый оператор Nested Loops, Merge Join, Hash Join, Sort, First Operator

27 Предупреждения

28 DEMO Ошибки и предупреждения в планах запросов

29 Я знаю, что ничего не знаю. Сократ

30 DEMO Небольшой пример непонятного

31 Диагностика планов запросов -- TOP 10 запросов, которые потребляю больше всего CPU и их планы select top(10) substring(t.text, qs.statement_start_offset / 2, case when qs.statement_end_offset = -1 then len(t.text) else (qs.statement_end_offset - qs.statement_start_offset) / 2 end), qs.execution_count, cast(qs.total_worker_time / as decimal(18, 2)) as total_worker_time_ms, cast(qs.total_worker_time * 1. / qs.execution_count / as decimal(18, 2)) as avg_worker_time_ms, cast(p.query_plan as xml) as query_plan from sys.dm_exec_query_stats as qs cross apply sys.dm_exec_sql_text(qs.sql_handle) as t cross apply sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) as p order by qs.total_worker_time desc; go

32 Техника чтения больших планов запросов Пробовать разбивать на логические блоки и анализировать постепенно. В SSMS при графическом отображении плана в правом нижнем углу появляется кнопка для более удобной навигации по плану запроса. Можно использовать XQuery\XPath.

33 DEMO Большой план запроса

35 DEMO SQL Sentry Plan Explorer

36 Подведем итоги Первый оператор Optimization level Compile time Size in cache Parameters, Compile Values Reason for Early Termination Стоимость итераторов Смотрите в первую очередь на операторы с самой высокой стоимостью. Не забывайте, что это всего лишь предполагаемые значения (даже в актуальных планах выполнения).

37 Подведем итоги Bookmark\Key Lookup Если их мало, то скорее всего проблемы нет. Если их много, создание покрывающего индекса поможет от них избавиться. Предупреждения Необходимо проверить, почему оно возникает и при необходимости принять меры.

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

39 Подведем итоги Сканирования Сканирования не означают, что есть проблема. Вполне возможно, что не хватает индекса на таблице, чтобы сделать более эффективный поиск. С другой стороны, при необходимости выбора всех или большой части таблицы сканирование будет более эффективно. Поиск не значит, что все хорошо. Большое число поисков по некластеризованным индексам может быть проблемой. Все, что вы не знаете в плане потенциально может быть проблемой

40 Вопросы

41 Контакты Олонцев Сергей Лаборатория Касперского

42 2013 Microsoft Corporation. All rights reserved. Microsoft, Windows, Windows Vista and other product names are or may be registered trademarks and/or trademarks in the U.S. and/or other countries. The information herein is for informational purposes only and represents the current view of Microsoft Corporation as of the date of this presentation. Because Microsoft must respond to changing market conditions, it should not be interpreted to be a commitment on the part of Microsoft, and Microsoft cannot guarantee the accuracy of any information provided after the date of this presentation. MICROSOFT MAKES NO WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, AS TO THE INFORMATION IN THIS PRESENTATION.

6 ответов

Существует несколько способов получения плана выполнения, который использовать будет зависеть от ваших обстоятельств. Обычно вы можете использовать SQL Server Management Studio для получения плана, однако, если по какой-то причине вы не можете запустить свой запрос в SQL Server Management Studio, вам может оказаться полезным получить план через SQL Server Profiler или путем проверки кеш плана.

Способ 1 - Использование SQL Server Management Studio

В SQL Server есть несколько опрятных функций, которые упрощают сбор плана выполнения, просто убедитесь, что пункт меню "Включить фактический план выполнения" (найденный в меню "Запрос") отмечен галочкой и запустит ваш как обычно.

Если вы пытаетесь получить план выполнения для операторов в хранимой процедуре, вы должны выполнить хранимую процедуру, например:

Exec p_Example 42

Когда ваш запрос завершен, вы увидите дополнительную вкладку "План выполнения", которая появится в панели результатов. Если вы запустили много утверждений, вы можете увидеть много планов, отображаемых на этой вкладке.

Здесь вы можете проверить план выполнения в SQL Server Management Studio или щелкнуть правой кнопкой мыши по плану и выбрать "Сохранить план выполнения как...", чтобы сохранить план в файл в формате XML.

Способ 2 - Использование опций SHOWPLAN

Этот метод очень похож на метод 1 (на самом деле это то, что делает SQL Server Management Studio внутренне), однако я включил его для полноты или если у вас нет доступной SQL Server Management Studio.

Перед выполнением запроса запустите один следующих операторов. Оператор должен быть единственным оператором в пакете, т.е. Вы не можете одновременно выполнять другой оператор:

SET SHOWPLAN_TEXT ON SET SHOWPLAN_ALL ON SET SHOWPLAN_XML ON SET STATISTICS PROFILE ON SET STATISTICS XML ON -- The is the recommended option to use

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

Как только вы закончите, вы можете отключить этот параметр со следующим утверждением:

SET <

Сравнение форматов плана выполнения

Если у вас есть сильное предпочтение, я рекомендую использовать параметр STATISTICS XML . Эта опция эквивалентна опции "Включить фактический план выполнения" в SQL Server Management Studio и предоставляет самую большую информацию в наиболее удобном формате.

  • SHOWPLAN_TEXT - отображает базовый оценочный план выполнения, основанный на тексте, без выполнения запроса
  • SHOWPLAN_ALL - отображает оценочный план выполнения на основе текста с оценкой стоимости без выполнения запроса
  • SHOWPLAN_XML - отображает оценочный план выполнения на основе XML с оценкой стоимости без выполнения запроса. Это эквивалентно опции "Отобразить примерный план выполнения..." в SQL Server Management Studio.
  • STATISTICS PROFILE - Выполняет запрос и отображает фактический план выполнения на основе текста.
  • STATISTICS XML - Выполняет запрос и отображает фактический план выполнения на основе XML. Это эквивалентно опции "Включить фактический план выполнения" в SQL Server Management Studio.

Способ 3 - Использование профилировщика SQL Server

Если вы не можете запустить запрос напрямую (или ваш запрос не запускается медленно при его непосредственном запуске - помните, что мы хотим, чтобы план запроса выполнялся плохо), тогда вы можете зафиксировать план с помощью SQL Server Profiler след. Идея состоит в том, чтобы запустить ваш запрос, пока трассировка, которая захватывает один из событий "Showplan", запущена.

Обратите внимание, что в зависимости от нагрузки вы можете использовать этот метод в рабочей среде, однако вы должны, очевидно, соблюдать осторожность. Механизмы профилирования SQL Server предназначены для минимизации влияния на базу данных, но это не означает, что влияние производительности не будет. У вас может также возникнуть проблема с фильтрацией и определением правильного плана в вашей трассе, если ваша база данных находится под большим использованием. Вы, очевидно, должны проверить свой администратор базы данных, чтобы убедиться, что они довольны тем, что вы делаете это в своей драгоценной базе данных!

  • Откройте SQL Server Profiler и создайте новую трассировку, соединяющую нужную базу данных, с которой вы хотите записать трассировку.
  • На вкладке "Выбор событий" установите флажок "Показать все события", проверьте строку "Производительность" → "Showplan XML" и запустите трассировку.
  • Пока трассировка работает, сделайте все, что вам нужно, чтобы запустить медленный запрос.
  • Дождитесь завершения запроса и остановки трассировки.
  • Чтобы сохранить трассировку, щелкните правой кнопкой мыши по плану xml в профиле SQL Server и выберите "Извлечь данные о событиях...", чтобы сохранить план в файл в формате XML.

Полученный вами план эквивалентен опции "Включить фактический план выполнения" в SQL Server Management Studio.

Метод 4 - Проверка кеша запросов

Если вы не можете запустить свой запрос напрямую, и вы также не можете захватить трассировку профилировщика, вы все равно сможете получить оценочный план, проверив кеш-план SQL-запроса.

Мы проверяем кеш плана, запрашивая SQL Server DMVs . Ниже приведен базовый запрос, в котором будут перечислены все кэшированные планы запросов (как xml) вместе с их текстом SQL. В большинстве баз данных вам также необходимо будет добавить дополнительные условия фильтрации, чтобы отфильтровать результаты вплоть до интересующих вас планов.

SELECT UseCounts, Cacheobjtype, Objtype, TEXT, query_plan FROM sys.dm_exec_cached_plans CROSS APPLY sys.dm_exec_sql_text(plan_handle) CROSS APPLY sys.dm_exec_query_plan(plan_handle)

Выполните этот запрос и щелкните на плане XML, чтобы открыть план в новом окне - щелкните правой кнопкой мыши и выберите "Сохранить план выполнения как...", чтобы сохранить план в файл в формате XML.

Примечания:

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

Вы не можете зафиксировать план выполнения для зашифрованных хранимых процедур.

"фактические" и "оцененные" планы выполнения

Фактический план выполнения - это тот, где SQL Server фактически выполняет запрос, тогда как оценочный план выполнения SQL Server работает над тем, что он мог бы сделать, не выполняя запрос. Хотя логически эквивалентный, фактический план выполнения намного полезнее, поскольку он содержит дополнительные данные и статистику о том, что на самом деле произошло при выполнении запроса. Это важно при диагностике проблем, когда оценки SQL-серверов отключены (например, когда статистика устарела).

Как интерпретировать план выполнения запроса?

Это тема, достойная достаточно для бесплатного book .

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

DECLARE @TraceID INT EXEC StartCapture @@SPID, @TraceID OUTPUT EXEC sp_help "sys.objects" /*<-- Call your stored proc of interest here.*/ EXEC StopCapture @TraceID

Моим любимым инструментом для получения и глубокого анализа планов выполнения запросов является SQL Sentry Plan Explorer . Это гораздо удобнее, удобнее и полно для детального анализа и визуализации планов выполнения, чем SSMS.

Вот пример экрана для вас, чтобы вы поняли, какая функциональность предлагается инструментом:

Это только одно из представлений, доступных в инструменте. Обратите внимание на набор вкладок в нижней части окна приложения, что позволяет вам получать различные типы представления плана выполнения и полезную дополнительную информацию.

Кроме того, я не заметил никаких ограничений в его бесплатной версии, которая предотвращает ее использование на ежедневной основе или заставляет вас покупать версию Pro в конечном итоге. Итак, если вы предпочитаете придерживаться бесплатной версии, вам ничего не запрещается.

Помимо методов, описанных в предыдущих ответах, вы также можете использовать бесплатный просмотрщик планов выполнения и инструмент оптимизации запросов ApexSQL Plan (который Ive недавно столкнулся в).

Вы можете установить и интегрировать план ApexSQL в SQL Server Management Studio, поэтому планы выполнения можно напрямую просмотреть из SSMS.

Просмотр прогнозируемых планов выполнения в Плане ApexSQL

  • Нажмите кнопку Новый запрос в SSMS и вставьте текст запроса в текстовое окно запроса. Щелкните правой кнопкой мыши и выберите "Отображать примерный план выполнения" в контекстном меню.

  1. На диаграмме плана выполнения будет показана вкладка Планирование выполнения в разделе результатов. Затем щелкните правой кнопкой мыши план выполнения и в контекстном меню выберите вариант "Открыть в ApexSQL Plan".

  1. Предполагаемый план выполнения будет открыт в Плане ApexSQL и может быть проанализирован для оптимизации запросов.

Просмотр фактических планов выполнения в Плане ApexSQL

Чтобы просмотреть фактический план выполнения запроса, перейдите к второму этапу, упомянутому ранее, но теперь, как только появится оценочный план, нажмите кнопку "Фактическое" на главной панели ленты в Плане ApexSQL.

После нажатия кнопки "Фактическое" будет показан фактический план выполнения с подробным предварительным просмотром параметров затрат вместе с другими данными плана выполнения.

Более подробную информацию о просмотре планов выполнения можно найти, следуя этой ссылке .

Планы запросов можно получить из сеанса расширенных событий через событие query_post_execution_showplan . Вот пример сеанса XEvent:

/* Generated via "Query Detail Tracking" template. */ CREATE EVENT SESSION ON SERVER ADD EVENT sqlserver.query_post_execution_showplan(ACTION(package0.event_sequence,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack)), /* Remove any of the following events (or include additional events) as desired. */ ADD EVENT sqlserver.error_reported(ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.database_id,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack) WHERE (.(.,(4)) AND .(.,(0)))), ADD EVENT sqlserver.module_end(SET collect_statement=(1) ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.database_id,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack) WHERE (.(.,(4)) AND .(.,(0)))), ADD EVENT sqlserver.rpc_completed(ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.database_id,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack) WHERE (.(.,(4)) AND .(.,(0)))), ADD EVENT sqlserver.sp_statement_completed(SET collect_object_name=(1) ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.database_id,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack) WHERE (.(.,(4)) AND .(.,(0)))), ADD EVENT sqlserver.sql_batch_completed(ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.database_id,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack) WHERE (.(.,(4)) AND .(.,(0)))), ADD EVENT sqlserver.sql_statement_completed(ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.database_id,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack) WHERE (.(.,(4)) AND .(.,(0)))) ADD TARGET package0.ring_buffer WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=30 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF) GO

После создания сеанса (в SSMS) перейдите в Обозреватель объектов и перейдите в раздел Управление | Расширенные события | Сессии. Щелкните правой кнопкой мыши сеанс "GetExecutionPlan" и запустите его. Щелкните его правой кнопкой мыши и выберите "Watch Live Data".

Затем откройте новое окно запроса и запустите один или несколько запросов. Здесь один для AdventureWorks:

USE AdventureWorks; GO SELECT p.Name AS ProductName, NonDiscountSales = (OrderQty * UnitPrice), Discounts = ((OrderQty * UnitPrice) * UnitPriceDiscount) FROM Production.Product AS p INNER JOIN Sales.SalesOrderDetail AS sod ON p.ProductID = sod.ProductID ORDER BY ProductName DESC; GO

Через минуту или два вы увидите некоторые результаты на вкладке "GetExecutionPlan: Live Data". Выберите одно из событий query_post_execution_showplan в сетке, а затем щелкните вкладку "План запроса" под сеткой. Он должен выглядеть примерно так:

EDIT : код XEvent и снимок экрана были созданы из SQL/SSMS 2012 w/SP2. Если вы используете SQL 2008/R2, вы можете настроить script, чтобы запустить его. Но эта версия не имеет графического интерфейса, поэтому вам нужно будет извлечь XML файл showplan, сохранить его как файл *.sqlplan и открыть его в SSMS. Это громоздко. XEvents не существовало в SQL 2005 или ранее. Итак, если вы не на SQL 2012 или позже, я бы настоятельно предложил один из других ответов, размещенных здесь.

поделиться