为什么我们总是喜欢在SQL语句中使用参数?

时间:2011-09-21 19:51:58

标签: sql sql-server sql-injection

我对使用数据库非常陌生。现在我可以编写SELECTUPDATEDELETEINSERT命令。但我见过许多我们喜欢写的论坛:

SELECT empSalary from employee where salary = @salary

...而不是:

SELECT empSalary from employee where salary = txtSalary.Text

为什么我们总是喜欢使用参数以及如何使用它们?

我想知道第一种方法的用途和好处。我甚至听说过SQL注入,但我并不完全理解它。我甚至不知道SQL注入是否与我的问题有关。

7 个答案:

答案 0 :(得分:112)

当数据库与程序界面(如桌面程序或网站)结合使用时,使用参数有助于防止 SQL注入攻击

在您的示例中,用户可以通过在txtSalary中制作语句来直接在数据库上运行SQL代码。

例如,如果他们要编写0 OR 1=1,则执行的SQL将是

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

将返还所有的empSalaries。

此外,用户可以针对您的数据库执行更糟糕的命令,包括删除它如果他们写了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:

根据George Stocker的评论,我将示例代码更改为不使用AddWithValue。此外,通常建议您在IDisposable语句中包含using

答案 1 :(得分:66)

你是对的,这与SQL injection有关,XKCD comic是一个漏洞,允许一个appleioius用户对你的数据库执行任意语句。这个旧时代的最爱bobby-tables.com说明了这个概念:

Her daughter is named Help I'm trapped in a driver's license factory.


在您的示例中,如果您只使用:

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

您对SQL注入持开放态度。例如,假设有人输入txtSalary:

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

执行此查询时,它会执行SELECTUPDATEDROP,或者他们想要的任何内容。最后的--只会注释掉您的查询的其余部分,如果您在txtSalary.Text之后连接任何内容,这对攻击很有用。


正确的方法是使用参数化查询,例如(C#):

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

这样,您就可以安全地执行查询。

有关如何避免使用其他几种语言进行SQL注入的参考,请查看由SO user维护的网站{{3}}。

答案 2 :(得分:5)

除了其他答案之外,需要添加该参数不仅有助于防止sql注入,而且可以提高查询性能。 Sql server缓存参数化查询计划并在重复查询执行时重用它们。如果您没有参数化您的查询,那么 sql server会在每个查询上编译新计划(有一些排除)执行,如果查询文本不同。

More information about query plan caching

答案 3 :(得分:3)

在Sql中,当任何单词包含@符号时,表示它是变量,我们使用此变量在其中设置值并在同一个sql脚本的数字区域上使用它,因为它只能在单个脚本上受限制而您可以声明许多脚本上有很多相同类型和名称的变量。我们在存储过程批次中使用此变量,因为存储过程是预编译的查询,我们可以从脚本,桌面和网站传递这些变量中的值,以获取有关Declare Local VariableSql Stored Procedure和{{3}的更多信息}。

另请阅读sql injections,它将指导您如何保护数据库。

希望它能帮助你理解任何评论我的问题。

答案 4 :(得分:3)

my first go之后两年,我正在考虑......

为什么我们更喜欢参数? SQL注入显然是一个重要原因,但可能是我们暗中渴望回到SQL 作为一种语言 。字符串文字中的SQL已经是一种奇怪的文化习俗,但至少你可以将你的请求复制并粘贴到管理工作室。当SQL具有条件和控制结构时,使用宿主语言条件和控制结构动态构造的SQL只是0级的野蛮行为。您必须在调试或跟踪中运行您的应用程序,以查看它生成的SQL。

不要仅使用参数停止。一直走,并使用 QueryFirst (免责声明:我写的)。您的SQL在.sql文件中 。您可以在神奇的TSQL编辑器窗口中编辑它,并为您的表和列提供语法验证和Intellisense。您可以在特殊注释部分中分配测试数据,然后单击“播放”以在窗口中运行查询。创建参数就像在SQL中放入“@myParam”一样简单。然后,每次保存时,QueryFirst都会为您的查询生成C#包装器。您的参数会弹出,强类型,作为Execute()方法的参数。您的结果将在IEnumerable或强类型POCO列表中返回,这些类型是从查询返回的实际架构生成的类型。如果您的查询未运行,您的应用将无法编译。如果您的数据库架构发生更改并且您的查询运行但某些列消失,则编译错误指向代码中试图访问缺失数据的行。还有许多其他优点。 为什么要以其他方式访问数据?

答案 5 :(得分:2)

其他答案涵盖了为什么参数很重要,但有一个缺点!在.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();
}
}

答案 6 :(得分:0)

旧帖但希望确保新手了解存储过程

我的10¢值得一提的是,如果您能够将SQL语句编写为存储过程,那么在我看来,这是最佳方法。我总是使用存储过程并且永远不会遍历我的主代码中的记录。例如:SQL Table > SQL Stored Procedures > IIS/Dot.NET > Class

使用存储过程时,您可以将用户限制为仅执行权限,从而降低安全风险

您的存储过程本质上是瘫痪的,您可以指定输入和输出参数。

存储过程(如果它通过SELECT语句返回数据)可以通过与代码中常规SELECT语句完全相同的方式进行访问和读取。

它在SQL Server上编译时也运行得更快。

我是否也提到过你可以做多个步骤,例如update一个表,检查另一个数据库服务器上的值,然后一旦最终完成,将数据返回给客户端,所有这些都在同一台服务器上,并且不与客户端进行交互。因此,这比在代码中编码此逻辑要快得多。