内容


比较实时基数和优化器的基数估计值

帮助优化查询的一种工具

Comments

本文适用于 Linux®、UNIX® 和 Windows® 上的 DB2 UDB。

简介

DBA 必须知道如何通过观察 EXPLAIN 结果 来检查和定位访问计划中的问题。有时候客户报告的性能问题中说某个特定查询的执行没有优化。出现这种情况可能有多种原因。从数据库的角度来看,统计信息可能过时,或者瓶颈来自锁定、缓冲池争用、排序等其他方面。从操作系统或硬件的角度来看,可能是 CPU、I/O 或有关网络因素造成了瓶颈。本文假设已经排除了所有其他可能性,如操作系统、硬件或 DB2 的其他性能领域等,问题完全缩小到访问计划上。

本文说明如何收集访问计划中特定操作的实际执行次数(实时基数),以便和 DB2 优化器中计算的估计基数进行比较。出现这类差异的原因包括统计相关和数据分布。DBA 可使用本文介绍的技术确定环境中容易出现问题的地方。因为在支持部门工作,我们常常要求客户收集特定操作的执行次数来了解访问计划中的瓶颈、优化器的估计是否准确以及如果不准确应如何改进。

优化器根据可用的统计信息计算基数的估计值,即预计查询返回的行数。必须保持统计信息是最新的,并尽可能收集更多关于表和统计的信息。

本文中的例子说明了如何确定特定操作返回的实时行数,帮助确定分析的方向。要注意,不应该把研究的范围仅限于计划分析,除非很明显所选的访问路径是次优的。

查看访问计划

查看访问计划有三种方法。本文主要介绍使用 db2exfmt 工具。使用该工具前必须创建数据库的 EXPLAIN 表。

为了更好地理解访问计划,必须了解其主要成分:

  • 基数
  • <PLAN OPERATOR>
  • (PLAN OPERATOR ID)
  • CPU 开销
  • I/O 开销

我们分析一下这些成分在访问计划上下文中的含义:

  • 基数(Cardinality) —— 应用筛选操作(比如使用断言应用程序或聚合)之后,优化器估计符合条件的总行数。
  • PLAN OPERATOR —— 操作类型的描述性标签,如 NLJOIN、HSJOIN、TBSCAN、IXSCAN 等等。
  • PLAN OPERATOR ID —— 计划操作在查询中的惟一 ID。
  • CPU 开销 —— 按照访问计划执行到(并包括)该计划操作预计的累积总开销(按时间)。
  • I/O 开销 —— 按照访问计划执行到(并包括)该操作预计的累积总 I/O 开销(按数据页 I/O 计算)。

清单 1 显示了 db2exfmt 输出的一个例子,其中包括上述这些成分:

清单 1. db2exfmt 输出中的访问计划成分
16.64    <-	(a) Cardinality
IXSCAN   <-	(b) PLAN OPERATOR
(   2)   <-	(c) PLAN OPERATOR ID
0.117254 <-	(d) CPU Cost
 0.02    <-	(e) I/O Cost
   |

在分析访问计划时,可能会发现与集的基数不像希望的那样精确。您可能希望计算实际的基数,而不使用访问计划中的估计基数。比如:

清单 2. 访问计划片段
		4.85057
		 FETCH
		(   9)
		15.6981
		 3.2039
	   /--------+--------\
        4.85057	   	  422
        IXSCAN	      TABLE: SKAPOOR
        (  10)	           TEST1
        0.234466
           0
	  |
          422
       INDEX:SKAPOOR
	TESTIND

清单 2 中的访问计划片段表明 IXSCAN 操作估计将返回 4.85057(约 5)行。但是,如果怀疑结果应该少于或者多于 5 行,您可以确定 “实际基数” 并将其与访问计划中的 “估计基数” 比较。为此需要查看 PLAN OPERATOR ID 确定的 PLAN OPERATOR 细节。在上面的计划片段中,IXSCAN 的 OPERATOR ID 是 (10)。在访问计划的详细操作部分找到这个操作 ID,并隔离应用于该操作(即 IXSCAN)的断言。比如:

清单 3. PLAN OPERATOR 细节
10) IXSCAN: (Index Scan)
		Cumulative Total Cost: 		0.234466
		Cumulative CPU Cost: 		129660
		Cumulative I/O Cost: 		0
		Cumulative Re-Total Cost:	 	0.170341
		Cumulative Re-CPU Cost:	 	94198.8
		Cumulative Re-I/O Cost: 		0
		Cumulative First Row Cost: 		0.21937
		Estimated Bufferpool Buffers: 	1
		Arguments:
		---------
		MAXPAGES: (Maximum pages for prefetch)
			ALL
		PREFETCH: (Type of Prefetch)
			NONE
		ROWLOCK : (Row Lock intent)
			NEXT KEY SHARE
		SCANDIR : (Scan Direction)
			FORWARD
		TABLOCK : (Table Lock intent)
			INTENT SHARE
		Predicates:
		----------
		30) Start Key Predicate
			Relational Operator: 	Equal (=)
			Subquery Input Required: 	No
			Filter Factor: 		0.0114943
			Predicate Text:
			--------------
			(Q2.NAME = 'PAR') 
		30) Stop Key Predicate
			Relational Operator: 	Equal (=)
			Subquery Input Required: 	No
			Filter Factor: 		0.0114943
			Predicate Text:
			--------------
			(Q2.NAME = 'PAR')

得到应用于 IXSCAN(10) 的断言后,可以创建 SQL 语句以获得应用于该操作的断言的实时数:

清单 4. 得到实时计数的 SQL 语句
select count(*) from skapoor.test1 Q2 where Q2.NAME = 'PAR';

Q2 是该查询改写的表名。(例子 一节将更详细地讨论。)

如果由于某种原因,发现上述查询的基数明显不同于估计的基数,那么可能是因为统计信息已经过期甚至没有, 可能需要一个 RUNSTATS。当然也可能是由于其他原因造成的,比如 RUNSTATS 没有收集足够的分布统计信息。(要增加分布统计信息,不一定要改变数据库配置(num_freqvals, num_quantiles)。只需要指定 RUNSTATS 命令的频率值和分位数。)

指导原则

收集 count(*) 查询时,首先要确定对给定的 SQL 语句需要多少次数。下面是收集计数时可以遵循的一些基本原则。

  1. 收集不依赖于连接(JOIN)的基本访问(表的索引扫描和表扫描)的计数。还要注意,虽然获取单个局部断言的独立计数很有用,但有时候独立于局部断言获取连接断言的计数也很有用,可用于确定连接中可能的偏差。
  2. 为不改变计划基数的操作分配较低的优先级。可以暂时跳过计划中基数不变的操作。但是,有时候两个相邻操作之间的估计基数不变,而这两个基数不变操作之间的实际基数是变化的,基数不变是由于消除重复或聚合 SORT(DISTINCT, GROUP BY)而造成的。如果优化器在计划时没有必要的信息就会造成这种情况。如果遇到这种异常情况,可以为此收集计数。
  3. 跳过带有冗余断言的操作。如果相同的断言在两个不同的操作中出现两次,可以跳过其中一个。
  4. 使用后序遍历生成查询。就是说从左到右收集操作输入树的 counts(*),然后在收集代表操作本身的根的计数。这个步骤很关键,因为按照这样的顺序允许为创建的每个后续 count(*) 增加断言。

特例

  • 不一定要按照访问计划中的顺序收集计数(比如不同的连接顺序组合,自身独立的断言)。比方说,在调试怀疑连接顺序不是最优的问题时,针对不同的连接组合收集计数可能会有帮助。这种方法可以验证不同的连接组合是否能够更早地过滤掉较多的行。要注意,这种方法可能用到多种组合,因而需要多个 count(*) 查询,最好留到更深入的分析阶段进行,除非已经确定连接顺序就是问题的原因。这个步骤至少需要 3 个连接表(3 个连接表中包含自连接)。
  • 此外,有时候返回的行数非常多,“select count(*)” 查询可能造成算术溢出。这种情况下可使用 “select count_big(*)”。
  • 如果查询的是 MDC 表,必须注意,如果计划中的 FETCH-IXSCAN 组合选择了 MDC BLOCK 索引,通常会看到 FETCH 估计的基数要高于 IXSCAN 上的基数,因为索引扫描基数估计值指的是记录块而不是单个记录。

例子

例 1

环境:Windows DB2 UDB ESE V8.2 fixpak 8 单分区。

访问计划中的数据库和数据库管理程序配置:

注意: 请参阅 “Recreate optimizer access plans using db2look” (developerWorks,2005 年),了解如何设置适当的值。

清单 5. 例 1
Database Context:
----------------
	Parallelism: 		None
	CPU Speed: 		9.446886e-007
	Comm Speed: 		0
	Buffer Pool size: 	250
	Sort Heap size: 	256
	Database Heap size: 	600
	Lock List size: 	50
	Maximum Lock List: 	22
	Average Applications: 	1
	Locks Available: 	1122
Package Context:
---------------
	SQL Type: 		Dynamic
	Optimization Level: 	5
	Blocking: 		Block All Cursors
	Isolation Level: 		Cursor Stability
		Arguments:
		---------
		BLDLEVEL: (Build level)
			DB2 v8.1.8.852 : special_13527
		ENVVAR  : (Environment Variable)
			DB2_HASH_JOIN = no    
		ENVVAR  : (Environment Variable)
			DB2_INLIST_TO_NLJN = yes
		ENVVAR  : (Environment Variable)
			DB2_LIKE_VARCHAR = 6.2
		STMTHEAP: (Statement heap size)
			2048

注意: 该例在 DB2 注册表级上关闭了 HASH JOIN(DB2_HASH_JOIN=NO)。

首先创建示例数据库,如果必要,使用 db2sampl 命令。

然后在 DB2 命令行中输入下面的查询:

清单 6. 从示例数据库中选取行
db2 select name, salary from staff s, org o where
s.dept=o.deptnumb and s.name='Sanders' and s.salary > 10000.00 
NAME      SALARY
--------- ---------
Sanders    18357.50
  1 record(s) selected.

结果显示该查询返回一行数据。

假设该查询的性能很低,需要分析访问计划。生成访问计划,查看优化器对该查询返回的总基数(行数)的估计值。

首先必须创建 EXPLAIN 表:

可以通过运行 SQLLIB\MISC 中的 EXPLAIN.DDL 来创建 EXPLAIN 表,就在安装 DB2 的目录下。

清单 7. 创建 EXPLAIN 表
C:\>db2 connect to sample
C:\>cd <install directory>\SQLLIB\MISC
C:\><install directory>\SQLLIB\MISC\> db2 -tvf EXPLAIN.DDL

创建了 EXPLAIN 表之后,就可以用 EXPLAIN 模式运行下面的查询:

清单 8. 在 EXPLAIN 模式下运行查询
C:\>db2 set current explain mode explain
C:\>db2 select name,salary  from staff s, org o where s.dept=o.deptnumb and
s.name='Sanders' and s.salary > 10000.00 
C:\>db2 set current explain mode no

现在收集 EXPLAIN 输出:

清单 9. 格式化 EXPLAIN 输出
C:\>db2 connect reset
C:\>db2exfmt -d <dbname> -g TIC -w -1 -n % -s % -# 0 -o exfmt.txt

分析 exfmt 输出:

清单 10. exfmt 输出
<snippet>
Original Statement:
------------------
select name,salary 
from staff s, org o 
where s.dept=o.deptnumb and s.name='Sanders' and s.salary > 10000.00 
Optimized Statement:
-------------------
SELECT Q2.NAME AS "NAME", Q2.SALARY AS "SALARY" 
FROM SKAPOOR.ORG AS Q1, SKAPOOR.STAFF AS Q2 
WHERE (+10000.00 < Q2.SALARY) AND (Q2.NAME = 'Sanders') AND (Q2.DEPT = Q1.DEPTNUMB) 
Access Plan:
-----------
	Total Cost: 		26.0872
	Query Degree:		1
              Rows 
             RETURN 
             (   1) 
              Cost 
               I/O 
               |
             2.176  		<- Total Estimated Number of rows.
             NLJOIN 
             (   2) 
             26.0872 
                2 
          /-----+-----\
   0.906667         2.4		<- Estimated Number of rows
     TBSCAN           TBSCAN 
     (   3)           (   4) 
     13.0678          13.0194 
        1                1 
        |                |
       68               60 
 TABLE: SKAPOOR   TABLE: SKAPOOR  
      STAFF             ORG

优化器估计的总行数是 2.176。接近但高于实际的行数。

我们根据前面讨论的 指导原则 来收集计数。

  • 根据原则 (A),TBSCAN(3) 是一个候选项。
  • 没有满足原则 (B) 的情况。
  • 按照原则 (C),可以忽略 TBSCAN(4) 的单独 count 查询,因为解析的连接断言和 NLJOIN(2) 相同。
  • 根据原则 (D),我们得到如下的顺序:

从最左侧、最深的操作开始,在这里就是 NLJOIN(2) 的 OUTER 即 TBSCAN(2),然后 INNER 即 TBSCAN(4),最后是连接断言本身即 NLJOIN(2)。这种情况下首先要查看 (3) 的细节,然后是 (4) 和 (2)。

清单 11. TBSCAN (3)
    0.906667            
     TBSCAN           
     (   3)        
       1                
       |                       
      68               
 TABLE: SKAPOOR   
      STAFF

查看操作 (3) 的细节:

清单 12. 操作 (3) 的细节
	3) TBSCAN: (Table Scan)
		Cumulative Total Cost: 		13.0678
		Cumulative CPU Cost: 		230558
		Cumulative I/O Cost: 		1
		Cumulative Re-Total Cost: 		0.172845
		Cumulative Re-CPU Cost: 		182965
		Cumulative Re-I/O Cost: 		0
		Cumulative First Row Cost: 		13.0653
		Estimated Bufferpool Buffers: 	1
		Arguments:
		---------
		JN INPUT: (Join input leg)
			OUTER
		MAXPAGES: (Maximum pages for prefetch)
			ALL
		PREFETCH: (Type of Prefetch)
			NONE
		ROWLOCK : (Row Lock intent)
			NEXT KEY SHARE
		SCANDIR : (Scan Direction)
			FORWARD
		TABLOCK : (Table Lock intent)
			INTENT SHARE
		TBISOLVL: (Table access Isolation Level)
			CURSOR STABILITY
		Predicates:
		----------
		2) Sargable Predicate
			Relational Operator: 	Less Than (<)
			Subquery Input Required: 	No
			Filter Factor: 		0.333333
			Predicate Text:
			--------------
			(+10000.00 < Q2.SALARY) 
		3) Sargable Predicate
			Relational Operator: 	Equal (=)
			Subquery Input Required: 	No
			Filter Factor: 		0.04
			Predicate Text:
			--------------
			(Q2.NAME = 'Sanders')

我们来看一看其中的断言。操作 (3) 有两个断言:

清单 13. 断言
- (+10000.00 < Q2.SALARY)
- (Q2.NAME = 'Sanders')

注意表名是 Q2。这是因为查询对表重新命名了。看一看优化后的语句:

清单 14. 优化后的语句
Optimized Statement:
-------------------
SELECT Q2.NAME AS "NAME", Q2.SALARY AS "SALARY" 
FROM SKAPOOR.ORG AS Q1, SKAPOOR.STAFF AS Q2 
WHERE (+10000.00 < Q2.SALARY) AND (Q2.NAME = 'Sanders') AND (Q2.DEPT = Q1.DEPTNUMB)

查询中涉及到的两个表被 DB2 重命名为:

清单 15. 重命名后的表
SKAPOOR.ORG AS Q1
SKAPOOR.STAFF AS Q2

为了查看这两个局部断言的实际计数,生成一个 count 查询:

清单 16. 查询 count
SELECT
	COUNT(*)
FROM
	SKAPOOR.STAFF AS Q2
WHERE
	(+10000.00 < Q2.SALARY) AND
	(Q2.NAME = 'Sanders');

如果运行该查询,结果如下所示:

清单 17. 查询结果
1
-----------
          1
  1 record(s) selected.

访问计划估计选中的行数为 0.906667。如果希望进一步看看 TBSCAN(3) 估计的筛选因子是否合理,应该分别对每个断言生成计数。比如:

清单 18. 为每个断言生成筛选因子
SELECT 
	COUNT(*)
FROM
	SKAPOOR.STAFF AS Q2
WHERE
	(+10000.00 < Q2.SALARY);
SELECT 
	COUNT(*)
FROM
	SKAPOOR.STAFF AS Q2
WHERE
	(Q2.NAME = 'Sanders');

这样就可以看到 TBSCAN(3) 分析得到的每个断言的独立筛选结果。可以看到上述每个局部断言估计的筛选因子,比如:

清单 19. 每个局部断言的筛选因子
Predicates:
----------
2) Sargable Predicate
	Relational Operator: 	Less Than (<)
	Subquery Input Required: 	No
	Filter Factor: 		0.333333
	Predicate Text:
	--------------
	(+10000.00 < Q2.SALARY) 
3) Sargable Predicate
	Relational Operator: 	Equal (=)
	Subquery Input Required: 	No
	Filter Factor: 		0.04
	Predicate Text:
	--------------
	(Q2.NAME = 'Sanders')

每个断言的筛选因子表示优化器估计符合断言的数据的比例。

现在转向 NLJOIN(2) 的 INNER:

TBSCAN(4) —— 因为这个连接断言和 NLJOIN(2) 分析出的相同,并且没有其他局部断言,可以忽略该操作。

我们生成 NLJOIN (2) 的计数。

清单 20. NLJOIN (2)
              2.176 	<- Total Estimated Number of rows.
             NLJOIN 
             (   2) 
             26.0872

我们来看看 NLJOIN (2) 计算得到的断言:

清单 21. NLJOIN (2) 计算的断言
4) Predicate used in Join
	Relational Operator:	Equal (=)
	Subquery Input Required: 	No
	Filter Factor: 		0.125
	Predicate Text:
	--------------
	(Q2.DEPT = Q1.DEPTNUMB)

因为该 NLJOIN 的 OUTER 即 TBSCAN(2) 应用于局部断言,必须将该断言增加到计数查询。从下图可以看到断言是累积的。

图 22. 将断言添加到 count 查询中
SELECT 
	COUNT(*)
FROM
	SKAPOOR.ORG AS Q1,
	SKAPOOR.STAFF AS Q2
WHERE
	(Q2.DEPT = Q1.DEPTNUMB) AND 
	(+10000.00 < Q2.SALARY) AND
	(Q2.NAME = 'Sanders');

该查询将返回 1 行,而估计值是 2.176。

这样就完成了访问计划中的所有操作。在这个具体的例子中,如果查看访问计划中两个表的 CARDINALITY 估值,STAFF 和 ORG 表分别为 68 和 60(请参阅 清单 10 中的 exfmt 输出)。但是,如果分别查看每个表中的记录数,就会看到这是不正确的:

清单 23. 记录数
C:\>db2 "select count(*) from staff"
1
-----------
         35
  1 record(s) selected.
C:\>db2 "select count(*) from org"
1
-----------
          8
1 record(s) selected.

如果查看计划底部的 “Objects used in Access Plan” 部分,就可以看到访问计划中有哪些表和索引及其统计信息。比如:

清单 24. 访问计划中的对象统计信息
Objects Used in Access Plan:
---------------------------
	Schema: SKAPOOR 
	Name: 	ORG
	Type: 	Table
			Time of creation: 			2005-09-24-12.53.47.951004
			Last statistics update:
			Number of columns: 		5
			Number of rows: 			60
			Width of rows: 			14
			Number of buffer pool pages: 	1
			Distinct row values: 		No
			Tablespace name: 			USERSPACE1        
			Tablespace overhead: 		12.670000
			Tablespace transfer rate: 		0.180000
			Source for statistics: 		Single Node
			Prefetch page count: 		32
			Container extent page count: 	32
			Table overflow record count: 	0
			Table Active Blocks: 		-1

在这个例子中,“Last statistics update” 列是空白的。这意味着该表从未执行过 RUNSTATS。还可以看到 STAFF 表也是空白的。因此在这种情况下,应该收集 STATISTICS 以帮助优化器更好地了解数据是什么样子的。收集数据分布的统计信息,然后再次生成访问计划。

连接到示例数据库,然后对 sales 和 org 表发出 RUNSTATS 命令:

清单 25. 对 sales 和 org 表运行 RUNSTATS
C:\> db2 runstats on table <schema>.org with distribution and indexes all
C:\> db2 runstats on table <schema>.staff with distribution and indexes all

其中 <schema> 是用于创建示例数据库的模式。

再次生成查询的访问计划。结果应该如下所示:

清单 26. 生成访问计划
Access Plan:
-----------
	Total Cost: 	25.9071
	Query Degree:	1
              Rows 
             RETURN 
             (   1) 
              Cost 
              I/O 
               |
            0.971429 
             NLJOIN 
             (   2) 
             25.9071 
                2 
          /-----+------\
    0.971429            1 
     TBSCAN           TBSCAN 
     (   3)           (   4) 
     12.9941          12.913 
        1                1 
        |                |
       35                8 
 TABLE: SKAPOOR   TABLE: SKAPOOR  
      STAFF             ORG

可以看到总体估计更加接近了(0.971429,实际值是 1)。

这个例子使用和 例 1 同样的环境、数据库和数据库管理程序配置。

在命令行中对下列三个表运行 RUNSTATS,如下所示:

清单 27. 对 sales、employee 和 department 表运行 RUNSTATS
C:\> db2 connect to sample
C:\> db2 runstats on table <schema>.sales with distribution and indexes all
C:\> db2 runstats on table <schema>.employee with distribution and indexes all
C:\> db2 runstats on table <schema>.department with distribution and indexes all

按照和例 1 同样的步骤生成查询的访问计划:

清单 28. 查询
select
	a.sales_person, 
	c.deptname, 
	c.deptno,
	b.sex,
	count(*) as count
from
	sales a, 
	employee b, 
	department c
where
	a.SALES_PERSON=b.LASTNAME AND
	b.workdept = c.deptno and
	c.deptno in ('E21', 'A00') and
	b.sex='M' and
	a.sales=1
group by 
	a.sales_person, 
	c.deptname, 
	c.deptno, b.sex;

在 EXPLAIN 输出中,可以看到类似下面的结果(注意该例中禁用了 DB2_HASH_JOIN):

清单 29. 例 2 的 Explain 输出
Original Statement:
------------------
select a.sales_person, c.deptname, c.deptno, b.sex, count(*) as count 
from sales a, employee b, department c 
where a.SALES_PERSON=b.LASTNAME AND b.workdept = c.deptno and c.deptno in 
        ('E21', 'A00') and b.sex='M' and a.sales=1 
group by a.sales_person, c.deptname, c.deptno, b.sex 
Optimized Statement:
-------------------
SELECT Q5.$C0 AS "SALES_PERSON", Q5.$C1 AS "DEPTNAME", Q5.$C2 AS "DEPTNO", 
        'M' AS "SEX", Q5.$C3 AS "COUNT" 
FROM 
   (SELECT Q4.$C0, Q4.$C1, Q4.$C2, COUNT(* ) 
   FROM 
      (SELECT Q3.SALES_PERSON, Q1.DEPTNAME, Q1.DEPTNO 
      FROM SKAPOOR.DEPARTMENT AS Q1, SKAPOOR.EMPLOYEE AS Q2, SKAPOOR.SALES AS Q3 
      WHERE (Q3.SALES = 1) AND (Q2.SEX = 'M') AND (Q2.WORKDEPT = Q1.DEPTNO) 
      AND (Q3.SALES_PERSON = Q2.LASTNAME) AND Q1.DEPTNO IN ('E21', 
           'A00')) AS Q4 
   GROUP BY Q4.$C2, Q4.$C1, Q4.$C0) AS Q5 
Access Plan:
-----------
	Total Cost: 		51.7706
	Query Degree:		1
                      Rows 
                     RETURN 
                     (   1) 
                      Cost 
                       I/O 
                       |
                     1.60887 
                     GRPBY  
                     (   2) 
                     51.7695 
                        4 
                       |
                     1.60887 
                     MSJOIN 
                     (   3) 
                     51.7689 
                        4 
                /-------+------\
           4.15625            0.387097 
           TBSCAN              FILTER 
           (   4)              (  14) 
           38.7748             12.9865 
              3                   1 
              |                   |
           4.15625               12 
           SORT                TBSCAN 
           (   5)              (  15) 
           38.7724             12.9865 
              3                   1 
              |                   |
           4.15625               12 
           MSJOIN              SORT   
           (   6)              (  16) 
           38.7659             12.9852 
              3                  1 
          /---+---\              |
        2         2.07813        12 
     TBSCAN       FILTER       TBSCAN 
     (   7)       (  10)       (  17) 
     12.9188      25.8441      12.9805 
        1            2            1 
        |            |            |
        2         4.15625        41 
     SORT         TBSCAN   TABLE: SKAPOOR  
     (   8)       (  11)        SALES 
     12.9176      25.8441 
        1            2 
        |            |
        2         4.15625 
     TBSCAN       SORT   
     (   9)       (  12) 
     12.915       25.8429 
        1            2 
        |            |
        9         4.15625 
 TABLE: SKAPOOR   TBSCAN 
   DEPARTMENT     (  13) 
                  25.8401 
                     2 
                     |
                    32 
              TABLE: SKAPOOR  
                 EMPLOYEE

下面收集每个操作的计数。我们按照前面所讨论的基本 指导原则 进行。

  • 根据原则 (A),TBSCAN(9)、TBSCAN(13)、TBSCAN(17) 都属于基本访问操作。
  • 根据原则 (B),我们对获取 SORT(8)、SORT(12) 和 SORT(16) 计数赋予较低的优先级,因为该操作基本级扫描的基数不会变化,也不是不同的 SORT,可以忽略。TBSCAN(7)、(11) 和 (15) 也可以忽略。如果怀疑 SORT 也起到一定作用,可以以后再分析。
  • 根据原则 (C),FILTER(10) 和 FILTER(14) 上连接断言是相同的,分别被命名为 MSJOIN(6) 和 MSJOIN(3)。因此这两个 FILTER(10) 和 FILTER(14) 也可以忽略。
  • 最后根据原则 (D),我们得到了下面的序列:
清单 30. 结合原则 D
TBSCAN (9)、
TBSCAN (13)、 
MSJOIN(6)、
TBSCAN(17)、
MSJOIN(3)。

从最左侧的树开始,按照顺序依次执行。按照和例 1 同样的步骤。

下面是优化语句中重命名的表。

清单 31. 重命名后的表
SKAPOOR.DEPARTMENT AS Q1, 
SKAPOOR.EMPLOYEE AS Q2, 
SKAPOOR.SALES AS Q3
清单 32. TBSCAN(9)
Predicates:
----------
10) Sargable Predicate
	Relational Operator: 	In List (IN)
	Subquery Input Required: 	No
	Filter Factor: 		0.222222
	Predicate Text:
	--------------
	Q1.DEPTNO IN ('E21', 'A00')

count 查询:

清单 33. Count 查询
SELECT COUNT(*)
	FROM
		SKAPOOR.DEPARTMENT AS Q1
	WHERE 
		Q1.DEPTNO IN ('E21', 'A00');

在 OPERATORS (8) 和 (7) 上没有任何局部或连接断言。同样进行基数估计,但是这两个操作不需要计数。

清单 33. TBSCAN(13)
Predicates:
----------
6) Sargable Predicate
	Relational Operator: 	Equal (=)
	Subquery Input Required: 	No
	Filter Factor: 		0.59375
	Predicate Text:
	--------------
	(Q2.SEX = 'M') 
9) Sargable Predicate
	Relational Operator: 	In List (IN)
	Subquery Input Required: 	No
	Filter Factor: 		0.21875
	Predicate Text:
	--------------
	Q2.WORKDEPT IN ('E21', 'A00')

有两个局部断言。首先我们分别检查每个断言的估计值。

清单 34. 每个断言的估计值
SELECT COUNT(*)
	FROM
		SKAPOOR.EMPLOYEE AS Q2
	WHERE
		(Q2.SEX = 'M');

注意,有 19 条记录要返回。如果对 employee 表执行 select count(*) 查询,表中有 32 条记录。因此筛选因子是 19/32 = 0.59375,和访问计划中估计的该断言的筛选因子匹配。

清单 35. 筛选因子
6) Sargable Predicate
	Relational Operator: 	Equal (=)
	Subquery Input Required: 	No
	Filter Factor: 		0.59375
	Predicate Text:
	--------------
	(Q2.SEX = 'M')

该操作中确定的下一个断言:

清单 36. 下一个断言
SELECT COUNT(*)
	FROM
		SKAPOOR.EMPLOYEE AS Q2
	WHERE 
		Q2.WORKDEPT IN ('E21', 'A00');

两个断言结合起来:

清单 37. 两个断言
SELECT COUNT(*)
	FROM
		SKAPOOR.EMPLOYEE AS Q2
	WHERE 
		Q2.WORKDEPT IN ('E21', 'A00') AND
		(Q2.SEX = 'M');

接下来可以跳过操作 (12) 和 (11),因为它们没有应用于断言,仅仅是排序和从排序后的数据中扫描,而 SORT 不是独特的。

要用到下面的连接断言:

清单 38. MSJOIN(6)
Predicates:
----------
7) Predicate used in Join
	Relational Operator: 	Equal (=)
	Subquery Input Required: 	No
	Filter Factor: 		0.5
	Predicate Text:
	--------------
	(Q2.WORKDEPT = Q1.DEPTNO)

和应用于 FILTER(10) 的连接断言相同。现在,把 inner 和 outer 累积起来生成 MSJOIN(6) 上的查询,即 FILTER(10) 和 TBSCAN(9)(与 TBSCAN(7) 相同)。

清单 39. 内连接(INNER JOIN)和外连接(OUTTER JOIN)叠加
SELECT COUNT(*)
	FROM
		SKAPOOR.DEPARTMENT AS Q1,
		SKAPOOR.EMPLOYEE AS Q2
	WHERE 
		Q2.WORKDEPT IN ('E21', 'A00') AND
		(Q2.SEX = 'M') AND
		(Q2.WORKDEPT = Q1.DEPTNO) AND
		Q1.DEPTNO IN ('E21', 'A00');

SORT(5) 和 TBSCAN(4) 同样没有应用于任何断言。跳到 (17)。

清单 40. TBSCAN(17)
Predicates:
----------
5) Sargable Predicate
	Relational Operator: 	Equal (=)
	Subquery Input Required: 	No
	Filter Factor: 		0.292683
	Predicate Text:
	--------------
	(Q3.SALES = 1)

count 查询应该写成:

清单 41. TBSCAN(17) 的 count 查询
SELECT COUNT(*)
	FROM
		SKAPOOR.SALES AS Q3 
	WHERE
		(Q3.SALES = 1);

操作 (16) 和 (15) 可以忽略,因为它们没有应用于任何断言,而 SORT 也不是不同的。

最后是 MSJOIN(3)。

清单 42. MSJOIN(3)
Predicates:
----------
8) Predicate used in Join
	Relational Operator: 	Equal (=)
	Subquery Input Required: 	No
	Filter Factor: 		0.0322581
	Predicate Text:
	--------------
	(Q3.SALES_PERSON = Q2.LASTNAME)

将内连接和外连接累积起来,获得了:

清单 43. 累积两个连接
SELECT COUNT(*)
	FROM
		SKAPOOR.DEPARTMENT AS Q1,
		SKAPOOR.EMPLOYEE AS Q2,
		SKAPOOR.SALES AS Q3 
	WHERE
		(Q3.SALES = 1) AND
		(Q3.SALES_PERSON = Q2.LASTNAME) AND
		 Q2.WORKDEPT IN ('E21', 'A00') AND
		(Q2.SEX = 'M') AND
		(Q2.WORKDEPT = Q1.DEPTNO) AND
		 Q1.DEPTNO IN ('E21', 'A00');

记住,这些是内连接(INNER JOIN)的计数。如果查询涉及外连接(OUTTER JOIN),那么必须按照查询重新编写。可以根据连接操作符的细节判断连接的类型(INNER、RIGHT 或 LEFT)。

例 3:分区的表

在多分区的环境中,可能具有单分区的表和多分区的表。这个例子将解释关于后者的一个问题。

如果跨两个逻辑分区创建示例数据库,并运行以下查询:

清单 44. 对分区的表进行查询
select * from sids.emp_photo

这个查询的 db2exfmt 输出类似于:

清单 45. db2exfmt 输出
Access Plan:
-----------
        Total Cost:             12.9181
        Query Degree:           1
      Rows
     RETURN
     (   1)
      Cost
       I/O
       |
       12
     DTQ
     (   2)
     12.9181
        1
        |
        6
     TBSCAN
     (   3)
     12.8735
        1
        |
        6
 TABLE: SIDS
    EMP_PHOTO

注意,TABLE 显示基数估计值是 6。这实际上是每个分区的基数估计值,不是完整的基数。原因在于,对于分区的表,基本级扫描(比如表扫描(TBSCAN)或索引扫描(IXSCAN))提供每个分区的估计值。

清单 46. 基数估计值
Formula:
Table cardinality = 
Per partition table cardinality * Number of partitions table is partitioned on

所以在上面的例子中,这意味着表的基数是 12(6 * 2)。可以通过检查 db2look 模拟或查询 SYSSTAT.TABLES 的 CARD 列来证实这一点。

清单 47. 证实基数
UPDATE SYSSTAT.TABLES
SET CARD=12,
    NPAGES=2,
    FPAGES=2,
    OVERFLOW=0,
    ACTIVE_BLOCKS=0
WHERE TABNAME = 'EMP_PHOTO' AND TABSCHEMA = 'SIDS    ';

在更一般的情况下,公式应该是:

清单 48. 基数估计值的公式
操作 X 在所有分区上的基数估计值 = 操作 X 在图上的基数估计值
 * 输出流中的分区总数

所以,如果需要比较在多个分区上执行的操作的实际基数,那么可以将 count(*) 输出与该操作在所有分区上的基数估计值进行比较。

例如,在前面的查询 db2exfmt 输出中,如果检查 DTQ(2) 的细节,就会注意到:

清单 49. DTQ(2) 操作
Output Streams:
--------------
        3) To Operator #1
                Estimated number of rows:       12
                Partition Map ID:               -100
                Partitioning:                   (COOR )
                                                Coordinator Partition
                Number of columns:              3
                Subquery predicate ID:          Not Applicable

这告诉我们,输出流(即 DTQ 的结果)流向一个协调器分区,这意味着 DTQ(2) 的基数不需要乘以 2。计划中显示的 DTQ(2) 基数是 12(即,12 行 * 1 个分区)。TBSCAN (3) 的基数也是 12(即,6 行 * 2 个分区)。

这个例子的意图是说明,在处理分区的表时,需要考虑到操作输出流涉及的分区数量。

例 4:逆向工程方法

在这个例子中,可以使用一个优化后的语句(由 db2 改写过的原始 SQL 语句版本)来生成一个更好的 count(*) 查询,可以更好地判断计划中各个操作的实际基数。

假设有一个涉及 UNION 的三表连接和来自查询的 db2exfmt 输出:

(a) 原始 SQL:

清单 50. 原始 SQL
select S0.COL3
from sids.TABLE1 B, sids.TABLE2 S0
where B.myid in
   (select myid
   from sids.TABLE1
   where (Col1,yourid,mynum) in
      (select Col1, 1420, max(mynum) as mynum
      from sids.TABLE1
      where Col2 = '0245338201' and mynum >= 2 and mynum
              <= 18051 and yourid=1420
      group by Col1
      union
      select Col1, 1592, max(mynum) as mynum
      from sids.TABLE1
      where Col2 = '0245338201' and mynum >= 5 and mynum
              <= 21122 and yourid=1592
      group by Col1 ) ) and boolflag='N' and S0.myid =
        B.myid

(b) 优化后的 SQL:

清单 51. 优化后的 SQL
SELECT DISTINCT Q12.COL3 AS "COL3"
FROM SIDS.TABLE1 AS Q1,
   (SELECT Q10.$C0, Q10.$C1, Q10.$C2
   FROM
      (SELECT Q4.$C0, 1592, Q4.$C1
      FROM
         (SELECT Q3.$C0, MAX(Q3.$C1)
         FROM
            (SELECT Q2.COL1, Q2.MYNUM
            FROM SIDS.TABLE1 AS Q2
            WHERE (Q2.YOURID = 1592) AND (Q2.MYNUM <= 21122) AND
                    (5 <= Q2.MYNUM) AND (Q2.COL2 =
                    '0245338201 ')) AS Q3
         GROUP BY Q3.$C0) AS Q4
      UNION ALL
      SELECT Q8.$C0, 1420, Q8.$C1
      FROM
         (SELECT Q7.$C0, MAX(Q7.$C1)
         FROM
            (SELECT Q6.COL1, Q6.MYNUM
            FROM SIDS.TABLE1 AS Q6
            WHERE (Q6.YOURID = 1420) AND (Q6.MYNUM <= 18051) AND
                    (2 <= Q6.MYNUM) AND (Q6.COL2 =
                    '0245338201 ')) AS Q7
         GROUP BY Q7.$C0) AS Q8 ) AS Q10) AS Q11, SIDS.TABLE2 AS
        Q12
WHERE (Q1.MYNUM = Q11.$C2) AND (Q1.YOURID = Q11.$C1) AND
        (Q1.COL1 = Q11.$C0) AND (Q12.MYID = Q1.MYID) AND
        (Q1.BOOLFLAG = 'N')

对应的图:

清单 52. 对应的图
Access Plan:
-----------
        Total Cost:             763.455
        Query Degree:           1
                                      Rows
                                     RETURN
                                     (   1)
                                      Cost
                                       I/O
                                        |
                                   2.89639e-05
                                     TBSCAN
                                     (   2)
                                     763.455
                                     30.5262
                                        |
                                   2.89639e-05
                                     SORT
                                     (   3)
                                     763.455
                                     30.5262
                                        |
                                   2.89639e-05
                                     NLJOIN
                                     (   4)
                                     763.453
                                     30.5262
                             /----------+----------\
                      3.02247e-05                 0.958284
                        NLJOIN                     IXSCAN
                        (   5)                     (  16)
                        738.421                    75.0356
                        29.5262                       3
                  /--------+--------\                 |
             7.84206              3.85418e-06    5.00908e+06
             UNION                  IXSCAN     INDEX: SIDS
             (   6)                 (  15)        A4_COVER
             150.123                75.0371
                6                      3
          /-----+-----\                |
     1.17815          6.66392     5.22714e+06
     GRPBY            GRPBY     INDEX: SIDS
     (   7)           (  11)   RELID_COLLIDSTAG
     75.0593          75.0622
        3                3
        |                |
     1.17815          6.66392
     TBSCAN           TBSCAN
     (   8)           (  12)
     75.0591          75.0614
        3                3
        |                |
     1.17815          6.66392
     SORT             SORT
     (   9)           (  13)
     75.0583          75.0601
        3                3
        |                |
     1.17815          6.66392
     IXSCAN           IXSCAN
     (  10)           (  14)
     75.057           75.057
        3                3
        |                |
   5.22714e+06      5.22714e+06
 INDEX: SIDS  INDEX: SIDS
     BASE_IX          BASE_IX

首先,判断需要为哪些操作收集计数,使用前面提到的 指导原则(A - D)

  1. 根据原则 (A),IXSCAN(10) 和 IXSCAN(14) 都是基本访问。尽管 IXSCAN(15) 是基本访问,但是它依赖于 NLJOIN (5),所以可以忽略它。
  2. 根据原则 (B),从 IXSCAN(10) 到 SORT(9) 基数没有改变,都是估计为 1.17815 行。所以在第一组计数中,可以跳过 SORT(9)。如果怀疑这个 SORT 有问题,那么以后可以收集它的计数。
  3. 原则 (C) 对于这个例子不适用。
  4. 结合原则 (D),决定按照以下次序收集计数:
清单 52. 收集计数
IXSCAN(10)
IXSCAN(14)
UNION(6)
NLJOIN(5)
NLJOIN(4)

注意: 以下清单已经经过删节,只显示计划的相关部分。

IXSCAN(10)

在查看 IXSCAN(10) 的属性时,可以看到应用的断言引用了由 Q2 这样的标签进行限定的列:

清单 53. IXSCAN
8) Sargable Predicate
                        Relational Operator:            Equal (=)
                        Subquery Input Required:        No
                        Filter Factor:                  0.0533979
                        Predicate Text:
                        --------------
                        (Q2.YOURID = 1592)
                        Relational Operator:            Less Than or Equal (<=)
                        Subquery Input Required:        No
                        Filter Factor:                  0.970071
                        Predicate Text:
                        --------------
                        (5 <= Q2.MYNUM)

这些标签是对列名进行限定的限定符。它们可以是基表或者中间结果集。

因为这个方法使用 “逆向方法” 生成计数,我们可以看到 Q2.YOURID 对应于优化语句最内层圆括号中的 SQL(见上面 清单 51斜体部分)。

在提取这个语句,然后修改选择列表,让它重新引用 count(*) 时,IXSCAN(10) 的 count(*) 是

清单 54. IXSCAN(10) 的计数查询
SELECT count(*) 
FROM SIDS.TABLE1 AS Q2 
WHERE (Q2.YOURID = 1592) AND (Q2.MYNUM <= 21122) 
AND 
(5 <= Q2.MYNUM) AND (Q2.COL2 = '0245338201 ')

IXSCAN(14) 在本质上是相似的。查看 IXSCAN(14) 的属性,注意到它的断言是:

清单 55. IXSCAN(14)
  13) Sargable Predicate
          Relational Operator:            Equal (=)
          Subquery Input Required:        No
          Filter Factor:                  0.301996
          Predicate Text:
          --------------
          (Q6.YOURID = 1420)

这也很容易在优化的语句中找到(见上面 清单 51粗体部分),这决定 IXSCAN(14) 的 count(*) 查询如下:

清单 55. IXSCAN(14) 的计数
SELECT count(*)
FROM SIDS.TABLE1 AS Q6 
WHERE (Q6.YOURID = 1420) AND (Q6.MYNUM <= 18051) AND 
(2 <= Q6.MYNUM) AND (Q6.COL2 = '0245338201 ')

为了获得 UNION(6) 的 count(*) 查询,需要从优化语句中提取出最内层圆括号中的 SQL 语句,最少给出 UNION ALL。怎么办呢?

可以看到这个 UNION ALL 是两个中间表的 UNION:Q4 和 Q8,来自清单 51 中的优化语句。Q4 本身是一个完整的 SQL,开始于 “SELECT Q4.$C0”,结束于 “AS Q4”。同样,Q8 也是一个完整的 SQL,开始于 “SELECT Q8.$C0”,结束于 “AS Q8”。提取这个语句的最简单的方式是从最内层的圆括号开始,向外构建。

这会产生以下 SQL 语句,它在结构上是完整的,但是语法是无效的。

清单 56. 从最内层的圆括号开始
SELECT Q4.$C0, 1592, Q4.$C1
      FROM
         (SELECT Q3.$C0, MAX(Q3.$C1)
         FROM
            (SELECT Q2.COL1, Q2.MYNUM
            FROM SIDS.TABLE1 AS Q2
            WHERE (Q2.YOURID = 1592) AND (Q2.MYNUM <= 21122) AND
                    (5 <= Q2.MYNUM) AND (Q2.COL2 =
                    '0245338201 ')) AS Q3
         GROUP BY Q3.$C0) AS Q4
      UNION ALL
      SELECT Q8.$C0, 1420, Q8.$C1
      FROM
         (SELECT Q7.$C0, MAX(Q7.$C1)
         FROM
            (SELECT Q6.COL1, Q6.MYNUM
            FROM SIDS.TABLE1 AS Q6
            WHERE (Q6.YOURID = 1420) AND (Q6.MYNUM <= 18051) AND
                    (2 <= Q6.MYNUM) AND (Q6.COL2 =
                    '0245338201 ')) AS Q7
         GROUP BY Q7.$C0) AS Q8

所以,下一步是纠正语法。应用两个通用规则:

  • 第一个规则是用 “Qn.Ci AS Ci” 替换 SELECT 列表中出现的每个 “Qn.$Ci”。其中的 i 是对于选择列表中逗号分隔的每个条目从 0 开始的枚举值,n 是中间结果表的编号。

    例如:

    清单 57. 纠正后的语法(6)
    "SELECT Q3.$C0, MAX(Q3.$C1)"
    改为:
    "SELECT Q3.C0 AS C0, MAX(Q3.C1) AS C1"

    这就删除了选择列表中的 “$” 并按照 C0, C1, .. C<i> 对每个 SELECT 列表条目进行枚举,直到 SELECT 列表中的第 i 个条目。

  • 第二个规则是删除查询中的所有 “$”。例如:

    清单 58. 查询中的 “$”
    "GROUP BY Q7.$C0"
    改为:
    "GROUP BY Q7.C0"

    应用这些规则就产生了以下查询:

    清单 59. 产生的查询
    SELECT Q4.C0, 1592, Q4.C1 
    FROM 
    (SELECT Q3.C0 AS C0, MAX(Q3.C1) AS C1 
    FROM 
    (SELECT Q2.COL1 AS C0, Q2.MYNUM AS C1 
    FROM SIDS.TABLE1 AS Q2 
    WHERE (Q2.YOURID = 1592) AND (Q2.MYNUM <= 21122) AND 
    (5 <= Q2.MYNUM) AND (Q2.COL2 = 
    '0245338201 ')) AS Q3 
    GROUP BY Q3.C0) AS Q4 
    UNION ALL 
    SELECT Q8.C0, 1420, Q8.C1 
    FROM 
    (SELECT Q7.C0 AS C0, MAX(Q7.C1) AS C1 
    FROM 
    (SELECT Q6.COL1 AS C0, Q6.MYNUM AS C1 
    FROM SIDS.TABLE1 AS Q6 
    WHERE (Q6.YOURID = 1420) AND (Q6.MYNUM <= 18051) AND 
    (2 <= Q6.MYNUM) AND (Q6.COL2 = 
    '0245338201 ')) AS Q7 
    GROUP BY Q7.C0) AS Q8

    注意: 还没有将上面的 SQL 修改为选择 count(*) 查询,因为简单地运行这个查询仍然会返回总的行计数。但是如果需要选择 count(*),那么只需将它添加到最外层选择中。

应用上面的规则,为 NLJOIN(5) 获得的查询如下:

清单 60. NLJOIN(5)
SELECT Q11.C2, Q11.C1, Q11.C0 
FROM SIDS.TABLE1 AS Q1, 
(SELECT Q10.C0 AS C0, Q10.C1 AS C1, Q10.C2 AS C2 
FROM 
(SELECT Q4.C0 AS C0, 1592 AS C1, Q4.C1 AS C2 
FROM 
(SELECT Q3.C0 AS C0, MAX(Q3.C1) AS C1 
FROM 
(SELECT Q2.COL1 AS C0, Q2.MYNUM AS C1 
FROM SIDS.TABLE1 AS Q2 
WHERE (Q2.YOURID = 1592) AND (Q2.MYNUM <= 21122) AND 
(5 <= Q2.MYNUM) AND (Q2.COL2 = 
'0245338201 ')) AS Q3 
GROUP BY Q3.C0) AS Q4 
UNION ALL 
SELECT Q8.C0 AS C0, 1420 AS C1, Q8.C1 AS C2 
FROM 
(SELECT Q7.C0 AS C0, MAX(Q7.C1) AS C1 
FROM 
(SELECT Q6.COL1 AS C0, Q6.MYNUM AS C1 
FROM SIDS.TABLE1 AS Q6 
WHERE (Q6.YOURID = 1420) AND (Q6.MYNUM <= 18051) AND 
(2 <= Q6.MYNUM) AND (Q6.COL2 = 
'0245338201 ')) AS Q7 
GROUP BY Q7.C0) AS Q8) AS Q10) AS Q11 
WHERE (Q1.MYNUM = Q11.C2) AND (Q1.YOURID = Q11.C1) AND 
(Q1.COL1 = Q11.C0) AND (Q1.BOOLFLAG = 'N');

与之相似,NLJOIN(4) 的查询如下:

清单 61. NLJOIN(4)
SELECT Q1.MYID, Q11.C2, Q11.C1, Q11.C0, Q12.COL3 AS "COL3" 
FROM SIDS.TABLE1 AS Q1, 
(SELECT Q10.C0 AS C0, Q10.C1 AS C1, Q10.C2 AS C2 
FROM 
(SELECT Q4.C0 AS C0, 1592 AS C1, Q4.C1 AS C2 
FROM 
(SELECT Q3.C0 AS C0, MAX(Q3.C1) AS C1 
FROM 
(SELECT Q2.COL1 AS C0, Q2.MYNUM AS C1 
FROM SIDS.TABLE1 AS Q2 
WHERE (Q2.YOURID = 1592) AND (Q2.MYNUM <= 21122) AND 
(5 <= Q2.MYNUM) AND (Q2.COL2 = 
'0245338201 ')) AS Q3 
GROUP BY Q3.C0) AS Q4 
UNION ALL 
SELECT Q8.C0 AS C0, 1420 AS C1, Q8.C1 AS C2 
FROM 
(SELECT Q7.C0 AS C0, MAX(Q7.C1) AS C1 
FROM 
(SELECT Q6.COL1 AS C0, Q6.MYNUM AS C1 
FROM SIDS.TABLE1 AS Q6 
WHERE (Q6.YOURID = 1420) AND (Q6.MYNUM <= 18051) AND 
(2 <= Q6.MYNUM) AND (Q6.COL2 = 
'0245338201 ')) AS Q7 
GROUP BY Q7.C0) AS Q8) AS Q10) AS Q11, SIDS.TABLE2 AS Q12 
WHERE (Q1.MYNUM = Q11.C2) AND (Q1.YOURID = Q11.C1) AND 
(Q1.COL1 = Q11.C0) AND (Q12.MYID = Q1.MYID) AND (Q1.BOOLFLAG = 'N');

作为练习,可以自己生成 NLJOIN(5) 和 NLJOIN(4) 的查询,并与上面最后两个查询进行比较。

结束语

对真实的基数和优化器估计的基数进行比较,可以帮助您识别出查询计划方面的问题。由于真实数据关系的复杂性,估计值不总是准确的,但是有许多工具和技术可以改进它们。本文帮助您识别在应用这些技术时要关注计划中的哪些部分。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Information Management
ArticleID=102453
ArticleTitle=比较实时基数和优化器的基数估计值
publish-date=01232006