SQL 优化(Optimize)和调优(Tuning)是从不同阶段和不同角度来保证 SQL 运行性能的方法。前者指的是在 SQL 运行之前获得代价最低的执行计划以保证性能;而后者通常是指到 SQL 运行出现性能问题后,所采取的方法和手段进行补救,解决性能问题。显然,我们更希望语句在运行之前就能确保其性能,而不是等到发生问题后再救火。但是,作为 DBA 或者开发者来说,我们需要既能使 SQL 尽量能获得最佳的执行路径(或者说执行计划),避免性能问题的发生,又要具备在发生性
能问题后尽快定位和解决问题的能力。自从 9i 以来,Oracle 引入了多个特性,在这两个方面为 DBA和开发者提供多种方法对语句进行优化和调优。我们将在本章节为读者介绍这些方法。
管理统计数据:统计数据是基于成本优化器(CBO)选择最佳执行计划的基本支持数据。有效的管理各个层面的统计数据,使得它们能够确切地反映当前的系统负载、对象数据数据及分布情况, 使保证优化器在解析执行计划时获得最佳性能执行计划的基本前提条件。我们在前面章节“CBO 代价估算”的“统计数据”中已经为读者详细介绍了统计数据的管理方法,因此,我们这里不再累述。
8.1 存储概要
我们知道,任何一条 SQL 在执行时都需要打开一个共享的子游标,该游标的内存堆(heap)中包含了相关的控制信息(heap0)以及解析的执行计划及其上下文(heap6)。而在系统的运行过程 当中,可能会出现各种情况使得子游标失效或者部分(如 heap6 占用的内存被重新分配给其他游标) 失效。此时,优化器需要重新解析语句的执行计划。当此时的解析环境与之前的解析环境并不一定 相同,例如相关解析语句的用户、统计数据发生改变,使得语句获得不同的执行计划。这就会相应 地造成语句运行的不稳定性。
许多情况下,我们期望优化器能够根据环境的改变帮助语句获得最优的执行计划,例如,表数据发生较大变化后,系统重新收集其统计数据,帮助相关的语句改变执行计划、获得与当前数据量 和数据分布相应的最佳性能。但是,有时候这种改变并不能获得我们期望的效果,反而可能会使语 句获得一个更差的执行计划。例如,我们在对一些数据量非常大的表做统计数据更新时,通常是通 过采样而不是完全扫描整张表的方式来获取其最新统计数据的。而采样方式是有随机性的,获得统 计数据不会与实际数据完全一致。而这种细微的差别可能会造成优化器不能选择到语句(尤其是复 杂语句)的最佳执行计划。为了避免这种执行计划不稳定性造成的性能影响,数据库管理员在能确 认语句的某个执行计划是其稳定的、性能最优的执行计划的情况下,可以通过使用存储概要将语句 该执行计划固定下,使得优化器在解析语句时不受环境影响、生成该执行计划。
提示:通过存储概要固定执行计划的方式不够灵活,在存储概要生效的情况下,优化器始终会生成 该存储概要所指示的执行计划。在 11g 当中,Oracle 引入了执行计划基线这一特性,它使优化器能更加灵活地选择执行计划。因此,在 11g 当中,Oracle 推荐用户使用执行计划基线而不是存储概要。
8.1.1 什么是存储概要
存储概要实际上就是一组优化器提示(Hint),强制优化器在解析执行计划时按照提示所给出的数据或指定的行为来生成、选择执行计划。优化器根据存储概要所生成的执行计划的稳定性也就 依赖于存储概要中的提示的数量多少。例如,对于一个复杂的 SQL 语句,我们可以通过提示仅指定其中一张表的数据访问通过全表扫描进行,优化器在生成、选择执行计划时,就会针对这种表强制 生成相应的访问路径(Access Path),而对其它表的访问方式以及关联方式等行为则还是已经当前的优化环境、统计数据等信息生成代价最少的执行计划。
使用存储概要的一个优势在于:它可以在不修改应用程序的前提下,改变语句的执行计划,改 善应用性能。
SQL“提示”是一段嵌入在 SQL 语句当中的注释,它不会改变语句的逻辑结果,但是会强制
SQL 引擎在解析和执行语句时按照特定的方式进行。一个提示可能属于多个 SQL 特性。
但是,并非所有 SQL“提示”可以作为存储概要提示来影响优化器生成执行计划。在 11g 中,
Oracle 提供了一个视图 VSQL_HINT,其中的 VERSION_OUTLINE 字段指示该提示是在哪个版本当中的存储概要中开始其作用。例如,我们通过以该视图所依赖的固定表为基础创建的类似视图,可以 查询到这些存储概要提示及其所属的 SQL 特性:


存储概要可以分为多个分类(Category),默认分类为“DEFAULT”。在每个分类下,一条 SQL 语句最多只有一个存储概要,且每条语句起作用的只有当前会话指定分类下的存储概要。存储概要 分为公有存储概要和私有存储概要。公有存储概要可以对所有会话中的特定语句起作用,其数据存 储在 Schema OUTLN 当中的物理数据字典表 OL、OLHINTS 和 OLNODES 当中;而私有存储概要只对当前会话中的语句起作用,其数据存储在 Schema SYSTEM 的临时表 OL、OLHINTS 和 OL$NODES 当中,会随着会话的结束而消失。
通常,我们利用私有存储概要对语句进行调优:在调优会话中,通过修改私有存储概要临时表 中的数据改变目标语句执行计划,在观察其性能,得到满意结果后再将数据写入 OUTLN 的数据字典表当中。这样做既不会在数据字典中产生不必要的垃圾数据、也不会影响的其他会话的语句。
注意,在 9i 当中,没有创建临时表的公共同义词,非 System 用户使用私有存储概要时,需要手工或者调用函数 dbms_outln_edit.create_edit_tables 创建一套相同的临时表(或者手工创建同义词并赋予用户对 System 的临时表的访问权限)。
通过视图 DBA/ALL/USER_OUTLINES、DBA/ALL/USER_OUTLINE_HINTS 可以查看当前所有的或用户自己创建的公共存储概要及其提示。通过公共同义词(Public Synonym)OL、OLHINTS 和
OL$NODES 则可以查看当前会话当中的私有存储概要。
8.1.2 创建存储概要
创建公共存储概要的方式分为三种。第一种是通过在系统或会话级别指定参数
CREATE_STORED_OUTLINES 使系统自动将为前系统或会话中所有被解析的语句产生存储概要数据, 并存储导指定分类或默认分类当中。当指定 CREATE_STORED_OUTLINES 为一个有效字符串时,存储概要被创建在以该字符串命名的分类下;当指定其为 TRUE 或者 DEFAULT 时,存储概要被创建在默认分类下;当被指定为 FALSE 或为空字符串(’’)时,系统不会创建存储概要。
示例:




8.1.3 管理存储概要
为了有效的管理存储概要,Oracle 提供了两个包:DBMS_OUTLN_EDIT 和 DBMS_OUTLN。
DBMS_OUTLN_EDIT 是一个公共包,所有拥有 Public 角色的用户都可以调用;而调用 DBMS_OUTLN
则需要有对其 EXECUTE 权限或者拥有 EXECUTE_CATALOG_ROLE 角色。下面简单介绍这两个包当中过程和函数:
DBMS_OUTLN_EDIT:
• refresh_private_outline:对私有存储概要的临时表中数据修改后,通过该过程将改动刷 新到缓存当中;
• CHANGE_JOIN_POS:修改私有存储概要中关联顺序;
• generate_signature:为特定语句生成数字签名; DBMS_OUTLN:
• drop_unused:删除为被使用过的公共存储概要数据(提示:在数据字典 OL$中有一个字段 FLAGS 为标识位,其二进制数值的第一位代表该数据是否被使用过,初始为 0, 被使用后会被设置为 1);
• drop_by_cat:删除指定分类下的所有公共存储概要数据;
• drop_extras:删除多余的提示(即数据字典中,提示编号大于存储概要提示总数的所 有提示);
• drop_unrefd_hints:删除不存在存储概要定义的提示;
• drop_collision:删除实际提示数量与存储概要数据中提示总数不符的存储概要;
• update_by_cat:修改公共存储概要的分类名称;
• refresh_outline_cache:将数据字典中存储概要数据刷新到缓存游标当中。
由于存储概要数据都是存储在表当中,我们也可以通过 DML 语句直接修改数据字典数据来对存储概要数据进行维护,但是并不推荐这样做。
此外,我们还可以通过语句 ALTER OUTLINE 和 DROP OUTLINE 来修改和删除存储概要。
• ALTER OUTLINE:重建、修改存储概要名称或所属分类,需要有 ALTER ANY OUTLINE 的权限。其语法为:
ALTER OUTLINE [PUBLIC|PRIVATE]
其中,操作可以为以下选项,并且可以同时存在:
o REBUILD:重建存储概要数据;
o RENAME TO new_name:修改存储概要名称;
o CHANGE CATEGORY TO new_category_name:修改存储概要所属分类;
o ENABLE/DISABLE:激活/禁用某个特定存储概要,以绝对其存储概要分类激活的情 况下,该存储概要能否被使用,默认情况下,新建的存储概要都是激活的。
例如:ALTER OUTLINE tmp_outln REBUILD RENAME TO new_outln_test CHANGE CATEGORY TO OUTLN_SYS_TEST;
• DROP OUTLINE:删除存储概要,需要有 DROP ANY OUTLINE 的权限。其语法为:
DROP OUTLINE
例如:DROP OUTLINE new_outln_test;




