提问人:Sandy 提问时间:9/22/2011 最后编辑:alamshahbaz16497Sandy 更新时间:6/24/2021 访问量:157794
为什么我们总是喜欢在 SQL 语句中使用参数?
Why do we always prefer using parameters in SQL statements?
问:
我对数据库非常陌生。现在我可以编写 、 、 和命令了。但是我看过很多论坛,我们更喜欢写:SELECT
UPDATE
DELETE
INSERT
SELECT empSalary from employee where salary = @salary
...而不是:
SELECT empSalary from employee where salary = txtSalary.Text
为什么我们总是喜欢使用参数,我将如何使用它们?
我想知道第一种方法的用途和好处。我什至听说过SQL注入,但我并不完全理解它。我什至不知道SQL注入是否与我的问题有关。
答:
当数据库与程序界面(如桌面程序或网站)结合使用时,使用参数有助于防止 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 括在语句中。AddWithValue
IDisposable
using
评论
Insert Into table (Col1, Col2) Values (@Col1, @Col2)
AddWithValue
parameter.Value = someValue
salaryParam.Value = CDec(txtMoney.Text)
money
Decimal
你是对的,这与SQL注入有关,这是一个漏洞,允许恶意用户对你的数据库执行任意语句。这部旧时最喜欢的 XKCD 漫画说明了这个概念:
在您的示例中,如果您只使用:
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 或他们想要的任何操作。末尾只是注释掉查询的其余部分,如果您在 . 之后连接任何内容,这将在攻击中很有用。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 用户维护的网站。
评论
在 Sql 中,当任何单词包含 @ 符号时,这意味着它是变量,我们使用此变量在其中设置值并将其用于同一 sql 脚本的数字区域,因为它仅在单个脚本上受到限制,而您可以在许多脚本上声明许多相同类型和名称的变量。我们在存储过程批次中使用此变量,因为存储过程是预编译的查询,我们可以从脚本、桌面和网站传递这些变量中的值,有关更多信息,请阅读声明局部变量、SQL 存储过程和 sql 注入。
另请阅读防止 sql 注入,它将指导如何保护数据库。
希望它能帮助您理解任何问题,请评论我。
其他答案涵盖了为什么参数很重要,但有一个缺点!在 .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();
}
}
评论
AddWithValue
除了其他答案之外,还需要补充的是,参数不仅有助于防止 sql 注入,而且可以提高查询的性能。SQL Server 缓存参数化查询计划,并在重复执行查询时重用它们。如果未对查询进行参数化,则如果查询文本不同,sql server 将在每次查询(排除)执行时编译新计划。
评论
在我第一次去两年后,我又开始......
为什么我们更喜欢参数?SQL注入显然是一个重要原因,但可能是我们暗中渴望回到SQL作为一种语言。字符串文字中的 SQL 已经是一种奇怪的文化实践,但至少您可以将请求复制并粘贴到 Management Studio 中。使用宿主语言条件和控制结构动态构造的 SQL,当 SQL 具有条件和控制结构时,只是 0 级野蛮。您必须在调试中或使用跟踪运行您的应用程序,以查看它生成的 SQL。
不要只停留在参数上。一路走来,使用 QueryFirst(免责声明:我写的)。您的 SQL 位于 .sql 文件中。您可以在出色的 TSQL 编辑器窗口中对其进行编辑,并为您的表和列提供语法验证和 Intellisense。您可以在特殊注释部分分配测试数据,然后单击“播放”以在窗口中运行查询。创建参数就像在 SQL 中输入“@myParam”一样简单。然后,每次保存时,QueryFirst 都会为查询生成 C# 包装。您的参数会弹出,以强类型形式作为 Execute() 方法的参数。结果以 IEnumerable 或强类型 POCO 列表的形式返回,这些类型是从查询返回的实际架构生成的。如果查询未运行,则应用将无法编译。如果数据库架构发生更改,并且查询运行,但某些列消失,则编译错误将指向代码中尝试访问缺失数据的行。还有许多其他优点。为什么要以其他方式访问数据?
旧帖子,但希望确保新人了解存储过程。
我在这里的 10 美分价值是,如果您能够将 SQL 语句编写为存储过程,那么在我看来这是最佳方法。我总是使用存储的过程,从不在我的主代码中循环访问记录。例如:。SQL Table > SQL Stored Procedures > IIS/Dot.NET > Class
使用存储过程时,可以将用户限制为仅具有 EXECUTE 权限,从而降低安全风险。
存储过程本质上是参数化的,可以指定输入和输出参数。
存储过程(如果它通过语句返回数据)的访问和读取方式与代码中的常规语句完全相同。SELECT
SELECT
由于在 SQL Server 上编译,因此它的运行速度也更快。
我有没有提到你可以做多个步骤,例如 一个表,检查另一个数据库服务器上的值,然后在最终完成后,将数据返回给客户端,所有数据都在同一台服务器上,并且不与客户端交互。因此,这比在代码中编写此逻辑要快得多。update
评论