SQL优化最佳实践:构建高效率Oracle数据库的方法与技巧
上QQ阅读APP看书,第一时间看更新

 

2.2 成本

在对SQL语句进行优化的过程中,对于成本的理解非常重要。因为Oracle绝大多数情况下就是使用基于成本的优化器对SQL语句制定执行计划的。只有对成本有更深层次的认识,才能理解优化器的行为,也更容易找出产生较差执行计划的原因。但对于成本及其计算方法,Oracle公司并没有开放很多资料,因而只能从一些公开的资料揣摩其工作原理、计算方法等。

下面会对成本的基本概念、计算方法加以简单说明。后面会结合一个SQL案例,阐述如何计算一个成本。最后,会对一个比较重要的概念——选择率加以说明。

 

2.2.1 基本概念

成本是指花费在单数据块读取上的时间,加上花费在多数据块读取上的时间,再加上所需的CPU处理时间,然后将总和除以单数据块读取所花费的时间。也就是说,成本是语句的预计执行时间的总和,以单数据块读取时间单元的形式来表示。

成本的概念也是在不断演化中的,在不同的Oracle版本中是不同的。在Oracle 8i的版本中,成本是考虑了I/O子系统所做的请求数,并没有考虑到CPU资源的使用开销以及多数据块访问和单数据块访问的不同。在Oracle 9i中,引入了对CPU成本的计算,此外也加入了对单数据块和多数据块I/O请求的不同的考虑。到了Oracle 10g,又引入了对数据分布特征、缓存数据块等因素的考虑。

 

2.2.2 计算公式

成本的具体计算公式如下:

Cost=(#SRDs*sreadtim+#MRDs*mreadtim+#CPUCycles/cpuspeed)/sreadtim

公式说明:

  • #SRDs:单数据块读取的次数。
  • #MRDs:多数据块读取的次数。
  • #CPUCycles:CPU时钟频率。
  • sreadtim:随机读取单数据块的平均时间,单位为毫秒。
  • mreadtim:顺序读取多数据块的平均时间,也就是多数据块平均读取时间,单位为毫秒。
  • cpuspeed:代表有负载CPU速度,CPU速度为每秒钟CPU周期数,也就是一个CPU一秒能处理的操作数,单位是百万次/秒。

 

2.2.3 计算示例

下面通过一个例子,说明如何通过上述公式计算一条SQL语句的运行成本。在此特别强调一下,成本的计算非常复杂,Oracle官方也没有公布其具体的算法。在计算中,受影响的因素也比较多。下面的示例,仅仅作为一个参考,简单描述了计算过程。

下面的示例是在Oracle 10gR2的版本中进行的,此版本的成本计算中既包含了I/O成本,也包含了CPU成本。下面的计算中就包含了两个部分的计算过程。

(1)准备工作:

create table t1 as select * from dba_objects
exec dbms_stats.gather_table_statsownname=>'HF'tabname=>'T1'estimate_percent=>100);
//创建了一个测试表

(2)优化器计算成本:

select * from t1
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost %CPU| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 51054 |  4636K|   200   1| 000003 |
|   1 |  TABLE ACCESS FULL| T1   | 51054 |  4636K|   200   1| 000003 |
--------------------------------------------------------------------------
//对于上述这条SQL语句,优化器采用了全表扫描的执行方式,其估算的成本为200

(3)10053 Trace:在开始计算之前,先对上述SQL语句进行一次10053的Trace。通过这个跟踪事件可以观察到CBO是如何选择执行计划的。关于这个跟踪事件的具体用法,可参见本书后面的讲解。在后面的计算过程中,我们可以参看这个跟踪事件的输出。

alter session set events '10053 trace name context forever'
select * from t1
alter session set events '10053 trace name context off'

(4)系统统计信息:先来查看一下计算公式,在公式中指标Sreadtim、Mreadtim、cpuspeed跟具体的物理硬件有关。在Oracle数据库中,可通过收集系统级的统计信息得到相关的数据(关于系统的统计信息,可参看后面的统计信息部分)。如果数据库没有收集相应的信息,则此时处于NOWORKLOAD状态,这种情况下可通过几个新的统计参数折算得到我们需要的指标。

在10053的跟踪事件中,我们可以找到相关的部分:

*****************************
SYSTEM STATISTICS INFORMATION
*****************************
  Using NOWORKLOAD Stats          
  CPUSPEED 1251 millions instruction/sec
  IOTFRSPEED 4096 bytes per millisecond default is 4096
  IOSEEKTIM 10 milliseconds default is 10

从上面输出中可见,这条语句执行时是使用NOWORKLOAD的状态,即此时没有收集系统的统计信息。CPUSEED已经给出,此外还给出另外两个统计参数IOTFRSPEED、IOSEEKTIM。我们所需要的指标可以通过如下关系进行折算。在计算中,还涉及另外两个系统参数:一个是块大小,由db_block_size参数设定,当前系统为8K;另外一个是一次多数据块读取的块数,由db_file_multiblock_read_count参数设定,当前系统为8。

Sreadtim = ioseektim + db_block_size/iotrfrspeed
     = 10 + 8192/4096 = 12
Mreadtim = ioseektim + db_file_multiblock_read_count * db_block_size/iotfrspeed
     = 10 + 8*8192/4096 = 26

(5)对象统计信息:在优化器计算成本时,还需要参考对象级的统计信息。我们可以通过数据字典查看,也可以在10053的Trace文件中找到。在此跟踪输出中,相关部分如下。

***************************************
BASE STATISTICAL INFORMATION
***********************
Table Stats::
  Table T1  Alias T1
    #Rows 51054  #Blks  723  AvgRowLen  93.00
//从上面的输出中可见,表T1的块数为723。对应于全表扫描而言,需要读取7238K的数据块。

(6)计算I/O成本:前面提到过,成本的计算分为两个部分,分别为I/O和CPU。下面简单看一下,I/O的计算过程。前面提到的计算公式如下。

Cost = 
        #SRDs * sreadtim +
        #MRDs * mreadtim +
        #CPUCycles /cpuspeed
         / sreadtim

简单变换一下:

Cost = 
        #SRDs +
        #MRDs * mreadtim/sreadtim +
        #CPUCycles/cpuspeed * sreadtim
        

其中前两行为I/O成本,暂不考虑最后一行,因为这条语句为全表扫描,使用的是多数据块读取的方式,因此,I/O成本计算值考虑到第二行即可。

IO_Cost = #MRDs * mreadtim/sreadtim
       = ceil723/8 * 26 / 12
          = 197.17
//系统总共需要读取723个数据块,每次读取8个块,共需要ceil723/8=91

(7)计算CPU成本:

CPU_Cost = #CPUCycles/cpuspeed * sreadtim
       = 25059861/1251*12000
           = 1.67
//总的CPU处理次数是从10053中得到的,后面会说明。整体CPU成本为1.67

(8)验证成本:下面解读一下10053的成本计算,可和上面我们手工计算的部分进行对比。

***************************************
SINGLE TABLE ACCESS PATH
  -----------------------------------------
  BEGIN Single Table Cardinality Estimation
  -----------------------------------------
  Table T1  Alias T1
    Card Original 51054  Rounded 51054  Computed 51054.00  Non Adjusted 51054.00
  -----------------------------------------
  END   Single Table Cardinality Estimation
  -----------------------------------------
  Access Path TableScan
    Cost  199.67  Resp 199.67  Degree 0     //CPU成本为199.67 - 198 = 1.67
Cost_io 198.00  Cost_cpu 25059861          //IO成本为198
Resp_io 198.00  Resp_cpu 25059861
  Best::AccessPath TableScan
       Cost 199.67  Degree 1  Resp 199.67  Card 51054.00  Bytes 0

从10053可见,优化器计算的I/O成本为198.00(对应于Cost_io)。这一点和计算得到的197.17非常接近。考虑到系统中有隐含参数,计算成本时一般向上取整。可以认为两者就是一致的。对于CPU成本计算,Cost_cpu:25059861就是前边引用的CPUCycles。整体CPU成本为总成本减去I/O成本,即199.67–198=1.67。这和我们前面计算的完全一致。