Хранимые процедуры и встроенный SQL

27

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

Я хотел бы знать технические причины этого (таким образом, чтобы я мог объяснить это кому-то позже).

Может ли кто-нибудь помочь мне сформулировать хороший ответ?

webdad3
источник
1
Должным образом параметризованные запросы являются столь же хорошо , как хранимой процедура, с точки зрения производительности. Оба компилируются перед первым использованием, оба будут повторно использовать кэшированный план выполнения при последующих выполнениях, оба плана сохраняются в одном и том же кэше планов, и оба будут обрабатываться с одним и тем же именем. В настоящее время в SQL Server нет никакого выигрыша в производительности для хранимой процедуры.
marc_s
@marc_s это правда, если запросы идентичны. Однако, как я указал в своем ответе, есть некоторые характеристики специальных запросов, которые могут быть проблемами с производительностью даже для запросов, которые кажутся идентичными.
Аарон Бертран

Ответы:

42

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

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

  • Если у меня есть несколько приложений, которые повторно используют одни и те же запросы, хранимая процедура инкапсулирует эту логику, а не засоряет один и тот же специальный запрос несколько раз в разных кодовых базах. Приложения, которые повторно используют одни и те же запросы, также могут подвергаться плану раздувания кэша, если они не копируются дословно. Даже различия в регистре и пробелах могут привести к сохранению нескольких версий одного и того же плана (расточительно).
  • Я могу проверять и устранять неполадки в том, что делает запрос, не имея доступа к исходному коду приложения или выполнения дорогих трассировок, чтобы точно узнать, что делает приложение.
  • Я также могу контролировать (и знать заранее), какие запросы может запускать приложение, к каким таблицам оно может обращаться и в каком контексте и т. Д. Если разработчики пишут запросы ad-hoc в своих приложениях, им либо придется приходите ко мне за рубашку каждый раз, когда им нужен доступ к столу, о котором я не знал или не мог предсказать, или, если я менее ответственен / полон энтузиазма и / или обеспокоен безопасностью, я просто буду продвигать это Пользователь в dbo, чтобы они перестали меня глючить. Как правило, это делается, когда разработчики превосходят по численности администраторов баз данных или администраторы администраций упрямы. Это последнее, что нам плохо, и мы должны быть лучше в предоставлении запросов, которые вам нужны.
  • На заметку о том, что набор хранимых процедур - это очень простой способ точно определить, какие запросы могут выполняться в моей системе. Как только приложению разрешено обходить процедуры и отправлять свои собственные специальные запросы, для их обнаружения необходимо выполнить трассировку, охватывающую весь бизнес-цикл, или проанализировать весь код приложения (опять же, что У меня может не быть доступа к), чтобы найти что-то похожее на запрос. Возможность просмотра списка хранимых процедур (и поиска единого источника sys.sql_modulesдля ссылок на конкретные объекты) значительно облегчает жизнь всем.
  • Я могу пойти на гораздо большие длины, чтобы предотвратить внедрение SQL; даже если я беру ввод и выполняю его с помощью динамического SQL, я могу контролировать многое из того, что разрешено. Я не имею никакого контроля над тем, что делает разработчик при построении встроенных операторов SQL.
  • Я могу оптимизировать запрос (или запросы), не имея доступа к исходному коду приложения, возможности вносить изменения, знаниям языка приложения, чтобы сделать это эффективно, полномочий (не говоря уже о хлопотах) для повторной компиляции и повторного развертывания. приложение и т. д. Это особенно проблематично, если приложение распространяется.
  • Могу ли я принудительно установить определенные параметры набора в хранимой процедуре, чтобы избежать отдельных запросов из-за медленных в приложении, быстрых в SSMS? проблемы. Это означает, что для двух разных приложений, вызывающих специальный запрос, одно может иметь SET ANSI_WARNINGS ON, а другое может иметь SET ANSI_WARNINGS OFF, и у каждого из них будет своя копия плана. План, который они получают, зависит от используемых параметров, статистики и т. Д. При первом вызове запроса в каждом случае, что может привести к различным планам и, следовательно, к разной производительности.
  • Я могу контролировать такие вещи, как типы данных и то, как используются параметры, в отличие от определенных ORM - некоторые более ранние версии EF могли бы параметризировать запрос на основе длины параметра, поэтому, если бы у меня был параметр N'Smith 'и другой N' Джонсон, я бы получил две разные версии плана. Они это исправили. Они исправили это, но что еще сломано?
  • Я могу делать вещи, которые ORM и другие «полезные» фреймворки и библиотеки пока не могут поддерживать.

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

Аарон Бертран
источник
2
Еще одна причина для хранимых процедур? Для длинных и сложных запросов вы должны каждый раз отправлять запрос на сервер, если только это не sproc, тогда вы просто нажимаете "exec sprocname" и несколько параметров. Это может иметь значение в медленной (или занятой) сети.
Дэвид Кроуэлл
0

Хотя я уважаю заявителя, я смиренно не согласен с предоставленным ответом, а не по «религиозным причинам». Другими словами, я считаю, что Microsoft не предоставила средства, которое уменьшило бы потребность в руководстве по использованию хранимых процедур.

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

Я думаю, что простой ответ на вопрос о том, почему использовать SPROC, заключается в том, что создатель предположил: SPROC анализируются, оптимизируются и компилируются. Таким образом, их планы запросов / выполнения кэшируются, потому что вы сохранили статическое представление запроса, и вы, как правило, будете изменять его только по параметрам, что неверно в случае скопированных / вставленных операторов SQL, которые, вероятно, изменяются от страницы к странице и компоненту / уровню, и часто меняются в зависимости от того, какие разные таблицы, даже имена баз данных, могут быть указаны при вызове. Разрешение для этого типа динамического ad hocОтправка SQL значительно снижает вероятность того, что DB Engine повторно использует план запросов для ваших специальных операторов в соответствии с некоторыми очень строгими правилами. Здесь я делаю различие между динамическими специальными запросами (в духе поставленного вопроса) и использованием эффективной системы SPROC sp_executesql.

Более конкретно, существуют следующие компоненты:

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

Когда с веб-страницы выдается оператор SQL, называемый «оператор ad hoc», механизм ищет существующий план выполнения для обработки запроса. Поскольку это текст, отправленный пользователем, он будет принят, проанализирован, скомпилирован и выполнен, если он действителен. В это время он получит нулевую стоимость запроса. Стоимость запроса используется, когда механизм БД использует свой алгоритм, чтобы определить, какие планы выполнения следует исключить из кэша.

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

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

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

Поэтому вполне вероятно, что такой план будет выселен первым, когда возникнет давление памяти.

Таким образом, если у вас есть серверная сборка с большим объемом памяти «вне ваших потребностей», вы можете столкнуться с этой проблемой не так часто, как с загруженным сервером, который имеет только «достаточную» память для обработки своей рабочей нагрузки. (Извините, объем памяти сервера и его использование несколько субъективны / относительны, хотя алгоритм не так.)

Теперь, если я на самом деле ошибаюсь по поводу одного или нескольких пунктов, я, безусловно, открыт для исправления.

Наконец, автор написал:

«Теперь у нас есть оптимизация на уровне операторов, поэтому правильно параметризованный запрос, поступающий из приложения, может использовать тот же план выполнения, что и этот запрос, встроенный в хранимую процедуру».

Я полагаю, что автор ссылается на опцию «оптимизировать для специальных рабочих нагрузок».

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

Однако вы должны включить эту опцию, так как по умолчанию она отключена.

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

Для получения дополнительной информации, пожалуйста, смотрите:

https://technet.microsoft.com/en-us/library/ms181055(v=sql.105).aspx
http://sqlmag.com/database-performance-tuning/don-t-fear-dynamic-sql

Бест,
Генри

Генри
источник
4
Я внимательно прочитал несколько параграфов вашего поста два-три раза, и до сих пор не знаю, какие мысли вы пытаетесь донести. В некоторых случаях вы видите, что в конце предложений вы говорите прямо противоположное тому, что предложение начинало пытаться сказать. Вы действительно должны тщательно вычитать и редактировать это представление.
Питер Гиркенс
Спасибо за отзывы, Питер. Если это так, возможно, я должен сократить свои предложения, чтобы прояснить ситуацию. Можете ли вы привести пример, где я, кажется, излагаю противоположность первоначальной мысли? Очень признателен.
Генри
Нет, я не имел в виду «Оптимизировать для специальных рабочих нагрузок», я имел в виду оптимизацию на уровне выписки. Например, в SQL Server 2000 хранимая процедура должна быть скомпилирована как единое целое, поэтому у приложения не было возможности повторно использовать план для своего специального специального запроса, который случайно соответствовал чему-то в процедуре. Я скажу, что я согласен с Питером - многим вещам, которые вы говорите, трудно следовать. Такие вещи, как «Я полагаю, что Microsoft не предоставила средства, которое уменьшило бы потребность в руководстве по использованию хранимых процедур». являются излишне сложными и требуют слишком много анализа, чтобы понять. ИМХО.
Аарон Бертран
1
кажется, что ваше отвращение к "ad hoc" sql основано на идее, что sql каким-то образом меняется между выполнениями ... это совершенно не соответствует действительности, когда речь идет о параметризации.
b_levitt
0

TLDR: между ними нет заметной разницы в производительности, пока ваш встроенный sql параметризован.

Вот почему я постепенно отказался от хранимых процедур:

  • Мы запускаем «бета» среду приложения - среду, параллельную производственной, которая совместно использует производственную базу данных. Поскольку код БД находится на уровне приложения, а изменения структуры БД встречаются редко, мы можем позволить людям подтверждать новые функциональные возможности, выходящие за рамки контроля качества, и выполнять развертывания за пределами рабочего окна развертывания, но при этом предоставлять производственную функциональность и некритические исправления. Это было бы невозможно, если бы половина кода приложения была в БД.

  • Мы практикуем devops на уровне базы данных (осьминог + dacpacs). Тем не менее, хотя бизнес-уровень и его уровень в основном можно очистить и заменить, а восстановление - наоборот, это не относится к постепенным и потенциально разрушительным изменениям, которые должны быть внесены в базы данных. Следовательно, мы предпочитаем, чтобы развертывание наших БД было более легким и менее частым.

  • Чтобы избежать точных копий одного и того же кода для необязательных параметров, мы часто будем использовать шаблон 'где @var равен нулю или @ var = table.field'. С сохраненным процессом вы, вероятно, получите тот же план выполнения, несмотря на довольно разные намерения, и, таким образом, либо столкнетесь с проблемами производительности, либо откажетесь от кэшированных планов с подсказками «перекомпиляции». Тем не менее, с помощью небольшого фрагмента кода, который добавляет комментарий «подписи» в конец sql, мы можем форсировать разные планы, в зависимости от того, какие переменные были нулевыми (не следует интерпретировать как разные планы для всех комбинаций переменных - только ноль против не ноль).

  • Я могу внести кардинальные изменения в результаты с незначительными изменениями на лету на SQL. Например, у меня может быть оператор, который завершается двумя CTE: «Raw» и «ReportReady». Нет ничего, что говорит, что оба CTE должны быть использованы. Мой SQL-оператор может быть:

    ...

    выберите * из {(формат)} "

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

  • когда у вас есть правило «только для procs», вы получаете массу избыточности в подавляющем большинстве ваших sql, которые заканчиваются CRUD - вы связываете все параметры, перечисляете все эти параметры в сигнатуре proc (и теперь вы находитесь в другом файле в другом проекте), вы сопоставляете эти простые параметры с их столбцами. Это создает довольно отдельный опыт разработки.

Существуют веские причины для использования процедур:

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

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

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

  • Повторное использование - я упомянул это выше как «плюс», но также хотел упомянуть здесь, что повторное использование должно в основном происходить на уровне бизнеса. Процедура вставки записи не должна рассматриваться как «повторно используемая», если бизнес-уровень также может проверять другие службы, не относящиеся к БД.

  • Переполнение плана кэширования - единственная причина, по которой это будет проблемой, - это если вы объединяете значения, а не параметризуетесь. Тот факт, что вы редко получаете более одного плана на процесс, часто причиняет вам боль, когда в запросе есть «или»

  • Размер оператора - дополнительные килобайты SQL-операторов над именем процесса обычно незначительны по отношению к возвращаемым данным. Если это нормально для сущностей, это нормально для меня.

  • Просмотр точного запроса - облегчить поиск запросов в коде так же просто, как добавить местоположение вызывающего абонента в качестве комментария к коду. Сделать код копируемым из кода c # в ssms так же просто, как творческая интерполяция и использование комментариев:

        //Usage /*{SSMSOnly_}*/Pure Sql To run in SSMS/*{_SSMSOnly}*/
        const string SSMSOnly_ = "*//*<SSMSOnly>/*";
        const string _SSMSOnly = "*/</SSMSOnly>";
        //Usage /*{NetOnly_}{InterpolationVariable}{_NetOnly}*/
        const string NetOnly_ = "*/";
        const string _NetOnly = "/*";
  • Sql Injection - Параметризация ваших запросов. Выполнено. Это на самом деле может быть отменено, если вместо этого используется динамический sql.

  • В обход развертывания - мы также практикуем devops на уровне базы данных, поэтому это не вариант для нас.

  • «Медленно в приложении, быстро в SSMS» - это проблема кэширования плана, которая затрагивает обе стороны. Заданные параметры просто приводят к компиляции нового плана, который, по-видимому, решает проблему с переменными THE ONE SET OFF. Это только объясняет, почему вы видите разные результаты - сами установленные параметры НЕ решают проблему анализа параметров.

  • Планы выполнения встроенного SQL не кэшируются - просто ложь. Параметризованный оператор, точно так же, как имя процесса быстро хэшируется, а затем этот план ищет план. Это на 100% то же самое.

  • Чтобы было ясно, я говорю о сыром встроенном sql не сгенерированном коде из ORM - мы используем только Dapper, который в лучшем случае является микро ORM.

https://weblogs.asp.net/fbouma/38178

/programming//a/15277/852208

b_levitt
источник