Чому ми завжди вважаємо за краще використовувати параметри в операторах SQL?


114

Я дуже новачок у роботі з базами даних. Тепер я можу написати SELECT, UPDATE, DELETEі INSERTкоманду. Але я бачив багато форумів, де ми вважаємо за краще писати:

SELECT empSalary from employee where salary = @salary

...замість:

SELECT empSalary from employee where salary = txtSalary.Text

Чому ми завжди вважаємо за краще використовувати параметри і як я їх би використовував?

Я хотів дізнатися про використання та переваги першого методу. Я навіть чув про ін'єкцію SQL, але не цілком її розумію. Я навіть не знаю, чи ін'єкція SQL пов'язана з моїм запитанням.


2
Ви маєте рацію, це пов'язано з ін'єкцією SQL. Як боротися з параметрами, як правило, несе відповідальність будь-яка мова / рамка вашої програми та може залежати від мови. Будь ласка, опублікуйте як RDBMS (корисно), так і ORM-рамку (необхідну).
Завод-Муза

1
Я використовую C # як мову програмування та Sql Server 2008 як базу даних. Я використовую Microsoft dotNet Framework 4.0. Мені дуже шкода, що я не впевнений у тому, що ви запитуєте (RDBMS чи ORM), можливо, ви можете мені дати мої версії RDBMS та ORM :-). Велике спасибі
Сенді

1
RDBMS - це ваша база даних, у вашому випадку SQL Server 2008. Ваш ORM - це метод, за допомогою якого ви отримуєте доступ до своєї бази даних, в даному випадку ADO.NET. Інші включають LINQ до SQL та Entity Framework . Насправді, коли ви вивчите основи ADO.NET і SQL, я рекомендую використовувати такі ORM, як LINQ або EF, оскільки вони вирішують багато питань, з якими ви зіткнетеся вручну, записуючи SQL.
Чад Леві

Відповіді:


129

Використання параметрів допомагає запобігти атакам SQL Injection, коли база даних використовується разом із програмним інтерфейсом, таким як настільна програма або веб-сайт.

У вашому прикладі користувач може безпосередньо запускати SQL-код у вашій базі даних, створюючи оператори в txtSalary.

Наприклад, якби вони писали 0 OR 1=1, виконаний SQL був би

 SELECT empSalary from employee where salary = 0 or 1=1

в результаті чого всі емісійні зарплати будуть повернені

Крім того, користувач може виконувати набагато гірші команди проти вашої бази даних, включаючи її видалення, якщо вони написали 0; Drop Table employee:

SELECT empSalary from employee where salary = 0; Drop Table employee

Потім таблицю employeeбуде видалено.


У вашому випадку, схоже, ви використовуєте .NET. Використовувати параметри так само просто:

C #

string sql = "SELECT empSalary from employee where salary = @salary";

using (SqlConnection connection = new SqlConnection(/* connection info */))
using (SqlCommand command = new SqlCommand(sql, connection))
{
    var salaryParam = new SqlParameter("salary", SqlDbType.Money);
    salaryParam.Value = txtMoney.Text;

    command.Parameters.Add(salaryParam);
    var results = command.ExecuteReader();
}

VB.NET

Dim sql As String = "SELECT empSalary from employee where salary = @salary"
Using connection As New SqlConnection("connectionString")
    Using command As New SqlCommand(sql, connection)
        Dim salaryParam = New SqlParameter("salary", SqlDbType.Money)
        salaryParam.Value = txtMoney.Text

        command.Parameters.Add(salaryParam)

        Dim results = command.ExecuteReader()
    End Using
End Using

Редагувати 2016-4-25:

Відповідно до коментаря Джорджа Стокера, я змінив зразок коду, щоб не використовувати AddWithValue. Крім того , він , як правило , рекомендується обернути IDisposableз в usingзвітності.


чудове рішення. Але чи можете ви пояснити трохи більше, чому і як використовувати параметри безпечно. Я маю на увазі, що все ще виглядає, що команда sql буде такою ж
Сенді,

чи можемо ми додати кілька параметрів до команди sql. Як нам може знадобитися команда INSERT?
Сенді

2
SQL Server розглядає текст всередині параметрів лише як вхідний і ніколи його не виконує.
Чад Леві

3
Так, ви можете додати кілька параметрів: Insert Into table (Col1, Col2) Values (@Col1, @Col2). У свій код слід додати кілька AddWithValues.
Чад Леві

1
Будь ласка, не використовуйте AddWithValue! Це може спричинити неявні проблеми перетворення. Завжди встановлюйте розмір явно і додайте значення параметра за допомогою parameter.Value = someValue.
Джордж Стокер

75

Ви маєте рацію, це пов’язано з інжекцією SQL , що є вразливістю, яка дозволяє користувачеві-малікею виконувати довільні заяви проти вашої бази даних. Цей улюблений давнім часом комікс XKCD ілюструє концепцію:

Її дочка названа Довідка. Я потрапив у пастку на фабриці посвідчення водія.


У вашому прикладі, якщо ви просто використовуєте:

var query = "SELECT empSalary from employee where salary = " + txtSalary.Text;
// and proceed to execute this query

Ви відкриті для ін'єкцій SQL. Наприклад, скажіть, що хтось входить у txtSallar:

1; UPDATE employee SET salary = 9999999 WHERE empID = 10; --
1; DROP TABLE employee; --
// etc.

Коли ви виконуєте цей запит, він виконує a SELECTі UPDATEабо DROP, або все, що вони хотіли. --В кінці просто закоментуйте решту вашого запиту, який буде корисний в атаці , якщо ви конкатенації що - небудь після txtSalary.Text.


Правильний спосіб - використовувати параметризовані запити, наприклад (C #):

SqlCommand query =  new SqlCommand("SELECT empSalary FROM employee 
                                    WHERE salary = @sal;");
query.Parameters.AddWithValue("@sal", txtSalary.Text);

З цим ви можете безпечно виконати запит.

Для отримання довідки про те, як уникнути ін'єкції SQL на кількох інших мовах, перегляньте bobby-tables.com , веб-сайт, який підтримує користувач SO .


1
чудове рішення. Але чи можете ви пояснити трохи більше, чому і як використовувати параметри безпечно. Я маю на увазі, як і раніше виглядає, що команда sql буде однаковою.
Сенді

1
@ user815600: поширене неправильне уявлення - ви все ще вважаєте, що запит з параметрами прийме значення і замінить параметри фактичними значеннями - правда? Ніякого цього не відбувається! - натомість оператор SQL з параметрами буде переданий на SQL Server разом зі списком параметрів та їх значеннями - оператор SQL не буде таким самим
marc_s

1
це означає, що введення sql контролюється внутрішнім механізмом або безпекою сервера. Дякую.
Сенді

4
Наскільки мені подобаються мультфільми, якщо ви використовуєте свій код з достатнім привілеєм для скидання таблиць, у вас, ймовірно, є більш широкі проблеми.
philw

9

На додаток до інших відповідей потрібно додати, що параметри не тільки допомагають запобігти введення sql, але можуть підвищити ефективність запитів . Кешування сервера Sql параметризує плани запитів і повторно використовувати їх при повторному виконанні запитів. Якщо ви не параметризували свій запит, то сервер sql складе новий план для кожного виконання запиту (з деяким виключенням), якщо текст запиту відрізнятиметься.

Детальніше про кешування плану запитів


1
Це доречніше, ніж можна було б подумати. Навіть "невеликий" запит може бути виконаний тисячі чи мільйони разів, ефективно промиваючи весь кеш запитів.
Джеймс

5

Через два роки після першого мого поїздки я відновлюю ...

Чому ми віддаємо перевагу параметрам? Введення SQL, очевидно, є великою причиною, але чи може ми бути, що ми таємно прагнемо повернутися до SQL як мови . SQL у рядкових літералах - це вже дивна культурна практика, але принаймні ви можете скопіювати та вставити свій запит у студію управління. Динамічно побудований SQL з умовами мови хосту та структурами управління, коли SQL має умовні умови та структури управління, є лише рівнем 0 варварства. Вам потрібно запустити додаток у налагодженні або зі слідом, щоб побачити, який SQL він генерує.

Не зупиняйтеся лише на параметрах. Пройдіть увесь шлях і використовуйте QueryFirst (відмова від відповідальності: яку я написав). Ваш SQL живе у .sql-файлі. Ви редагуєте його у казковому вікні редактора TSQL з валідацією синтаксису та Intellisense для ваших таблиць та стовпців. Ви можете призначити тестові дані в розділі спеціальних коментарів і натиснути «грати», щоб запустити запит прямо там у вікні. Створити параметр так само просто, як і розмістити "@myParam" у своєму SQL. Потім, кожного разу, коли ви зберігаєте, QueryFirst генерує обгортку C # для вашого запиту. Ваші параметри спливають, сильно набрані як аргументи методам Execute (). Результати повертаються в IEnumerable або Список сильно набраних POCO, типи, згенеровані за допомогою фактичної схеми, повернуті вашим запитом. Якщо ваш запит не запускається, програма не збирається. Якщо ваша схема db змінюється і ваш запит запускається, але деякі стовпці зникають, помилка компіляції вказує на рядок у вашому кодіщо намагається отримати доступ до відсутніх даних. І є численні інші переваги. Чому ви хочете отримати доступ до даних будь-яким іншим способом?


4

У Sql, коли будь-яке слово містить @ знак, це означає, що воно є змінним, і ми використовуємо цю змінну, щоб встановити в ній значення і використовувати його для області номерів того ж скрипту sql, оскільки воно обмежене лише для одного сценарію, тоді як ви можете оголосити безліч змінних одного типу та імені на багатьох сценаріях. Ми використовуємо цю змінну в наборі збережених процедур, оскільки збережена процедура є попередньо складеними запитами, і ми можемо передавати значення в цій змінній зі скрипту, робочого столу та веб-сайтів для отримання додаткової інформації, прочитаної Декларуйте локальну змінну , процедуру збереження Sql та ін'єкції sql .

Читайте також Захист від введення sql, це допоможе вам захистити вашу базу даних.

Сподіваюся, це допоможе вам зрозуміти також будь-яке питання, коментуючи мене.


3

Інші відповіді висвітлюють, чому параметри важливі, але є мінус! У .net існує кілька методів створення параметрів (Add, AddWithValue), але всі вони вимагають, щоб ви не турбувалися про ім'я параметра, і всі вони зменшують читабельність SQL у коді. Праворуч, коли ви намагаєтеся медитувати над SQL, вам потрібно полювати вгорі або внизу, щоб побачити, яке значення було використано в параметрі.

Я покірно стверджую, що мій маленький клас SqlBuilder - це найелегантніший спосіб написання параметрів, що задаються . Ваш код буде виглядати приблизно так ...

C #

var bldr = new SqlBuilder( myCommand );
bldr.Append("SELECT * FROM CUSTOMERS WHERE ID = ").Value(myId);
//or
bldr.Append("SELECT * FROM CUSTOMERS WHERE NAME LIKE ").FuzzyValue(myName);
myCommand.CommandText = bldr.ToString();

Ваш код буде коротшим і значно читабельнішим. Вам навіть не потрібні додаткові рядки, і, читаючи назад, вам не потрібно обшукувати значення параметрів. Вам потрібний клас ...

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;

public class SqlBuilder
{
private StringBuilder _rq;
private SqlCommand _cmd;
private int _seq;
public SqlBuilder(SqlCommand cmd)
{
    _rq = new StringBuilder();
    _cmd = cmd;
    _seq = 0;
}
public SqlBuilder Append(String str)
{
    _rq.Append(str);
    return this;
}
public SqlBuilder Value(Object value)
{
    string paramName = "@SqlBuilderParam" + _seq++;
    _rq.Append(paramName);
    _cmd.Parameters.AddWithValue(paramName, value);
    return this;
}
public SqlBuilder FuzzyValue(Object value)
{
    string paramName = "@SqlBuilderParam" + _seq++;
    _rq.Append("'%' + " + paramName + " + '%'");
    _cmd.Parameters.AddWithValue(paramName, value);
    return this;
}
public override string ToString()
{
    return _rq.ToString();
}
}

Іменування параметрів, безумовно, допомагає при профілюванні запитів, на яких запущений сервер.
Дейв Р.

Мій начальник сказав те саме. Якщо значущі імена параметрів для вас важливі, додайте аргумент paramName до методу value. Я підозрюю, що ти зайво ускладнюєш справи.
bbsimonbb

Погана ідея. Як було сказано раніше, це AddWithValueможе спричинити неявні проблеми перетворення.
Адам Калвет Бол

@Адам ви праві, але це не перешкоджає використанню AddWithValue () дуже широко використовуватись, і я не думаю, що це визнає недійсною ідею. Але тим часом я придумав набагато кращий спосіб написання параметризованих запитів, і це не використовує AddWithValue () :-)
bbsimonbb

Правильно! Обіцяйте, що я скоро на це подивлюсь!
Адам Калвет Бол

3

Стара публікація, але хотіла переконатись, що новачки знають про збережені процедури .

Моє значення 10 ¢ тут полягає в тому, що якщо ви зможете написати свій SQL-оператор як збережену процедуру , це, на мій погляд, є оптимальним підходом. Я ЗАВЖДИ використовувати збережені пуття і ніколи петлі через запис в моєму основному коді. Для прикладу: SQL Table > SQL Stored Procedures > IIS/Dot.NET > Class.

Використовуючи збережені процедури, ви можете обмежити користувача лише ВИКОНАВЧИМ дозволом, зменшуючи ризики безпеки .

Ваша збережена процедура суттєво парамеризована, і ви можете вказати параметри вводу та виводу.

Збережена процедура (якщо вона повертає дані через SELECTоператор) може бути доступна і прочитана точно так само, як і звичайна SELECTзаява у вашому коді.

Він також працює швидше під час компіляції на SQL Server.

Я також згадав, що ви можете робити кілька кроків, наприклад updateтаблицю, перевіряти значення на іншому сервері БД, а потім, нарешті, закінчити, повернути дані клієнтові, все на одному сервері, і ніякої взаємодії з клієнтом. Тож це набагато швидше, ніж кодування цієї логіки у вашому коді.

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