Який сенс і користь від використання SqlCommand.Prepare ()?


14

Я натрапив на код розробника, де SqlCommand.Prepare () (див. MSDN) метод широко використовується перед виконанням SQL запитів. І мені цікаво, в чому користь цього?

Зразок:

command.Prepare();
command.ExecuteNonQuery();
//...
command.Parameters[0].Value = 20;
command.ExecuteNonQuery();

Я трохи пограв і простежив. Виконання команди після виклику Prepare()методу змушує Sql Server виконати наступне твердження:

declare @p1 int
set @p1=1
exec sp_prepexec @p1 output,N'@id int,@desc text',N'INSERT INTO dbo.testtable (id) VALUES (@id)',@id=20'
select @p1

Після цього, коли Параметр отримує значення і SqlCommand.ExecuteNonQuery()викликається, на Sql-сервері виконується таке:

exec sp_execute 1,@id=20

Для мене це виглядає так, що заява стає якось складеною Prepare() виконується. Цікаво, яка користь від цього? Чи означає це, що він вкладається в кеш плану і може бути повторно використаний, як тільки виконується остаточний запит із потрібними значеннями параметрів?

Я зрозумів (і задокументував це в іншому запитанні ), що SqlCommands, які виконуються за допомогою SqlParameters, завжди загорнуті вsp_executesql викликах процедури. Це робить сервер Sql здатним зберігати та використовувати плани незалежно від значень параметрів.

З цього приводу мені цікаво, чи prepare()метод є марним чи застарілим чи я щось тут пропускаю?


Я завжди думав, що це пов'язане із запобіганням ін'єкції SQL, тому ви підготуєте запит, щоб прийняти певні змінні певних типів. Таким чином, якщо ви не переходите до змінних цих типів, запит не вдається
Марк Сінкінсон

Хоча, кажучи, це пояснення PHP, ймовірно, так само справедливе для .NET php.net/manual/en/pdo.prepared-statements.php
Марк Сінкінсон,

- Так, сер. Водієві, готуйтеся до виїзду. "Що ви готуєте ?! Ви завжди готуєтесь! Просто йдіть!"
Йон усіх торгів

Відповіді:


19

Підготовка партії SQL окремо від виконання підготовленої партії SQL - це безкорисна конструкція для SQL Server з урахуванням того, як кешуються плани виконання. Розмежування етапів підготовки (розбору, прив'язки будь-яких параметрів та компіляції) та виконання має сенс лише тоді, коли немає кешування. Метою є економія часу, витраченого на розбір та компіляцію шляхом повторного використання існуючого плану, і саме це робить кеш внутрішнього плану. Але не всі RDBMS виконують цей рівень кешування (зрозуміло, що існує в цих функціях), і тому від клієнтського коду залишається запитувати, щоб план був "кешований", і таким чином зберегти цей ідентифікатор кешу, а потім повторно використовувати це. Це додаткове навантаження на клієнтський код (і програміст пам'ятати, щоб це зробити і правильно), що зайве. Фактично, сторінка MSDN для ефективною **IDbCommand.Prepare () методу зазначено:

Сервер автоматично кешує плани повторного використання за необхідності; отже, немає необхідності викликати цей метод безпосередньо у вашій клієнтській програмі.

Слід зазначити, що, як показує моє тестування (яке відповідає тому, що ви показуєте у запитанні), виклик SqlCommand.Prepare () , не робить операцію "підготувати" - він викликає sp_prepexec, який готує та виконує SQL ; він не викликає sp_prepare, який розбирається і компілюється (і не приймає жодних значень параметрів, лише їх імена та типи даних). Отже, не може бути жодної переваги "попередньої компіляції" виклику, SqlCommand.Prepareоскільки вона негайно виконує (припустимо, що мета - відкласти виконання). ЗАРАЗ , є цікава потенційна незначна користь від дзвінка SqlCommand.Prepare(), навіть якщо він робить дзвінок sp_prepexecзамість sp_prepare. Будь ласка, дивіться примітку ( внизу ** ) для отримання детальної інформації.

Моє тестування показує, що навіть при SqlCommand.Prepare()виклику (і зауважте, що цей виклик не видає жодних команд на SQL Server), ExecuteNonQueryабо ExecuteReaderте, що випливає далі, повністю виконується, і якщо це ExecuteReader, він повертає рядки. Тим не менш, SQL Server Profiler дійсно показує, що перший SqlCommand.Execute______()дзвінок після SqlCommand.Prepare()виклику реєструється як подія "Підготувати SQL", а наступні .Execute___()виклики реєструються як події "Exec Prepared SQL".


Код тесту

Він завантажений у PasteBin за адресою: http://pastebin.com/Yc2Tfvup . Код створює додаток .NET / C # Console, який призначений для роботи під час запуску трафіку SQL Server Profiler (або сеансу розширених подій). Він робить паузи після кожного кроку, щоб було зрозуміло, які твердження мають особливий ефект.


ОНОВЛЕННЯ

Знайдено більше інформації та невелику потенційну причину, щоб уникнути дзвінків SqlCommand.Prepare(). Одне, що я помітив під час тестування, і що слід зазначити для тих, хто працює з тестовою програмою Console App: ніколи не робиться явного дзвінка sp_unprepare. Я кілька копав і знайшов наступне повідомлення на форумах MSDN:

SqlCommand - Готувати чи не готувати?

Прийнята відповідь містить купу інформації, але важливі моменти:

  • ** Те, що готується насправді, економить, перш за все, час, необхідний для передачі рядка запиту по дроту.
  • Підготовлений запит не гарантує збереження кешованого плану і не швидший, ніж спеціальний запит, коли він досягне сервера.
  • RPC (CommandType.StoredProcedure) нічого не отримує від підготовки.
  • На сервері підготовлена ​​ручка в основному є індексом на карті пам'яті, що містить TSQL для виконання.
  • Карта зберігається на основі з'єднання, використання перехресного з'єднання неможливе.
  • Скидання, що надсилається, коли клієнт повторно використовує з'єднання з пулу, очищає карту.

Я також виявив наступне повідомлення від команди SQLCAT, яке, схоже, пов'язане, але може бути просто проблемою, характерною для ODBC, тоді як SqlClient видаляє правильно після видалення SqlConnection. Важко сказати, оскільки публікація SQLCAT не згадує додаткових тестів, які допомогли б довести це як причину, наприклад, очищення пулу з'єднань тощо.

Слідкуйте за підготовленими операторами SQL


ВИСНОВОК

Враховуючи, що:

  • Єдиною реальною перевагою дзвінка SqlCommand.Prepare()є те, що вам не потрібно знову надсилати текст запиту по мережі,
  • виклик sp_prepareта sp_prepexecзбереження тексту запиту як частини пам'яті з'єднання (тобто пам'яті SQL Server)

Я б рекомендував не викликати дзвінки, SqlCommand.Prepare()оскільки єдиною потенційною перевагою є економія мережевих пакетів, тоді як зворотний бік займає більше пам’яті сервера. Незважаючи на те, що об'єм споживаної пам’яті, мабуть, дуже малий у більшості випадків, пропускна здатність мережі рідко виникає, оскільки більшість серверів БД безпосередньо підключені до серверів додатків понад 10 мегабітних мінімумів (більш імовірно, 100 мегабіт або гігабіт в ці дні) ( а деякі навіть на одній коробці ;-). Це і пам'ять майже завжди є дефіцитнішим ресурсом, ніж пропускна здатність мережі.

Я думаю, що для тих, хто пише програмне забезпечення, яке може підключатися до декількох баз даних, тоді зручність стандартного інтерфейсу може змінити цей аналіз витрат / вигод. Але навіть у такому випадку я маю вірити, що все-таки досить легко абстрагувати "стандартний" інтерфейс БД, який дозволяє різним постачальникам (і, отже, різниці між ними, наприклад, не потрібно викликати Prepare()), враховуючи, що це є основним об'єктом Орієнтоване програмування, яке ви, ймовірно, вже робите в коді програми ;-).


Дякую за цю обширну відповідь. Я перевірив ваші кроки та отримав дві аберації з ваших очікувань / результатів: на кроці 4 я отримав додатковий результат. На кроці 10 я отримав інший план_зв'язку, ніж на кроці 2.
Magier

1
@Magier Не впевнений у кроці 4 ... можливо, у вас були різні варіанти SET, або текст запиту між ними був різним? Навіть одна різниця символів (видима чи ні) буде різним планом. Скопіювати та вставити з цієї веб-сторінки може не допомогти, тому скопіюйте запит (все між окремими цитатами) з sp_prepareвиклику в sp_executesql, лише щоб бути впевненим. На кроці 10, так, я вчора зробив більше тестувань і побачив деякі випадки з різними ручками для sp_prepare, але все ще ніколи не для того ж sp_executesql. Я перевірю ще одну річ і оновлю свою відповідь.
Соломон Руцький

1
@Magier Насправді тест, який я проходив раніше, охоплював його, і я не думаю, що існує справжнє повторне використання плану, особливо з огляду на той пост MSDN форуму, який говорить про те, що він кешує лише SQL. Мені все ще цікаво, як він може повторно використовувати plan_handle, тоді як sp_executesqlне може чи ні. Але незалежно, час розбору все ще існує, sp_prepareякщо план було видалено з кешу, тож я видалю цю частину своєї відповіді (цікаво, але не має значення). Ця частина так чи інакше була цікавою стороною, оскільки вона не стосувалася вашого прямого запитання. Все інше все ще стосується.
Соломон Руцький

1
Це було таке пояснення, яке я шукав.
CSharpie

5

З цього приводу мені цікаво, чи підходить метод priprem () якийсь марний чи застарілий, чи я щось тут пропускаю?

Я б сказав, що метод «Підготувати» має обмежене значення SqlCommand світу, не те, що він абсолютно марний. Одна з переваг полягає в тому, що Prepare є частиною інтерфейсу IDbCommand, який реалізує SqlCommand. Це дозволяє запускати той самий код проти інших постачальників СУБД (що може вимагати Підготуватися) без умовної логіки, щоб визначити, чи слід викликати підготовку чи ні.

Крім цього, підготовка не має значення для постачальника .NET для SQL Server, окрім випадків використання, AFAIK.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.