有没有办法让 Oracle 为每个查询调用重新计算查询计划?
Posted
技术标签:
【中文标题】有没有办法让 Oracle 为每个查询调用重新计算查询计划?【英文标题】:Is there a way to make Oracle recalculate a query plan for each query invocation? 【发布时间】:2009-04-07 07:40:27 【问题描述】:我有一个参数化查询。根据参数值,最佳查询计划会有很大差异。麻烦在于:Oracle 将第一次查询调用的计划用于后续调用,导致性能不佳。我通过动态 SQL 处理它,但这种方式远非优雅。那么问题来了:有没有办法告诉Oracle必须重新计算查询计划?
【问题讨论】:
【参考方案1】:如果查询计划确实在参数值上发生了显着变化,也许你不应该为这个参数使用绑定变量。
该参数可以取多少个不同的值?如果只有几个,您最终会得到几个查询计划(每个值一个),这些计划有望表现良好并且可以重复使用。
或者您可以在 SQL 语句中使用 cmets "/* THIS IS VALUE BRACKET ONE * /" 来分隔它们(或查询分析器提示,如果您觉得自己知道哪些是合适的,例如 /*+ CARDINALITY * / 可能适用于此)。
无论哪种方式,我认为您都希望拥有单独的 SQL 语句,以便您可以在 Statspack 和朋友中获得单独的报告,因为看起来您确实想要微调该查询。
【讨论】:
【参考方案2】:对于 Oracle 10g,我们会选择查询中的任何表并执行
GRANT SELECT ON table1 TO user1;
这将使引用该表的任何查询的计划无效。当然,您会希望选择一个对其他查询影响最小的表。另请参阅this page 了解更多信息和示例列表。
【讨论】:
【参考方案3】:如果您真的想每次都生成一个新的查询计划,只需按照 thilo 的建议添加一个独特的注释
select /* SQLID=1234 */ 1 from dual;
select /* SQLID=1235 */ 1 from dual;
这些应该会产生独特的计划。
我非常怀疑是否需要这样做,但在尝试绕过优化器之前,您应该非常确定您的统计数据没有错误。
【讨论】:
我无法修改 SQL 源。统计数据很好。就我而言,问题是查询计划已过时且不好。如果我使计划无效,Oracle 将计算出新的好计划。 如果您根本无法更改 SQL 源,那么 Il-Bhima 是您的最佳选择。【参考方案4】:优化器使用的东西之一是相关列的直方图。如果您使用绑定变量并且相关列上有直方图,则计划可能会根据参数值而改变。第一个计划将保留在共享池中,并将用于所有值。
如果你不想这样,那么你可以使用文字而不是绑定(如果你不会有太多相同的 sql 版本)。或者您可以移除直方图,移除直方图可确保独立于绑定参数值生成相同的计划。
每次执行都使 sql 无效不是一个好主意。根据这个 sql 的使用频率,它可能会导致新的问题,例如硬解析导致的闩锁问题。
【讨论】:
【参考方案5】:有没有办法告诉 Oracle 必须重新计算查询计划?
您可以为不同的执行计划创建多个OUTLINE
,并使用OUTLINE CATEGORIES
选择要使用的一个:
CREATE OUTLINE ol_use_nl
FOR
SELECT *
FROM mytable1 mt1
JOIN mytable2 mt2
ON mt1.id = mt2.id
WHERE mt1.value BETWEEN :a AND :b
CATEGORY FILTERED;
/* Edit the outline to add USE_NL */
CREATE OUTLINE ol_use_nl
FOR
SELECT *
FROM mytable1 mt1
JOIN mytable2 mt2
ON mt1.id = mt2.id
WHERE mt1.value BETWEEN :a AND :b
CATEGORY UNFILTERED;
/* Edit the outline to add USE_HASH */
ALTER SESSION SET USE_STORED_OUTLINES = FILTERED;
SELECT *
FROM mytable1 mt1
JOIN mytable2 mt2
ON mt1.id = mt2.id
WHERE mt1.value BETWEEN 1 AND 2
/* This will use NESTED LOOPS */
ALTER SESSION SET USE_STORED_OUTLINES = UNFILTERED;
SELECT *
FROM mytable1 mt1
JOIN mytable2 mt2
ON mt1.id = mt2.id
WHERE mt1.value BETWEEN 1 AND 1000000
/* This will use HASH JOIN */
【讨论】:
【参考方案6】:您的问题是由于绑定变量偷看 - 为整个数据库关闭它可能会破坏其他东西,但您可以通过添加以下提示来关闭它:
/*+ opt_param('_OPTIM_PEEK_USER_BINDS',FALSE) */
【讨论】:
他可能想要相反的意思,“每次都偷看”。我想知道是否也有提示。 “每次偷看”就是使用文字。【参考方案7】:OP 告诉我们他不能更改 sql 语句。使用包dbms_advanced_rewrite
可以拦截SQL 语句并更改该SQL 语句。
【讨论】:
以上是关于有没有办法让 Oracle 为每个查询调用重新计算查询计划?的主要内容,如果未能解决你的问题,请参考以下文章
Spring Boot 2 + JdbcTemplate - 有没有办法为每个支持的数据库提供 SQL 查询?