5

假设我有一个用标准 B-Tree 索引调用Projects的列调用的表。Budget该表有 50,000 个项目,其中只有 1% 的预算超过一百万。如果我运行 SQL 查询:

SELECT * From Projects WHERE Budget > 1000000;

规划器将使用索引范围扫描Budget从堆表中获取行。但是,如果我使用查询:

SELECT * From Projects WHERE Budget > 50;

规划器很可能会对表进行顺序扫描,因为它知道这个查询最终会返回大部分或所有行,并且没有理由将索引的所有页面加载到内存中。

现在,假设我运行查询:

SELECT * From Projects WHERE Budget > :budget;

:budget传递到我的数据库的绑定参数在哪里。根据我的阅读,上面的查询将被缓存,并且无法推断出任何有关基数的数据。事实上,大多数数据库只是假设分布均匀,缓存的查询计划将反映这一点。这让我感到惊讶,因为通常当您阅读绑定参数的好处时,它的主题是防止 SQL 注入攻击。

显然,如果生成的查询计划相同,这可以提高性能,因为不必编译新计划,但如果 的值变化很大,也会损害性能。:budget

我的问题:为什么在生成和缓存查询计划之前没有解析绑定参数?现代数据库不应该努力为查询生成最佳计划,这应该意味着查看每个参数的值并获得准确的索引统计信息吗?

注意:这个问题可能不适用于 mySql,因为 mySql 不缓存 SQL 计划。但是,我对为什么 Postgres、Oracle 和 MS SQL 会出现这种情况很感兴趣。

4

2 回答 2

7

具体而言,对于 Oracle,这取决于。

相当长一段时间(至少 9i),Oracle 一直支持绑定变量窥视。这意味着第一次执行查询时,优化器会查看绑定变量的值,并将其基数估计基于第一个绑定变量的值。在大多数查询执行将具有返回相似大小结果的绑定变量值的情况下,这是有道理的。如果 99% 的查询使用较小的预算值,则第一次执行很可能会使用较小的值,因此缓存的查询计划将适用于较小的绑定变量值。当然,这意味着当您指定一个较大的绑定变量值时(或者,更糟糕的是,如果您很幸运并且第一次执行时使用了一个较大的值),您将获得的查询计划不是最优的。

如果你使用的是 11g,Oracle 可以使用自适应游标共享. 这允许优化器为单个查询维护多个查询计划,并根据绑定变量值选择适当的计划。不过,随着时间的推移,这可能会变得相当复杂。如果您有一个包含 N 个绑定变量的查询,优化器必须弄清楚如何将该 N 维空间划分为针对不同绑定变量值的不同查询计划,以便确定何时以及是否为新的查询重新优化一组绑定变量值以及何时简单地重用早期计划。很多工作最终都在夜间维护时段内完成,以避免在高效的白天产生这些成本。

于 2012-09-05T17:13:20.107 回答
6

这让我感到惊讶,因为通常当您阅读绑定参数的好处时,它的主题是防止 SQL 注入攻击。

不要将参数化查询准备好的语句混淆。两者都提供参数化,但准备好的语句提供查询计划的额外缓存。

为什么在生成和缓存查询计划之前没有解析绑定参数?

因为有时生成查询计划是一个昂贵的步骤。准备好的语句允许您分摊查询计划的成本。

但是,如果您要寻找的只是 SQL 注入保护,请不要使用准备好的语句。使用参数化查询。

例如,在 PHP 中,您可以使用http://php.net/pg_query_params执行参数化查询而不缓存查询计划;同时http://php.net/pg_preparehttp://php.net/pg_execute用于缓存一个准备好的语句的计划,然后执行它。

编辑:9.2 显然改变了准备好的语句的计划方式

于 2012-09-05T17:11:49.237 回答