在跨年时出现执行计划的突变

遇到这样一个问题,客户的某系统在进入2014年后,某sql执行就非常慢,看到这个sql的执行计划发生了改变,原来很好的走hash连接的执行计划变成了走索引的nest loop的执行计划。在收集统计信息后,恢复正常。

这个故障的测试案例,我们可以这样模拟:

我们看一下数据大致的情况:

注意这里建立的a字段是number,然后我放入的数字是类似年月的形式,但是是number型的表示数字表示日期。另外,从count也可以看到,此次insert的数据基本均匀,每个值基本在250个左右。

我们收集下统计信息,看看统计信息中的信息:

这里用了个函数display_raw。不是oracle自带的,其定义如下:

我们可以看到在统计信息中A列的最小值(DISLAY_LOW_VAL)是20130101,最大值(DISPLAY_HIGH_VAL)是20131230。

我们来模拟下故障场景,先用a=20130403查询,注,这是在最大值和最小值范围内的。

可以看到估算的值rows是275,是在我们的实际值277行附近,是符合实际情况的。

然后我们继续insert数据,insert跨年的数据,但不收集统计信息:

我们查询一个跨年的数据,就出现错误的估计值,rows为1行,这是错误的估算值,且由于认为才1行,所以选择了走索引:

我们如果测试不同的a的值,可以大致得到rows的估计值为如下曲线:

我们可以看到,在超过边界值20131230之后,rows的值就缓慢下降直至变成1。这个下降是个缓慢的过程,且和10g的算法不同,当超过边界时,10g的算法是density=1/(2*NUM_ROWS)(可见Jonathan Lewis’s 的文章),11g的下降过程平缓的多。

解决方法:收集统计信息,或者将a字段改成date型,因为20131230和20140101虽然在日期上只是相隔了一天,但是在数值上却隔了8871,在算out of range的值的时候,很容易就到了距离边界值很远的地方。

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据