3

SQL 参数化现在是一个热门话题,并且有充分的理由,但除了体面地转义之外,它真的有什么作用吗?

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

> 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 是否具有这些功能,以及是否自动完成查询重用(或者如果没有,如何做到这一点)。

另外,请在有人阅读此更新时发表评论。我不确定它是否会影响问题或什么......

谢谢!

4

3 回答 3

7

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

但是,根据使用 SQL Server 的经验,我可以告诉您,使用 ADO.NET 发送命令时会保留参数。它们没有被折叠到声明中。例如,如果您使用 SQL Profiler,您将看到一个远程过程调用,如:

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)并查看数据包。

不管它在内部是如何处理的,以及它当前可能或可能不为给定引擎提供的任何优化,都很少(什么都没有?)从不使用参数中获益。

于 2012-08-11T21:11:13.733 回答
3

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

于 2012-08-11T21:10:17.980 回答
2

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

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

一个例子是,如果您将 Npgsql 与不是准备好的语句的查询一起使用,那么它几乎只是正确地转义了事情(尽管在 Postgresql 中转义有一些边缘情况,知道转义的人会错过,而 Npgsql 会全部捕获它们,所以还是有收获的)。使用准备好的语句,它将参数作为准备好的语句参数发送。因此,一种情况允许比另一种情况更多的查询计划重用。

同一框架 (ADO.NET) 的 SQLServer 驱动程序将查询作为对 sp_executesql 的调用传递,从而允许查询计划重用。

除此之外,出于以下几个原因,逃跑的问题仍然值得考虑:

每次都是相同的代码。如果您要逃避自己,那么您要么每次都通过同一段代码这样做(因此使用别人的同一段代码没有任何不利之处),或者您每次都冒着失误的风险时间。

他们也更擅长不逃跑。例如,没有必要遍历数字的字符串表示中的每个字符来查找'字符。但不逃避算作不必要的风险,或合理的微观优化。

好吧,“合理的微优化”本身意味着两件事之一。要么它不需要精神上的努力来写或读之后的正确性(在这种情况下你也可以),或者它被频繁地击中以至于微小的节省会加起来,而且很容易完成。

(相关地,编写一个高度优化的转义器也更有意义 - 所涉及的字符串替换是最常见的替换方法至少不如某些语言中的其他方法快的情况,但是只有当方法被调用很多次时优化才有意义)。

如果您有一个包含类型检查参数的库(基于类型使用的格式或通过验证,这两者在此类代码中都很常见),那么这很容易做到,因为这些库旨在大规模使用,这是一个合理的微选。

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

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

另外,我真的不同意将其称为“热门话题”。至少十多年来,它已经达成了公认的共识。

于 2012-08-11T21:46:38.217 回答