it-swarm.cn

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

我很擅长使用数据库。现在我可以编写SELECTUPDATEDELETEINSERT命令。但我看过很多我们喜欢写的论坛:

SELECT empSalary from employee where salary = @salary

...代替:

SELECT empSalary from employee where salary = txtSalary.Text

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

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

99
Sandy

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

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

例如,如果他们要写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:

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

112
Chad Levy

你是对的,这与 SQL注入 有关,这是一个漏洞,允许一个appleioius用户对你的数据库执行任意语句。这个旧时代的最爱 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.

当您执行此查询时,它将执行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 user 维护的网站。

66
NullUserException

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

有关查询计划缓存的更多信息

4
Oleg

两年后 我第一次去 ,我正在考虑......

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

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

3
bbsimonbb

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

同时阅读 保护sql注入 它将指导如何保护您的数据库。

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

3
Emaad Ali

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

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

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

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

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

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

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

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

0
Arvin Amir