SQL参数化:这是如何在幕后工作的?

时间:2012-08-11 20:52:35

标签: mysql sql parameterized

SQL参数化现在是一个热门话题,对于a good reason来说,除了正确地逃避之外,它真的做了什么吗?

我可以想象一个参数化引擎只是在将数据插入查询字符串之前确保数据被正常转义,但这真的是它的全部功能吗?在连接中做一些不同的事情会更有意义,例如:像这样:

> Sent data. Formatting: length + space + payload
< Received data
-----
> 69 SELECT * FROM `users` WHERE `username` LIKE ? AND `creation_date` > ?
< Ok. Send parameter 1.
> 4 joe%
< Ok. Send parameter 2.
> 1 0
< Ok. Query result: [...]

这种方式可以简单地消除SQL注入的问题,因此您不必通过转义来避免它们。我能想到参数化可能如何工作的唯一方法是通过转义参数:

// $params would usually be an argument, not in the code like this
$params = ['joe%', 0];

// Escape the values
foreach ($params as $key=>$value)
    $params[$key] = mysql_real_escape_string($value);

// Foreach questionmark in the $query_string (another argument of the function),
// replace it with the escaped value.
$n = 0;
while ($pos = strpos($query_string, "?") !== false && $n < count($params)) {
    // If it's numeric, don't use quotes around it.
    $param = is_numeric($params[$n]) ? $params[$n] : "'" . $params[$n] . "'";
    // Update the query string with the replaced question mark
    $query_string = substr($query_string, 0, $pos) //or $pos-1? It's pseudocode...
                  . $param
                  . substr($query_string, $pos + 1);
    $n++;

如果是后者,我现在还不打算将我的网站切换到参数化。它没有我能看到的优势,它只是另一个强大的弱变量输入讨论。强类型可能会在编译时捕获更多错误,但它并没有真正做出任何其他可能很难做到的事情 - 与此参数化相同。 (如果我错了,请纠正我!)


更新

  • 我知道这将取决于SQL服务器(以及客户端,但我认为客户端使用最好的技术),但大多数时候我都考虑过MySQL。关于其他数据库的答案虽然也受欢迎。
  • 据我了解答案,参数化确实不仅仅是简单地转义数据。它实际上是以参数化方式发送到服务器的,因此将变量分开而不是作为单个查询字符串。
  • 这也使服务器能够存储和重用具有不同参数的查询,从而提供更好的性能。

我得到了一切吗?我仍然很好奇的一件事是MySQL是否具有这些功能,以及是否自动完成查询重用(如果没有,可以如何完成)。

此外,请在有人阅读此更新时发表评论。我不确定它是否会碰到这个问题......

谢谢!

3 个答案:

答案 0 :(得分:6)

我确信您的命令和参数的处理方式会因特定的数据库引擎和客户端库而异。

但是,根据SQL Server的经验,我可以告诉您,使用ADO.NET发送命令时会保留参数。他们没有被纳入声明。例如,如果您使用SQL事件探查器,您将看到一个远程过程调用,如:

exec sp_executesql N'INSERT INTO Test (Col1) VALUES (@p0)',N'@p0 nvarchar(4000)',@p0=N'p1'

请记住,除了阻止SQL注入之外,参数化还有其他好处。例如,查询引擎更有可能重复使用参数化查询的查询计划,因为该语句始终相同(只是参数值更改)。

响应更新: 查询参数化非常普遍,我希望MySQL(实际上是任何数据库引擎)能够同样处理它。

基于MySQL协议文档,看起来使用COM_PREPARECOM_EXECUTE数据包处理预处理语句,这些数据包支持二进制格式的单独参数。目前尚不清楚是否会准备好所有参数化语句,但看起来没有准备好的语句由COM_QUERY处理,没有提及参数支持。

如有疑问:请测试。如果您真的想知道通过线路发送了什么,请使用Wireshark之​​类的网络协议分析器并查看数据包。

无论内部处理方式如何,以及目前为给定引擎提供的优化程度如何,都可以从不使用参数中获得很少(没有?)。

答案 1 :(得分:3)

参数化查询作为参数化查询传递给SQL实现,除非实现决定回退连接自身,否则参数永远不会连接到查询本身。参数化查询避免了转义的需要,并且提高了性能,因为查询是通用的,并且更有可能的是,数据库服务器已经缓存了编译形式的查询。

答案 2 :(得分:2)

直接的答案是“它在所讨论的特定实现中以任何方式实现”。有几十个数据库,几十个访问层,在某些情况下,同一个访问层有多种方式可以处理相同的代码。

所以,这里没有一个正确答案。

一个例子是,如果你使用Npgsql的查询不是一个准备好的语句,那么它几乎只是正确地逃避了事情(虽然在Postgresql中转义有一些边缘情况,知道逃避错过的人,和Npgsql抓住他们所有,所以仍然是一个收获)。使用预准备语句,它将参数作为prepared-statment参数发送。因此,有一种情况允许更多的查询计划重用。

同一框架的SQLServer驱动程序(ADO.NET)通过调用sp_executesql来传递查询,这允许重新使用查询计划。

除此之外,出于某些原因,逃避问题仍值得考虑:

每次都是相同的代码。如果你正在逃避自己,那么你每次都要通过同一段代码这样做(所以这并不像使用别人的同一段代码那样有任何不利因素),或者你每个人都要冒犯时间。

他们也更善于逃避。例如,在查找'个字符的数字的字符串表示中,每个字符都没有意义。但是,不能将数量视为不必要的风险或合理的微观优化。

嗯,“合理的微观优化”本身就是两件事之一。要么它不需要精神上的努力就可以在之后编写或读取正确性(在这种情况下你可能也是如此),或者它经常受到影响,以至于很少有节省,并且很容易完成。

(相关地,编写高度优化的转义器也更有意义 - 涉及的字符串替换类型是这样一种情况,其中最常见的替换方法不如某些语言中的某些其他方法那么快,但优化只有在非常多次调用该方法时才有意义。)

如果你有一个包含类型检查参数的库(无论是基于类型使用的格式,还是通过验证,这两种都是这种代码都很常见),那么它很容易实现,因为这些库的目标是在大规模使用时,这是一个合理的微观选择。

如果您每次都在考虑8参数调用的参数编号7是否可能包含'字符,那么它不是。

如果您愿意,它们也更容易转换为其他系统。再看一下上面给出的两个例子,除了创建的类之外,你可以使用与System.Data.SqlClient完全相同的代码和Npgsql,尽管SQL-Server和Postgresql有不同的转义规则。它们的二进制字符串,日期时间和一些其他数据类型的格式完全不同。

另外,我真的不同意称这是一个“热门话题”。至少十多年来,它已经建立了良好的共识。