Por que sempre preferimos usar parâmetros em instruções SQL?

114

Eu sou muito novo no trabalho com bancos de dados. Agora eu posso escrever SELECT, UPDATE, DELETE, e INSERTcomandos. Mas eu vi muitos fóruns onde preferimos escrever:

SELECT empSalary from employee where salary = @salary

...ao invés de:

SELECT empSalary from employee where salary = txtSalary.Text

Por que sempre preferimos usar parâmetros e como eu os usaria?

Eu queria saber o uso e os benefícios do primeiro método. Já ouvi falar de injeção de SQL, mas não entendo muito bem. Eu nem sei se injeção de SQL está relacionada à minha dúvida.

Sandy
fonte
2
Você está certo, isso está relacionado à injeção de SQL. Como lidar com os parâmetros é geralmente responsabilidade de qualquer linguagem / estrutura que seu programa esteja executando e pode ser dependente da linguagem. Por favor, poste seu RDBMS (útil) e estrutura ORM (necessário).
Clockwork-Muse de
1
Estou usando C # como linguagem de programação e Sql Server 2008 como banco de dados. Estou usando o Microsoft dotNet framework 4.0. Eu realmente sinto muito, não tenho certeza do que você está pedindo (RDBMS ou ORM), talvez você possa me dar minhas versões de framework RDBMS e ORM agora :-). Muito obrigado
Sandy,
1
RDBMS é seu banco de dados, no seu caso o SQL Server 2008. Seu ORM é o método pelo qual você está acessando seu banco de dados, neste caso, ADO.NET. Outros incluem LINQ to SQL e Entity Framework . Na verdade, depois de aprender o básico de ADO.NET e SQL, recomendo usar um ORM como LINQ ou EF, pois eles cuidam de muitos dos problemas que você encontrará ao escrever SQL manualmente.
Chad Levy de

Respostas:

129

O uso de parâmetros ajuda a evitar ataques de injeção de SQL quando o banco de dados é usado em conjunto com uma interface de programa, como um programa de desktop ou site.

Em seu exemplo, um usuário pode executar diretamente o código SQL em seu banco de dados elaborando instruções em txtSalary.

Por exemplo, se eles fossem escrever 0 OR 1=1, o SQL executado seria

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

em que todos os empSalaries seriam devolvidos.

Além disso, um usuário poderia executar comandos muito piores em seu banco de dados, incluindo excluí-lo se escrevesse 0; Drop Table employee:

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

A tabela employeeseria então excluída.


No seu caso, parece que você está usando .NET. Usar parâmetros é tão fácil quanto:

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

Editar 2016-4-25:

De acordo com o comentário de George Stocker, alterei o código de amostra para não usar AddWithValue. Além disso, geralmente é recomendado que você envolva IDisposables em usinginstruções.

Chad Levy
fonte
ótima solução. Mas você pode explicar um pouco mais, por que e como o uso de parâmetros é seguro. Quer dizer, ainda parece que o comando sql será o mesmo
Sandy,
podemos adicionar vários parâmetros ao comando sql. Como podemos exigir em um comando INSERT?
Sandy,
2
O SQL Server trata o texto dentro dos parâmetros apenas como entrada e nunca o executará.
Chad Levy de
3
Sim, você pode adicionar vários parâmetros: Insert Into table (Col1, Col2) Values (@Col1, @Col2). Em seu código, você adicionaria vários AddWithValues.
Chad Levy de
1
Não use AddWithValue! Isso pode causar problemas de conversão implícitos. Sempre defina o tamanho explicitamente e adicione o valor do parâmetro com parameter.Value = someValue.
George Stocker
75

Você está certo, isso está relacionado à injeção de SQL , que é uma vulnerabilidade que permite que um usuário malicioso execute declarações arbitrárias contra o seu banco de dados. Este quadrinho favorito dos velhos tempos XKCD ilustra o conceito:

A filha dela se chama Help I'm presa em uma fábrica de carteiras de motorista.


No seu exemplo, se você apenas usar:

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

Você está aberto à injeção de SQL. Por exemplo, digamos que alguém insira txtSalary:

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

Quando você executar esta consulta, ela executará um SELECTe um UPDATEou DROP, ou o que quiserem. O --no final simplesmente comenta o resto da sua consulta, o que seria útil no ataque se você estivesse concatenando algo depois txtSalary.Text.


A maneira correta é usar consultas parametrizadas, por exemplo (C #):

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

Com isso, você pode executar a consulta com segurança.

Para referência sobre como evitar injeção de SQL em vários outros idiomas, verifique bobby-tables.com , um site mantido por um usuário SO .

NullUserException
fonte
1
ótima solução. Mas você pode explicar um pouco mais, por que e como o uso de parâmetros é seguro. Quer dizer, ainda parece que o comando sql será o mesmo.
Sandy,
1
@ user815600: um equívoco comum - você ainda acredita que a consulta com parâmetros pegará o valor e substituirá os valores reais pelos parâmetros - certo? Não, isso não está acontecendo! - em vez disso, a instrução SQL com parâmetros será transmitida ao SQL Server, junto com uma lista de parâmetros e seus valores - a instrução SQL não será a mesma
marc_s
1
isso significa que a injeção de sql está sendo monitorada pelo mecanismo interno ou de segurança do servidor sql. obrigado.
Sandy,
4
Por mais que eu goste de desenhos animados, se você está executando seu código com privilégios suficientes para eliminar tabelas, provavelmente terá problemas mais amplos.
philw
9

Além de outras respostas, é necessário adicionar que os parâmetros não apenas ajudam a prevenir a injeção de sql, mas podem melhorar o desempenho das consultas . Planos de consulta parametrizados de cache do servidor SQL e reutilizá-los na execução de consultas repetidas. Se você não parametrizou sua consulta, o sql server compilará um novo plano em cada execução da consulta (com alguma exclusão) se o texto da consulta for diferente.

Mais informações sobre o cache do plano de consulta

Oleg
fonte
1
Isso é mais pertinente do que se possa imaginar. Mesmo uma consulta "pequena" pode ser executada milhares ou milhões de vezes, liberando efetivamente todo o cache de consulta.
Tiago
5

Dois anos depois da minha primeira tentativa , estou recidivando ...

Por que preferimos parâmetros? A injeção de SQL é obviamente um grande motivo, mas pode ser que estejamos secretamente desejando voltar ao SQL como linguagem . SQL em strings literais já é uma prática cultural estranha, mas pelo menos você pode copiar e colar sua solicitação no estúdio de gerenciamento. O SQL construído dinamicamente com condicionais da linguagem host e estruturas de controle, quando o SQL tem condicionais e estruturas de controle, é apenas barbarismo de nível 0. Você tem que executar seu aplicativo em depuração, ou com um rastreamento, para ver qual SQL ele gera.

Não pare apenas com parâmetros. Vá até o fim e use o QueryFirst (isenção de responsabilidade: que escrevi). Seu SQL reside em um arquivo .sql que tenta acessar os dados ausentes. E existem inúmeras outras vantagens. Por que você deseja acessar os dados de outra forma?. Você o edita na fabulosa janela do editor TSQL, com validação de sintaxe e Intellisense para suas tabelas e colunas. Você pode atribuir dados de teste na seção de comentários especiais e clicar em "reproduzir" para executar sua consulta ali mesmo na janela. Criar um parâmetro é tão fácil quanto colocar "@myParam" em seu SQL. Então, cada vez que você salva, o QueryFirst gera o wrapper C # para sua consulta. Seus parâmetros aparecem, fortemente tipados, como argumentos para os métodos Execute (). Seus resultados são retornados em um IEnumerable ou Lista de POCOs fortemente tipados, os tipos gerados a partir do esquema real retornado por sua consulta. Se sua consulta não for executada, seu aplicativo não será compilado. Se seu esquema de banco de dados mudar e sua consulta for executada, mas algumas colunas desaparecerem, o erro de compilação apontará para a linha em seu código

bbsimonbb
fonte
4

No Sql, quando qualquer palavra contém o sinal @, significa que é variável e usamos esta variável para definir o valor nela e usá-la na área de número no mesmo script sql porque é restrito apenas no script único enquanto você pode declarar muitas variáveis do mesmo tipo e nome em muitos scripts. Usamos essa variável no lote de procedimento armazenado porque procedimento armazenado são consultas pré-compiladas e podemos passar valores nessas variáveis ​​de script, desktop e sites para obter mais informações, leia Declare Local Variable , Sql Stored Procedure e injeções de sql .

Leia também Proteger contra injeção de sql, que irá guiar como você pode proteger seu banco de dados.

Espero que ajude você a entender também qualquer dúvida me comente.

Emaad Ali
fonte
3

Outras respostas cobrem por que os parâmetros são importantes, mas há uma desvantagem! Em .net, existem vários métodos para criar parâmetros (Add, AddWithValue), mas todos eles exigem que você se preocupe, desnecessariamente, com o nome do parâmetro e todos reduzem a legibilidade do SQL no código. Bem quando você está tentando meditar sobre o SQL, você precisa procurar acima ou abaixo para ver qual valor foi usado no parâmetro.

Eu humildemente afirmo que minha pequena classe SqlBuilder é a maneira mais elegante de escrever consultas parametrizadas . Seu código ficará assim ...

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();

Seu código será mais curto e muito mais legível. Você nem mesmo precisa de linhas extras e, quando está lendo de volta, não precisa ficar procurando o valor dos parâmetros. A aula que você precisa está aqui ...

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();
}
}
bbsimonbb
fonte
Nomear seus parâmetros certamente ajuda na criação de perfil das consultas que o servidor está executando.
Dave R.
Meu chefe disse a mesma coisa. Se nomes de parâmetros significativos forem importantes para você, adicione um argumento paramName ao método de valor. Suspeito que você esteja complicando as coisas desnecessariamente.
bbsimonbb
Péssima ideia. Como foi dito antes, AddWithValuepode causar problemas de conversão implícitos.
Adam Calvet Bohl
@Adam, você está certo, mas isso não impede que AddWithValue () seja amplamente usado e não acho que invalide a ideia. Mas enquanto isso, descobri uma maneira muito melhor de escrever consultas parametrizadas, e que não usa AddWithValue () :-)
bbsimonbb
Certo! Prometo que verei isso em breve!
Adam Calvet Bohl
3

Postagem antiga, mas queria garantir que os novatos estão cientes dos procedimentos armazenados .

Meu valor de 10 centavos aqui é que, se você for capaz de escrever sua instrução SQL como um procedimento armazenado , essa é a abordagem ideal. SEMPRE uso procs armazenados e nunca faço loop pelos registros em meu código principal. Por Exemplo: SQL Table > SQL Stored Procedures > IIS/Dot.NET > Class.

Ao usar procedimentos armazenados, você pode restringir o usuário apenas à permissão EXECUTAR , reduzindo assim os riscos de segurança .

Seu procedimento armazenado é inerentemente paramerizado e você pode especificar parâmetros de entrada e saída.

O procedimento armazenado (se retornar dados por meio de uma SELECTinstrução) pode ser acessado e lido exatamente da mesma maneira que você faria com uma SELECTinstrução normal em seu código.

Ele também é executado com mais rapidez, pois é compilado no SQL Server.

Eu também mencionei que você pode fazer várias etapas, por exemplo, updateuma tabela, verificar os valores em outro servidor de banco de dados e, quando finalmente terminar, retornar os dados para o cliente, tudo no mesmo servidor, e sem interação com o cliente. Portanto, isso é MUITO mais rápido do que codificar essa lógica em seu código.

Arvin Amir
fonte