假设我有一个名为Projects
的表,其中一个名为Budget
的列具有标准的B-Tree索引。该表有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的情况感兴趣。
答案 0 :(得分:7)
具体来说,取决于Oracle。
在相当长的时间内(至少9i),Oracle支持绑定变量偷看。这意味着第一次执行查询时,优化器会查看绑定变量的值,并将其基数估计值基于第一个绑定变量的值。在大多数查询执行将具有返回类似大小结果的绑定变量值的情况下,这是有意义的。如果99%的查询使用的是较小的预算值,则第一次执行很可能会使用较小的值,因此缓存的查询计划将适用于小的绑定变量值。当然,这意味着当你指定一个大的绑定变量值时(或者更糟糕的是,如果你运气好并且第一次执行的值很大),你将获得不太理想的查询计划。
如果您使用11g,Oracle可以使用adaptive cursor sharing。这允许优化器为单个查询维护多个查询计划,并根据绑定变量值选择适当的计划。但是,随着时间的推移,这会变得相当复杂。如果您有一个带有N绑定变量的查询,优化器必须弄清楚如何将该N维空间划分为不同的绑定变量值的不同查询计划,以便确定何时以及是否为新的查询重新优化查询绑定变量值的集合以及何时简单地重用早期计划。许多工作最终在夜间维护窗口期间完成,以避免在生产日期间产生这些费用。但是,这也带来了DBA希望数据库随着时间的推移有多大的自由度以及DBA想要控制计划的程度,以便数据库不会突然开始选择导致某些主要系统的糟糕计划的问题。在随机的一天慢慢爬行。
答案 1 :(得分:6)
这让我感到惊讶,因为通常当您阅读绑定参数的好处时,它就会阻止SQL注入攻击。
请勿将参数化查询与预设报表混淆。两者都提供参数化,但是准备好的语句提供了查询计划的额外缓存。
为什么在生成和缓存查询计划之前未解析绑定参数?
因为有时候生成查询计划是一个昂贵的步骤。准备好的语句允许您分摊查询计划的成本。
但是,如果您要查找的只是SQL注入保护,请不要使用预准备语句。使用参数化查询。
例如,在PHP中,您可以使用http://php.net/pg_query_params执行参数化查询而不缓存查询计划;同时http://php.net/pg_prepare和http://php.net/pg_execute用于缓存预准备语句的计划,然后执行它。
修改:9.2 apparently changes the way prepared statements are planned