|
本帖最后由 newkid 于 2017-3-2 10:55 编辑
SQL计划指令
SQL计划指令是根据通过自动重优化学习到的信息所创建出来的。一个SQL计划指令是一些额外的信息, 优化器可用来生成一个更优的执行计划。例如,当发生连接的两个表在连接列有倾斜数据,SQL计划指令可以指引优化器使用动态统计信息来获得更加精确的连接基数估算。
SQL计划指令是在查询表达式之上创建的,而非语句级或者对象级,这样就可确保它们可被应用于多个SQL语句。在一个SQL语句上有多个SQL计划指令也是可能发生的。一个SQL语句所使用的SQL计划指令数目被显示于执行计划下方的备注部分(图14)。
(图14: 一个语句所使用的SQL计划指令数目被显示于执行计划下方的备注部分)
数据库自动维护SQL计划指令,并把它们存储在SYSAUX表空间。任何未被使用的SQL计划指令在53周之后会被自动清除。SQL计划指令也可以通过DBMS_SPD包手动管理(修改或删除),然而,你不可能手动创建一个SQL计划指令。SQL计划指令可以通过视图DBA_SQL_PLAN_DIRECTIVES和DBA_SQL_PLAN_DIR_OBJECTS进行监控(见图15)。
(图15: 查看根据通过自动重优化学习到的信息所创建出来的SQL计划指令)
总共有两种类型的SQL计划指令行:DYNAMIC_SAMPLING(动态取样) 和 DYNAMIC_SAMPLING_RESULT(动态取样结果)。“动态取样”类型会告诉优化器,如果看到了这个特定的查询表达式(例如,在country_id, cust_city, 和 cust_state_province上一起使用的过滤谓词),它就应该使用动态取样来纠正基数估算的偏差。
“动态取样结果”这种类型是在Oracle数据库12c第二版开始才有的,它指明了动态取样查询的结果是保存在SQL指令知识库中(而非Oracle数据库12c第一版所使用的服务器结果缓存)。
(图16: 保存在SQL指令知识库中的动态取样结果,始于Oracle数据库12c第二版)
SQL计划指令同样被ORACLE用来确定扩展统计信息(特别是列群组)是否缺失,是否能够纠正基数估算偏差。一个SQL指令被使用之后,优化器会决定基数估算偏差是否能够用列群组解决。如果是这样的话,它会在下一次收集表统计信息的时候自动创建那个列群组。这个步骤在在Oracle数据库12c第一版中是“始终开启”的,但是从Oracle数据库12c第二版开始,它是通过DBMS_STATS的AUTO_STAT_EXTENSIONS选项进行控制。注意缺省的设置是OFF,所以为了开启自动列群组的创建,你必须执行下列步骤:
EXEC DBMS_STATS.SET_GLOBAL_PREFS('AUTO_STAT_EXTENSIONS', 'ON')
于是如果可能的话,扩展信息就会取代SQL计划指令被使用在SQL计划中(等值谓词,group by分组等等)。如果SQL计划指令已经没必要存在,它会在53周后被自动清除。
(注:关于扩展统计信息的更多信息可见参考文章1“了解Oracle数据库12c的优化器统计信息”)
优化器统计信息
优化器统计信息是描述数据库以及里面的对象的数据的集合。优化器利用这些统计信息来为每个SQL语句选择最佳的执行计划。对于任何一个Oracle系统,为了把性能维持在一个可接受的水平,及时收集适当的统计信息是至关重要的。随着每个新版本的发布,Oracle一直致力于自动提供必要的统计信息。
此处提供的仅是摘要,完整的详细信息请见参考文章1,“了解Oracle数据库12c第二版的优化器统计信息”。
新型的直方图
直方图告诉优化器,数据在一个列中是如何分布的。在缺省情况下,优化器假定在一个列中,数据行是跨越不同的值均匀分布的, 在带有等值谓词查询中,基数的计算方法是将总行数除以等值谓词所用到的列中的不同值的个数。直方图的存在改变了优化器用来确定基数估算的公式,并且允许它生成更精确的估算值。从Oracle 12c第一版开始,多了两种额外的直方图,即顶级频度直方图和混合直方图。它们允许优化器对更为棘手的数据倾斜情形推导出改良的基数估算。在Oracle 12c之前,有两种类型的直方图:频度和等高直方图。
统计信息在线收集
当一个索引被创建,Oracle将自动收集优化器统计信息作为索引创建任务的一部分, 在创建索引所必需的全表扫描以及排序之上,顺便加上统计信息的收集(这从Oracle 9i以来就有了)。从Oracle数据库12c第一版开始,同样的技术也被应用于直接路径操作,例如create table as select (CTAS)和对空表的insert as select(IAS)操作。将统计信息收集搭载为数据加载操作的一部分,意味着在数据加载结束之后,不需要额外的全表扫描就可以立即拥有统计信息。
增量统计信息
在分区表上收集统计信息,包括在表级(全局统计信息)和分区(子分区)级别的收集。如果表分区(或子分区)的数据有任何变动,或者分区被添加或移除,那么全局级别的统计信息就必须被修改,以体现出这种变化,使得分区级别以及全局级别的统计信息之间有对应关系。对于大的分区表,扫描整个表来重构精确的全局级别统计信息可能是非常昂贵的。因此,Oracle数据库11g引入了增量统计信息来解决此问题,从而为表中的每个分区创建了纲要(synopses)。这些数据结构可以被用来推导出全局级别的统计信息——包括不可聚合的统计信息,例如列的基数——而无需扫描整个表。
增量统计信息及其陈化(staleness)
在Oracle 11g数据库中,如果一张表的增量统计被打开,当其中的一个分区的一行数据被修改时,那个分区的统计信息就被认为已经过于陈旧,必需被重新收集才能用于生成全局级别的统计信息。
在Oracle 12c数据库中,一个称为INCREMENTAL_STALENESS的新属性允许你控制分区统计信息何时被认为已陈化,并且不能胜任生成全局统计信息。在缺省情况下,INCREMENTAL_STALENESS被设为NULL, 这意味着一旦有一行数据被修改,分区级统计就被认为已陈化(和11g相同)。
或者,它也可被设置为USE_STALE_PERCENT 或 USE_LOCKED_STATS。USE_STALE_PERCENT的意思是,在相应的分区或子分区中,只要被修改行数的百分比低于STALE_PRECENTAGE属性的值(缺省是10%),分区级统计就会被使用。USE_LOCKED_STATS的意思是如果一个分区上的统计信息被锁定,就会被用于生成全局统计信息,不管这个分区中自从上次收集以来有多少行数据被修改。
增量统计信息以及分区交换加载
分区的好处之一是可以通过分区交换命令轻易地、迅速地加载数据,对业务用户造成最小的影响。分区交换命令允许一张非分区表中的数据被切换到分区表的指定分区中。这个命令并不会物理地移动数据;相反,它只是修改了数据字典,将指针从分区交换到表上,反之亦然。
在以前的版本中,在分区交换的操作过程中,你无法在非分区表上生成必要的统计信息来支持增量统计信息。相反,统计信息只有在交换发生后才能被收集,这是为了确保全局统计信息能够被增量维护。
在Oracle 12c数据库中,必要的统计信息(纲要)可以在非分区表上创建,从而使得在分区交换中被交换的统计信息能够自动被用于增量维护全局统计信息。
更加紧凑的纲要
使用增量统计信息来收集统计信息的性能好处,可能会伴随着纲要所消耗的更高的磁盘存储空间的代价(它们存储在SYSAUX表空间中)。对于具有大量分区和大量列的表,存储空间需求就更高,特别是在不同值(NDV)的数量较多的情况下。除了消耗存储空间,维护非常大的摘要的性能开销也可能变得很可观。 Oracle 数据库12c第二版引入了一种用于收集和存储NDV信息的新算法,这导致纲要缩小了很多,同时保持与先前算法类似的精度水平。
并发统计信息
在Oracle 11g数据库中,统计信息的并发收集被引入。当全局统计信息的收集属性CONCURRENT被设置,Oracle会利用作业调度器和高级队列部件来为每一个对象(表或者分区)创建和管理一个并发的作业。
在Oracle 12c数据库中,统计信息的并发收集被强化以更好地利用每个调度的作业。如果一个表,或分区,或者子分区很小或者是空的,数据库可能自动将这个对象以及其他的小对象整批放进一个单独的作业来降低作业维护的开销。
列群组的自动侦测
扩展统计信息是在Oracle 11g中被引入的。它们有助于优化器改善SQL语句的基数估算的精确性,如果这个语句涉及到包含函数的列(例如UPPER(LastName)),或者在过滤谓词、连接条件、分组键中用到的来自同一个表的多个列。虽然扩展统计信息极其有用,但要得知何种扩展信息应该被创建是很困难的,如果你对应用或者数据集不熟悉的话。
列群组的自动侦测,会自动根据给定的工作负载确定一张表上需要哪些列群组。列群组的自动侦测是一个简单的三步骤的过程。
(关于列群组的创建请参见“了解Oracle数据库12c的优化器统计信息”白皮书)
DBMS_STATS包中的新的报告子程序
对于任何一个系统,为了维持一个可接受的性能水平,知道何时收集、如何收集及时的统计信息是至关重要的。想要确定一个环境中正在执行的是什么样的统计信息收集操作,对统计方法的变动将会如何影响系统,这可能是一项困难且耗时的任务。
在Oracle 12c数据库中, 新的报告子程序被加入到DBMS_STATS包中,使得我们更易于监控何种统计收集活动正在进行,以及对这些操作的参数进行修改会有什么影响。这些DBMS_STATS 子程序是REPORT_STATS_OPERATIONS, REPORT_SINGLE_STATS_OPERATION 和 REPORT_GATHER_*_STATS。
图17演示了一个来自REPORT_STATS_OPERATIONS函数的输出例子。报告显示出详细的信息,在一个指定的时间窗口发生了哪些统计信息收集操作。它给出了详细信息,关于每个操作合适发生,状态如何,覆盖了多少个对象,这可以用文本或者HTML格式显示。
(图17: 统计信息操作的报告)
优化器统计信息顾问
众所周知,劣质统计会导致查询性能问题。 识别陈旧的、过时的统计信息和缺失的统计信息相对比较容易,但低质量的统计信息可能就难以识别:例如表和索引之间的不一致,主键/外键关系等等。
统计信息中的不一致通常是不遵循推荐方法的结果,但要严格遵守这些方法有时候也不容易,原因有多种。 例如,Oracle不断增强统计信息收集功能,但升级后这些增强功能可能会被忽视(一个很好的示例是关于使用AUTO_SAMPLE_SIZE而非固定百分比的建议)。 DBA可能使用旧脚本手动收集统计信息,不愿意更改“已被证明”的过程。 有时,统计信息收集可能被忽视,在批处理期间可能得不到维护,并且在批处理窗口中可能会令人感觉时间不足。 还有许多“继承”下来的系统,没有人能理解用于维护统计信息的脚本。
为了解决这些问题,Oracle 数据库 12.2含有一个叫做优化器统计信息顾问(Optimizer Statistics Advisor)的新功能。 顾问的目标是分析如何收集统计信息,检验已收集的统计信息的质量,并检查自动统计信息收集的状态(例如,检查是否成功完成)。为了实现这一点,它根据一套规则对数据字典进行检查。 如果它发现违反规则的例外情况,可能会生成调查结果,而这些调查结果可能随之导致具体的建议。 顾问将会生成一份报告,列出调查结果(伴随相应的“被违反”的规则),然后列出具体建议以纠正这种情况。 最后,这些建议可以使用一组操作来实现。 操作可以采用SQL脚本的方式输出,也可以自动被实施。
完整的详细信息请见参考文章2, "Oracle 12c数据库优化器统计信息收集的最佳实践"
新增的以及强化的优化技术
在查询优化的过程中,Oracle会使用一系列复杂的技术对SQL语句进行变换。查询优化的这个阶段的目标是为了将原来的SQL语句变换为一个语义上等价、但是处理起来更加高效的SQL语句。Oracle 12c数据库引入了几种新的查询优化方法。
从Oracle 数据库12c第一版开始有的:
部分连接取值
部分连接取值是这样一种优化技术,它是在连接顺序的生成过程中被使用的。这种技术的目标是为了避免产生重复的行,如果不用这种技术,这些重复只能在计划中随后用一个DISTINCT操作符来去除。通过在计划中早些用一个内连接(INNER JOIN)或者半连接(SEMI-JOIN)来取代DISTINCT操作符,这一步骤产生的行数将会减少。这应该会使得计划的总体性能得到改善,因为随后的步骤只需在缩小的行的集合上进行操作。这种优化可以应用在如下类型的查询块:MAX(),MIN(), SUM(DISTINCT), AVG (DISTINCT), COUNT (DISTINCT), DISTINCT, 以及UNION, MINUS, INTERSECT 操作符的分支, [NOT] EXISTS 子查询等等。
考虑如下的DISTINCT查询:
SELECT DISTINCT order_id
FROM orders o, customers c
WHERE o.customer_id = c.customer_id
AND order_id < 2400;
(译者注:这个例子举得不好。order_id在orders中应该是唯一的,customer_id在customers表中也应该是唯一的,所以连接之后order_id在结果中也应该是唯一的,DISTINCT完全多余)
在Oracle 11g数据库中,ORDERSh CUSTOMERS之间的连接是一个哈希连接(HASH JOIN),必须在去除重复行的排序发生前被完全取值。
(图18: Oracle 11g数据库的计划要求在ORDERS 和 CUSTOMERS之间的全连接,然后用unique sort去除重复)
有了部分连接取值,ORDERS 和 CUSTOMERS之间的连接被转换为一个半连接,这意味着一旦在CUSTOMERS表中找到一个匹配的CUSTOMER_ID,查询就会转移到下一个CUSTOMER_ID。通过将哈希连接转换为半连接,流入HASH UNIQUE 的行数大大减少,因为相同连接键的重复行已经被去除了。变换过的SQL的计划如图19所示。
(图19: Oracle 12c数据库的计划显示了ORDERS 和 CUSTOMERS之间的半连接,没有产生重复的行)
接受空值的半连接
应用开发者在包含EXISTS子查询的SQL语句中加入IS NULL谓词是不罕见的。加入额外的IS NULL谓词是因为 EXISTS子查询产生的半连接结果会去除具有空值的行,正如内连接(INNER JOIN)所做的一样。考虑如下的查询:
SELECT p.prod_id,s.quantity_sold,s.cust_id
FROM products p, sales s
WHERE p.prod_list_price > 11
AND p.prod_id = s.prod_id
AND (s.cust_id IS NULL
OR EXISTS (SELECT 1
FROM customers c
WHERE c.cust_id = s.cust_id
AND c.country_id = 'US'
)
);
这里的假定是在s.cust_id 列上可能有空值,而我们想要返回那些行。在Oracle 12c数据库之前,EXISTS子查询无法被展开,因为它出现在一个带有IS NULL谓词的OR谓词(析取谓词)中。因为子查询无法被展开,导致不理想的计划被产生,子查询被作为过滤操作应用在SALES和PRODUCTS表的连接之后。
(图20: Oracle 11g数据库的计划显示EXISTS 自查询被作为过滤操作应用于连接之后)
在Oracle 12c数据库中,一种新型的半连接被引入,称为接受空值的半连接。这种新连接扩展了半连接的算法,在连接的左边的表的连接列上检查空值。在这个例子中检查会发生在s.cust_id。如果列包含空值,那么相应的SALES表上的行被返回,否则半连接被执行以确定该行是否满足连接条件。接受空值的半连接计划如下图21所示。
(图21: Oracle 12c数据库的计划显示 EXISTS子查询被展开,在customers和sales之间使用了接受空值的半连接)
|
|