第四章 系统和对象统计信息
4.1包dbms_stats简介
从9i开始,dbms_stats代替analyze,后者仅用于对象统计信息之外的用途,例如,index structure validate,行迁移的统计。
4.2系统统计信息
I/O开销模型(I/O cost model):执行SQL语句所需的数据块读的多少
该方法的主要缺点是认为单块读和多块读开销相当,结果,优化器更多倾向于使用多块读操作,如全表扫描,直到8i,初始化参数optimizer_index_caching和optimizer_index_cost_adj解决了这个问题。但缺省值0和100,仅适用于OLAP环境,而不是常用的OLTP环境。
到了9i,产生了一种新的CPU开销模型(CPU cost model),它除了考虑I/O的多少之外,还考虑I/O子系统的性能。必须提供系统统计信息才可以使用CPU开销模型,系统统计信息包括:
I/O子系统的性能;
CPU的性能;
9i缺省没有系统统计信息;10G缺省就有(但缺省的值不一定是合适的),除非SQL提示中指定no_cpu_costing,否则优化器都是使用CPU开销模型。或者使用隐含初始化参数:_optimizer_cost_model值为io时,指定使用I/O开销模型。
系统统计信息包括非工作量统计信息和工作量统计信息两种,前者是人工基准测试(自动模拟工作负载),后者使用应用程序基准测试(以实际的工作负载为准)。系统统计信息存放在aux_stats$表中。通过执行dbms_stats.gather_system_stats来进行收集。
一个数据库只有一套该信息,RAC系统所有实例使用同一个系统统计。
系统统计信息的状态和时间:
select pname,pval2 from aux_stats$ where sname='SYSSTATS_INFO'
PNAME PVAL2
DSTART 10-24-2009 14:15
DSTOP 10-24-2009 14:35
FLAGS
STATUS COMPLETED
STATUS 值为Badstats表示收集过程有错,这种情况下优化器不会使用这样的统计信息。
系统统计信息的结果:
select pname,pval1 from aux_stats$ where sname='SYSSTATS_MAIN'
PNAME PVAL1
CPUSPEED 1265 MHZ
CPUSPEEDNW 484.974958263773 每个CPU每秒钟处理的操作数(百万次)
IOSEEKTIM 10 平均磁盘寻道时间,缺省为10毫秒,本机为12.06
IOTFRSPEED 4096 平均每毫秒磁盘传输的字节,缺省为4096,实际上远不止这个数,本机7200转的笔记本硬盘,值为41248
MAXTHR
MBRC
MREADTIM
SLAVETHR
SREADTIM 4.423
非工作量统计信息的收集(注意,可能有时需要执行多次才生效):
exec dbms_stats.gather_system_stats(gathering_mode => 'noworkload');
由于使用人工基准测试产生负载来衡量系统性能,所以应在相对空闲的时间执行,约1分钟内完成。
10G开始,非工作量统计信息是不能删除的,即使删除,数据库下次启动时会自动收集。
9i上,即使收集了,也不在数据字典aux_stats$中存储,只是显示状态为noworkload;
工作量统计信息的收集,要利用正常业务的工作负载来评估I/O性能,必须显示的收集后才有统计数据,才可用。它分为三个步骤:
1. 执行快照,并存储初始值到aux_stats$中(sname为sysstats_temp)
exec dbms_stats.gather_system_stats(gathering_mode => 'start');
2. 等待有代表性的业务运行,建议至少30分钟
select count(产地) from yhis.药品收发记录 --多块读
select * from yhis.病人信息 where 病人id=110 --单块读
3. 第二次快照
手工停exec dbms_stats.gather_system_stats(gathering_mode => 'stop');
自动停exec dbms_stats.gather_system_stats(gathering_mode => 'interval',interval => 30);
4. 根据两次快照的差值,产生系统统计信息。
下面的笔记本上的统计信息
|
PNAME |
PVAL1 |
|
|
CPUSPEED |
1392 |
单位MHZ, 只是一个基准线操作的内部校准 |
|
CPUSPEEDNW |
781.577 |
每个CPU每秒钟处理的操作数(百万次) |
|
IOSEEKTIM |
12.06 |
|
|
IOTFRSPEED |
41248 |
|
|
MAXTHR |
系统最大IO吞吐量(字节/秒) |
|
|
MBRC |
14 |
一次多块读,平均读取块数 |
|
MREADTIM |
8.656 |
多块数据平均读取时间,毫秒 |
|
SLAVETHR |
并行处理从属线程的平均IO吞吐量(字节/秒) |
|
|
SREADTIM |
4.421 |
单块数据平均读取时间,毫秒 |
下面是某三甲医院的统计信息
|
CPUSPEEDNW |
1107.385 |
|
IOSEEKTIM |
3.457 |
|
IOTFRSPEED |
26413.414 |
|
SREADTIM |
1.082 |
|
MREADTIM |
0.557 |
|
CPUSPEED |
1119 |
|
MBRC |
15 |
|
MAXTHR |
444416 |
|
SLAVETHR |
为了收集到有代表性的统计信息,可连续多天收集后取平均值,使用手工设定,调用过程dbms_stats.set_system_stats来进行。
下面是通过模拟工作负载来收集工作负载统计信息的方法(执行需要3-5分钟):
Create Or Replace Procedure Oltp_Style As
l_Rec Yhis.住院费用记录%Rowtype;
l_n Number;
Begin
For I In 1 .. 10000 Loop
l_n := Trunc(Dbms_Random.Value(2, 1000000));
Begin
Select * Into l_Rec From Yhis.住院费用记录 Where ID = l_n;
Exception
When Others Then
Null;
End;
End Loop;
For I In 1 .. 3 Loop
Select Count(年龄) Into l_n From Yhis.病人信息;
End Loop;
End;
/
exec dbms_stats.drop_stat_table( user, 'SYSTEM_STATS' );
exec dbms_stats.create_stat_table( user, 'SYSTEM_STATS' );
exec dbms_stats.delete_system_stats;
declare
n number;
begin
oltp_style;
dbms_job.submit( n, 'oltp_style;' );
dbms_job.submit( n, 'oltp_style;' );
dbms_job.submit( n, 'oltp_style;' );
commit;
dbms_stats.gather_system_stats( gathering_mode => 'START',
stattab => 'SYSTEM_STATS',
statid => 'OLTP' );
select count(*) into n from user_jobs where what = 'oltp_style;';
while ( n > 0 )
loop
dbms_lock.sleep(5);
select count(*) into n from user_jobs where what = 'oltp_style;';
end loop;
dbms_stats.gather_system_stats( gathering_mode => 'STOP',
stattab => 'SYSTEM_STATS',
statid => 'OLTP' );
end;
/
alter system flush shared_pool;
begin
dbms_stats.import_system_stats
( stattab => 'SYSTEM_STATS', statid => 'OLTP', statown => user );
end;
/
select * from sys.aux_stats$;
系统统计信息对优化器的影响
CPU开销的计算,10.2开始,计算访问一个列的开销:
Cpu_cost=column_position*20
即CPU开销和列的位置相关,每往后一列,增加20(所以,列的位置会影响SQL性能)
验证脚本:
SET ECHO ON
DROP TABLE t;
DELETE plan_table;
CREATE TABLE t (c1 NUMBER, c2 NUMBER, c3 NUMBER,
c4 NUMBER, c5 NUMBER, c6 NUMBER,
c7 NUMBER, c8 NUMBER, c9 NUMBER);
INSERT INTO t VALUES (1, 2, 3, 4, 5, 6, 7, 8, 9);
execute dbms_stats.gather_table_stats(user,'t')
EXPLAIN PLAN SET STATEMENT_ID 'c1' FOR SELECT c1 FROM t;
EXPLAIN PLAN SET STATEMENT_ID 'c2' FOR SELECT c2 FROM t;
EXPLAIN PLAN SET STATEMENT_ID 'c3' FOR SELECT c3 FROM t;
EXPLAIN PLAN SET STATEMENT_ID 'c4' FOR SELECT c4 FROM t;
EXPLAIN PLAN SET STATEMENT_ID 'c5' FOR SELECT c5 FROM t;
EXPLAIN PLAN SET STATEMENT_ID 'c6' FOR SELECT c6 FROM t;
EXPLAIN PLAN SET STATEMENT_ID 'c7' FOR SELECT c7 FROM t;
EXPLAIN PLAN SET STATEMENT_ID 'c8' FOR SELECT c8 FROM t;
EXPLAIN PLAN SET STATEMENT_ID 'c9' FOR SELECT c9 FROM t;
SELECT statement_id, cpu_cost AS total_cpu_cost,
cpu_cost-lag(cpu_cost) OVER (ORDER BY statement_id) AS cpu_cost_1_coll,
io_cost
FROM plan_table
WHERE id = 0
ORDER BY statement_id;
DROP TABLE t;
PURGE TABLE t;
优化器计算总开销的公式:
工作量统计信息
Cost=io_cost+cpu_cost/(cpuspeed*sreadtim*1000)
Sreadtim=单块数据平均读取时间
非工作量统计信息
Cost=io_cost+cpu_cost/( CPUSPEEDNW *sreadtim*1000)
其中sreadtim= IOSEEKTIM+db_block_size/ IOTFRSPEED
mreadtim= IOSEEKTIM+mbrc*db_block_size/ IOTFRSPEED
CPUSPEEDNW=每个CPU每秒钟处理的操作数
IOSEEKTIM=平均磁盘寻道时间
IOTFRSPEED=平均每毫秒磁盘传输的字节,缺省为4096,收集后是它的10倍以上
如果存在工作量统计信息,优化器会忽略非工作量统计信息。
4.3对象统计信息
直方图
频度直方图
桶数:唯一值的数量,最大254,每个桶在user_tab_histogram中存储为一行;
列endpoint_value是number型,非数字型的列进行了一个转换,只取前6个字节。如果前面几个字符相同,则直方图的分布会严重不均衡。
列endpoint_number是累计计数,前去前一行的数,则为当前值的计数。
等高直方图
桶数大于254时,会使用等高直方图,所有的值分为5个段。
等高直方图可能导致错误的估算,引起查询优化器估值不准。
扩展的统计信息
11G以上,考虑到查询中列的相关性,可以收集扩展的统计信息。
Dbms_stats.create_extended_stats
4.3.2收集对象统计信息
1. dbms_stats包
Gather_database_stats:收集整个数据库;
Gather_schema_stats:收集指定模式的所有对象;
Gather_table_stats:收集表(索引可选);
Gather_index_stats:收集索引;
数据字典的对象统计信息(10G以上才提供):dbms_stats.gather_dictionary_stats
数据字典的固定表的特定对象统计信息(10G以上才提供):
dbms_stats.gather_fixed_objects_stats
查询该过程处理了哪些表select * from v$fixed_table where type='TABLE';