只有当谓词条件中使用了未经修改的列值时,上一节中描述的列统计信息和直方图才会起作用。例如,如果使用了谓词country='Switzerland',通过country列上适当的列统计信息和直方图,查询优化器应该能够正确估算它的选择率。这是因为列统计信息和直方图描述的是country列自身的值。
另一方面,如果使用了谓词upper(country)='SWITZERLAND',查询优化器就不再能够直接从对象统计信息和直方图中推断出选择率了。当一个谓词条件引用了多个列时也会出现类似的问题。举个例子,如果将谓词条件country='Denmark' AND language='Danish'应用到一张包含全世界人口信息的表上,则很可能这两个限制条件都应用到了表中大多数记录的相同记录上了。
实际上,大多数讲丹麦语的人生活在丹麦,生活在丹麦的大多数人讲丹麦语。换句话说,这两个限制条件几乎是冗余的。这样的列通常称作关联列(correlated column),并且它们会对查询优化器造成挑战。这是因为没有任何对象统计信息或者直方图描述这样互相依赖的数据,或者换句话说,查询优化器实际上是假设存储在不同列中的数据没有相互依赖关系。
自11.1版本开始,就可以做到基于表达式或者一组列来收集对象统计信息和直方图来解决这样的问题。这些新的统计信息称作扩展统计信息。这背后其实主要就是根据一个表达式或者一组列来创建一个叫作扩展信息的隐藏列。然后就在这个隐藏列上收集对象统计信息和直方图。
这个概念通过dbms_stats包的create_extended_stats函数来实现。例如,通过接下来的查询创建两个表达式。第一个是在upper(pad)上,第二个是由列val2和val3组成的一个列组。在测试表中,这些列包含完全一样的值;换句话说,这些列是高度关联的(实际上是完全关联的)。根据定义,如下面要展示的,表达式或这组列必须包含在一对圆括号中。注意,这个函数返回的是由系统生成的扩展信息名称(一个由SYS_STU开头的30个字节的名称):
SELECT
dbms_stats.create_extended_stats(ownname
=> user,
tabname => 'T',
extension => '(upper(pad))') AS ext1,
dbms_stats.create_extended_stats(ownname
=> user,
tabname => 'T',
extension => '(val2,val3)') AS ext2
FROM dual;
EXT1 EXT2
SYS_STUOKSQX64#IO1CKJ5FPGFK3W9 SYS_STUPS77EFBJCOTDFMHM8CHP7Q1
注意 生成扩展信息的这组列不能引用表达式或虚拟列。
显然,一旦扩展信息创建完毕,数据字典就可以提供关于它们的信息。下面的查询基于user_stat_extensions视图,显示了已经存在的测试表的扩展信息。视图同时还有dba、all以及在12.1多租户环境下的cdb版本:
COLUMN extension_name FORMAT A30
COLUMN extension FORMAT A15
SELECT extension_name, extension
FROM user_stat_extensions
WHERE table_name = 'T';
EXTENSION_NAME EXTENSION
SYS_STUOKSQX64#IO1CKJ5FPGFK3W9 (UPPER("PAD"))
SYS_STUPS77EFBJCOTDFMHMBCHP7Q1 ("VAL2","VAL3")
如同在接下来的查询输出中所示,隐藏列和扩展信息的名称相同。还要注意扩展信息的定义是如何添加到列上的:
COLUMN column_name FORMAT A30
COLUMN data_type FORMAT A9
COLUMN hidden_column FORMAT A6
SELECT column_name, data_type,
hidden_column, data_default
FROM user_tab_cols
WHERE table_name = 'T'
ORDER BY column_id;
ID NUMBER NO
VAL1 NUMBER NO
VAL2
NUMBER NO
VAL3 NUMBER NO
PAD VARCHAR2 NO
SYS_STUOKSQX64#IO1CKJ5FPGFK3W9 VARCHAR2
YES UPPER("PAD")
SYS_STUPS77EFBJCOTDFMHM8CHP7Q1 NUMBER YES
SYS_OP_COMBINED_HASH("VAL2","VAL3")
无法创建超过20条扩展信息。如果你想创建超过20扩展信息,那么就会出现如下错误:
ORA-20008:Number of extensions in table xxx
already reaches the upper limit (20)。
警告 因为一组列的扩展统计信息来自一个散列函数(sys_op_combined_hash),所以这些统计信息只能够应用于等价谓词上。换句话说,如果使用了基于类似BETWEEN以及""这样的运算符的谓词条件,则查询优化器无法利用扩展统计信息。一组列的扩展统计信息也可以用于估算GROUP BY条件的基数,并且从11.2.0.3版本开始,也可以用于DISTINCT运算符和select子句。
要删除一个扩展信息,dbms_stats包提供了drop_extended_stats存储过程。在接下来的例子中,PL/SQL代码块删除了之前建立的两个扩展信息:
BEGIN
dbms_stats.drop_extended_stats(ownname
=> user,
tabname => 'T',
extension
=> '(upper(pad))');
dbms_stats.drop_extended_stats(ownname
=> user,
tabname => 'T',
extension
=> '(val2,val3)');
END;
/
完全没有必要因为一件小事就决定哪一组列适合在上面创建扩展信息。下面的方法可以用于11.2.0.2之后的版本中(在脚本seed_col_usage.sql中有完整的例子可供访问)。
(1)调用dbms_stats包的seed_col_usage存储过程来指示查询优化器记录以下信息:WHERE子句中指定的关于谓词的信息,GROUP
BY子句中引用的关于列的信息,以及从11.2.0.3版本开始起select子句中关于DISTINCT运算符的信息。做该记录要么是为了sqlset_name和owner_name参数中指定的SQL调优集的所有SQL语句,要么是为了由time_limit参数指定的以秒为单位的一段时间内进行了硬解析(不需要执行,因此使用EXPLAIN PLAN语句就足够了)的所有SQL语句:
BEGIN
dbms_stats.seed_col_usage(sqlset_name => NULL,
owner_name =>
NULL,
time_limit =>
30);
END;
/
(2)一旦记录过程完毕,就会调用dbms_stats包的report_col_usage函数来报告列的使用情况。每个列的使用模式都被报告出来。例如,在下面的输出中,val1和val2列都是一个基于等值条件的单表谓词的一部分:
SELECT dbms_stats.report_col_usage(ownname
=> user, tabname => 't') FROM dual;
(3)使用dbms_stats包的create_extended_stats存储过程来创建扩展信息。注意扩展信息自身的定义如果不是作为参数来传递,那么定义就从记录过程中存储的信息中获得。因此,只需要模式和表名两个参数。请注意,在下面的例子中如何调用一次create_extended_stats函数就创建三个扩展:
select
dbms_stats.create_extended_stats(ownname=>user,tabname=>'t') FROM dual;
(4)在创建完扩展信息后,重新收集修正过的表的对象统计信息。
在12.1版本中,扩展信息也可以由数据库引擎自动创建。实际上,对于利用统计信息反馈的SQL语句,查询优化器可以创建一个用于通知数据库引擎创建扩展信息的SQL计划指令(SQL plan
directive)。这样,就可以避免将来在统计信息反馈过程中的重新优化。
完整的例子可以在脚本seed_col_usage.sql中找到。这里有两个关键点需要了解。
第一,扩展信息可以创建并且会自动创建。
第二,扩展信息只有在对象统计信息已经收集的情况下才可创建。换句话说,创建SQL计划指令和创建扩展信息之间的时间间隔依赖于对象统计信息收集的频率。
有意思的是,要注意扩展统计信息以另一个特性为基础,这个特性是在11.1版本中引入的,被称作虚拟列(virtual
column)。虚拟列是不存储数据而只简单地通过基于其他列的表达式来生成其内容的列。这在应用程序频繁使用某个给定的表达式时非常有用。典型的例子是,在一个VARCHAR2列上应用upper函数,或者在一个DATE列上应用trunc函数。如果这些表达式的使用非常频繁,那么像下面这样直接在表上定义这些表达式就非常合理了:
DROP TABLE persons PURGE;
CREATE TABLE persons (
name VARCHAR2(100),
name_upper AS (upper(name))
);
INSERT INTO persons (name) VALUES
('Michelle');
SELECT name FROM persons WHERE name_upper =
'MICHELLE';
虚拟列的主要问题是,与扩展统计信息相比,它们会改变某些SQL语句的行为(例如,select *语句和没有列清单的INSERT语句),除非它们被定义成不可见的(虚拟列的可见性自12.1版本起可设置)。换句话说,因为扩展统计信息是基于隐藏列的,它们对于应用程序来说是完全透明的。
无论虚拟列是如何定义的(不管是通过用户显式定义还是通过扩展统计信息隐式定义),关于它们的对象统计信息和直方图都会正常收集,认识到这一点非常重要。这样一来,查询优化器就获得了关于数据的额外统计信息。
1.1.1.1 SQL计划指令
SQL计划指令是在12.1版本中引入的新概念。它们的用途是帮助查询优化器应对错误的估算。要达到这个目的,SQL计划指令将引起错误估算的表达式信息存储在数据字典中。因为它们并不与具体的SQL语句相关联,所以不仅多个SQL计划指令可以同时应用于一个单独的SQL语句,而且一个单独的SQL计划指令也可以应用于多个SQL语句。
在某些情况下,SQL计划指令通知数据库引擎自动创建扩展统计信息(明确地说,列组)。如果无法创建扩展统计信息,则会通知查询优化器使用动态采样。
当初始化参数optimizer_adaptive_features的值为TRUE(即默认值)时会启用SQL计划指令。激活SQL计划指令时,数据库引擎会自动维护(例如,创建和清除)SQL计划指令。一些管理操作也可以通过dbms_spd包手动执行。
可用的SQL计划指令信息可以通过dba_sql_plan_directives和dba_sql_plan_dir_objects视图来查询(这些视图的cdb版本也可以使用)。