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

Why do we always prefer using parameters in SQL statements?

提问人:Sandy 提问时间:9/22/2011 最后编辑:alamshahbaz16497Sandy 更新时间:6/24/2021 访问量:157794

问:

我对数据库非常陌生。现在我可以编写 、 、 和命令了。但是我看过很多论坛,我们更喜欢写:SELECTUPDATEDELETEINSERT

SELECT empSalary from employee where salary = @salary

...而不是:

SELECT empSalary from employee where salary = txtSalary.Text

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

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

sql sql-server sql 注入

评论


答:

147赞 Chad Levy 9/22/2011 #1

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

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

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

 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。使用参数非常简单:

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();
}
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 的评论,我将示例代码更改为不使用 .此外,通常建议您将 s 括在语句中。AddWithValueIDisposableusing

评论

0赞 Sandy 9/22/2011
很棒的解决方案。但是您能解释一下,为什么以及如何使用参数是安全的吗?我的意思是看起来 sql 命令仍然是一样的
2赞 Chad Levy 9/22/2011
SQL Server 仅将参数中的文本视为输入,并且永远不会执行它。
3赞 Chad Levy 9/22/2011
是的,您可以添加多个参数:.在代码中,您将添加多个 s。Insert Into table (Col1, Col2) Values (@Col1, @Col2)AddWithValue
1赞 George Stocker 3/11/2015
请不要使用 AddWithValue!这可能会导致隐式转换问题。始终显式设置大小,并使用 添加参数值。parameter.Value = someValue
2赞 Andrew Morton 3/17/2017
您确实应该使用:SQL Server 在 .NET 中:SQL Server 数据类型映射。参数名称需要“@”,如“@salary”。salaryParam.Value = CDec(txtMoney.Text)moneyDecimal
87赞 NullUserException 9/22/2011 #2

你是对的,这与SQL注入有关,这是一个漏洞,允许恶意用户对你的数据库执行任意语句。这部旧时最喜欢的 XKCD 漫画说明了这个概念:

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.

当您执行此查询时,它将执行 a 和 or 或他们想要的任何操作。末尾只是注释掉查询的其余部分,如果您在 . 之后连接任何内容,这将在攻击中很有用。SELECTUPDATEDROP--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赞 Sandy 9/22/2011
很棒的解决方案。但是您能解释一下,为什么以及如何使用参数是安全的吗?我的意思是看起来 sql 命令仍然是一样的。
1赞 marc_s 9/22/2011
@user815600:一个常见的误解 - 您仍然认为带有参数的查询将接受值并用参数替换实际值 - 对吗?不,这不会发生! - 取而代之的是,带有参数的 SQL 语句将与参数及其值列表一起传输到 SQL Server - SQL 语句不会相同
1赞 Sandy 9/22/2011
这意味着 SQL 注入正受到 SQL Server 内部机制或安全性的监视。谢谢。
7赞 philw 8/4/2013
就像我喜欢卡通一样,如果你以足够的权限运行你的代码来删除表,你可能会遇到更广泛的问题。
4赞 Emaad Ali 9/22/2011 #3

在 Sql 中,当任何单词包含 @ 符号时,这意味着它是变量,我们使用此变量在其中设置值并将其用于同一 sql 脚本的数字区域,因为它仅在单个脚本上受到限制,而您可以在许多脚本上声明许多相同类型和名称的变量。我们在存储过程批次中使用此变量,因为存储过程是预编译的查询,我们可以从脚本、桌面和网站传递这些变量中的值,有关更多信息,请阅读声明局部变量SQL 存储过程sql 注入

另请阅读防止 sql 注入,它将指导如何保护数据库。

希望它能帮助您理解任何问题,请评论我。

3赞 bbsimonbb 12/5/2014 #4

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

评论

0赞 Dave R. 5/6/2015
在分析服务器正在运行的查询时,命名参数肯定会有所帮助。
0赞 bbsimonbb 6/4/2015
我的老板也说了同样的话。如果有意义的参数名称对您很重要,请向 value 方法添加 paramName 参数。我怀疑你把事情弄得不必要地复杂化了。
0赞 Adam Calvet Bohl 1/25/2017
坏主意。如前所述,可能会导致隐式转换问题。AddWithValue
0赞 bbsimonbb 1/25/2017
@Adam你是对的,但这并不能阻止 AddWithValue() 被广泛使用,而且我不认为它会使这个想法无效。但与此同时,我想出了一种更好的方法来编写参数化查询,并且不使用 AddWithValue() :-)
0赞 Adam Calvet Bohl 1/25/2017
右!答应我很快就会看到的!
12赞 Oleg 6/11/2015 #5

除了其他答案之外,还需要补充的是,参数不仅有助于防止 sql 注入,而且可以提高查询的性能。SQL Server 缓存参数化查询计划,并在重复执行查询时重用它们。如果未对查询进行参数化,则如果查询文本不同,sql server 将在每次查询(排除)执行时编译新计划

有关查询计划缓存的详细信息

评论

2赞 James 1/13/2017
这比人们想象的更相关。即使是“小”查询也可以执行数千或数百万次,从而有效地刷新整个查询缓存。
7赞 bbsimonbb 2/8/2017 #6

在我第一次去两年后,我又开始......

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

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

4赞 Vinnie Amir 6/7/2017 #7

旧帖子,但希望确保新人了解存储过程

我在这里的 10 美分价值是,如果您能够将 SQL 语句编写为存储过程,那么在我看来这是最佳方法。我总是使用存储的过程,从不在我的主代码中循环访问记录。例如:。SQL Table > SQL Stored Procedures > IIS/Dot.NET > Class

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

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

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

由于在 SQL Server 上编译,因此它的运行速度也更快。

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