一、现象
开发人员反馈同一条sql,sql中where条件in的值的个数不同,执行效率差异巨大。以下是截取的sql的一部分,sql中in的值的个数为为2个或3个时执行时间超过40s,in的值的个数为为1个或大于3个时不到1秒就可以返回执行结果。
二、原因分析
看到这种现象,最开始怀疑是数据库表的统计信息存在过期的可能,尝试通过绑定历史执行计划解决这个问题。但是发现,in的值个数不同的sql,其sql_id是不一样的,说明并不是统计信息过期导致执行计划出了问题,每个sql_id的历史的执行计划也只有一个,无法通过绑定历史执行计划解决。因此,我们可以先对比两个sql的执行计划,看看有什么差异,能否通过hint的方式指定执行计划。可以看到两个执行计划的差别主要在第九个算子,一个是hash join,一个是nestloop join,走hash join快得多。在SQL的子查询里面加hint /*+ USE_HASH(A TAB1) */ 强制走hash join, 原来需要执行47秒的sql不到1s就执行完了。
三、问题解决
虽然在SQL语句里加hint可以解决这个问题,但需要应用去改SQL,无法立即优化。对于已上线的业务,可以通过对SQL创建outline实现在线执行计划绑定。创建outline是指将一组Hint加入到SQL中,从而使优化器根据指定的一组Hint,对该SQL生成更优计划。有两种方式可以创建outline,一种是根据sql_id创建,一种是通过sql_text创建。实际应用中由于大多使用了绑定变量,所以一般通过sql_id绑定outline执行计划,其中sql_id可以通过gv$plan_cache_plan_stat视图获取,也可以直接通过ocp的sql诊断功能获取。本案例绑定语法如下例所示:
CREATE OUTLINE otl_idx_c2 ON 'ED570339F2C856BA96008A29EDF04C74' USING HINT /*+ USE_HASH(A TAB1) */;
但实际上创建了outline后并没有生效,这是因为之前在sql中加的hint在子查询中,而创建outline无法指定所加hint的位置。
在OB中,Outline Data是优化器为了完全复现某一计划而生成的一组Hint信息,因此我们可以直接获取执行效率高的执行计划的Outline Data,通过explain extended命令获取执行计划,会把执行计划的Outline Data显示出来。这样我们就可以使用现成的Outline Data直接绑定执行计划:
绑定完成后,再次执行SQL,之前47秒的SQL现在不到1秒就执行完了。