通过分析SQL语句的执行计划优化SQL(总结)

2018-10-22 19:09

通过分析SQL语句的执行计划优化SQL(总结)

做DBA快7年了,中间感悟很多。在DBA的日常工作中,调整个别性能较差的SQL语句时一项富有挑战性的工

作。其中的关键在于如何得到SQL语句的执行计划和如何从SQL语句的执行计划中发现问题。总是想将日常

经验的点点滴滴总结一下,但是直到最近才下定决心,总共花了3个周末时间,才将其整理成册,便于自

己日常工作。不好意思独享,所以将其贴出来。

修改日志:

2006.02.20:

根据网友反馈已做部分修改,但pdf文件没有做修改,修改部分在“如何产生执行计划”关于set

autotraceonly的介绍部分

第一章、第2章 并不是很重要,是自己的一些想法,关于如何做一个稳定、高效的应用系统的一些想法。

第三章以后都是比较重要的。

附录的内容也是比较重要的。我常用该部分的内容。

前言

本文档主要介绍与SQL调整有关的内容,内容涉及多个方面:SQL语句执行的过程、ORACLE优化器,表之间

的关联,如何得到SQL执行计划,如何分析执行计划等内容,从而由浅到深的方式了解SQL优化的过程,使

大家逐步步入SQL调整之门,然后你将发现??。

该文档的不当之处,敬请指出,以便进一步改正。请将其发往我的信箱:xu_yu_jin2000@sina.com。

如果引用本文的内容,请著名出处

目录

第1章 性能调整综述 第2章 有效的应用设计

第3章 SQL语句处理的过程 第4章 ORACLE的优化器 第5章 ORACLE的执行计划 访问路径(方法) -- access path 表之间的连接

如何产生执行计划 如何分析执行计划

如何干预执行计划 - - 使用hints提示 具体案例分析

第6章 其它注意事项 附录

第1章 性能调整综述

Oracle数据库是高度可调的数据库产品。本章描述调整的过程和那些人员应与Oracle服务器的调整有关,

以及与调整相关联的操作系统硬件和软件。本章包括以下方面: l 谁来调整系统? l 什么时候调整?

l 建立有效调整的目标 l 在设计和开发时的调整 l 调整产品系统 l 监控产品系统

谁来调整系统:

为了有效地调整系统,若干类人员必须交换信息并牵涉到系统调整中,例如: l 应用设计人员必须传达应用系统的设计,使得每个人都清楚应用中的数据流动. l 应用开发人员必须传达他们选择的实现策略,使得语句调整的过程中能快速、容易地识别有问题的应用

模块和可疑的SQL语句.

l 数据库管理人员必须仔细地监控系统活动并提供它们的资料,使得异常的系统性能可被快速得识别和纠 正.

l 硬件/软件管理人员必须传达系统的硬件、软件配置并提供它们的资料,使得相关人员能有效地设计和

管理系统。

简而言之,与系统涉及的每个人都在调整过程中起某些作用,当上面提及的那些人员传达了系统的特性并

提供了它们的资料,调整就能相对的容易和更快一些。

不幸的是,事实上的结果是:数据库管理员对调整负有全部或主要的责任。但是,数据库管理员很少有合

适的系统方面的资料,而且,在很多情况下,数据库管理员往往是在实施阶段才介入数据库,这就给调整

工作带来许多负面的影响,因为在设计阶段的缺陷是不能通过DBA的调整而得以解决,而设计阶段的缺陷

往往对数据库性能造成极大的影响。

其实,在真正成熟的开发环境下,开发人员作为纯代码编写人员时,对性能的影响最小,此时大部分的工

作应由应用设计人员完成,而且数据库管理员往往在前期的需求管理阶段就介入,为设计人员提供必要的

技术支持。

调整并不是数据库管理员的专利,相反大部分应该是设计人员和开发人员的工作,这就需要设计人员和开

发人员具体必要的数据库知识,这样才能组成一个高效的团队,然而事实上往往并非如此。

什么时候作调整?

多数人认为当用户感觉性能差时才进行调整,这对调整过程中使用某些最有效的调整策略来说往往是太迟 了。此时,如果你不愿意重新设计应用的话,你只能通过重新分配内存(调整SGA)和调整I/O的办法或多或

少地提高性能。Oracle提供了许多特性,这些特性只有应用到正确地设计的系统中时才能够很大地提高性

能。

应用设计人员需要在设计阶段设置应用的性能期望值。然后在设计和开发期间,应用设计人员应考虑哪些

Oracle 特性可以对系统有好处,并使用这些特性。

通过良好的系统设计,你就可以在应用的生命周期中消除性能调整的代价和挫折。图1-1图1-2说明在应用

的生命周期中调整的相对代价和收益,正如你见到的,最有效的调整时间是在设计阶段。在设计期间的调

整能以最低的代价给你最大的收益。

图1-1 在应用生命周期中调整的代价

图1-2 在应用生命周期中调整的收益

当然,即使在设计很好的系统中,也可能有性能降低。但这些性能降低应该是可控的和可以预见的。

调整目标

不管你正在设计或维护系统,你应该建立专门的性能目标,它使你知道何时要作调整。如果你试图胡乱地

改动初始化参数或SQl 语句,你可能会浪费调整系统的时间,而且无什么大的收益。调整你的系统的最有

效方法如下:

l 当设计系统时考虑性能

l 调整操作系统的硬件和软件 l 识别性能瓶颈 l 确定问题的原因 l 采取纠正的动作

当你设计系统时,制定专门的目标;例如,响应时间小于3秒。当应用不能满足此目标时,识别造成变慢

的瓶颈(例如,I/O竞争),确定原因,采取纠正动作。在开发期间,你应测试应用研究,确定在采取应

用之前是否满足设计的性能目标。

当你正在维护生产库系统时,有多种快速有效的方法来识别性能瓶颈。

不管怎样,调整通常是一系列开销。一旦你已确定了瓶颈,你可能要牺牲一些其它方面的指标来达到所要

的结果。例如,如果I/O有问题,你可能需要更多内存或磁盘。如果不可能买,你可能要限制系统的并发

性,来获取所需的性能。然而,如果你已经明确地定义了性能的目标,那用什么来交换高性能的决策就变

的很容易的,因为你已经确定了哪些方面是最重要的,如过我的目标为高性能,可能牺牲一些空间资源。

随着应用的越来越庞大,硬件性能的提高,全面的调整应用逐渐变成代价高昂的行为,在这样情况下,要

取得最大的投入/效率之比,较好的办法是调整应用的关键部分,使其达到比较高的性能,这样从总体上

来说,整个系统的性能也是比较高的。这也就是有名的20/80原则,调整应用的20%(关键部分),能解决

80%的问题。

在设计和开发系统时作调整

良好设计的系统可以防止在应用生命周期中产生性能问题。系统设计人员和应用开发人员必须了解Oracle

的查询处理机制以便写出高效的SQL语句。“第2章 有效的应用设计”讨论了你的系统中各种可用的配置

,以及每种配置更适合哪种类型的应用。“第5章 优化器”讨论了Oracle的查询优化器,以及如何写语句

以获取最快的结果。

当设计你的系统时,使用下列优化性能的准则:

l 消除客户机/服务器应用中不必要的网络传输。-- 使用存储过程。

l 使用适合你系统的相应Oracle服务器选件(例如,并行查询或分布式数据库)。 l 除非你的应用有特殊的需要,否则使用缺省的Oracle锁。

l 利用数据库记住应用模块,以便你能以每个模块为基础来追踪性能。 l 选择你的数据块的最佳大小。 -- 原则上来说大一些的性能较好。 l 分布你的数据,使得一个节点使用的数据本地存贮在该节点中。

调整产品系统

本节描述对应用系统快速、容易地找出性能瓶颈,并决定纠正动作的方法。这种方法依赖于对Oracle服务

器体系结构和特性的了解程度。在试图调整你的系统前,你应熟悉Oracle调整的内容。

为调整你已有的系统,遵从下列步骤: l 调整操作系统的硬件和软件

l 通过查询V $SESSION_WAIT视图,识别性能的瓶颈,这个动态性能视图列出了造成会话(session)等待的

事件。

l 通过分析V $SESSION_WAIT中的数据,决定瓶颈的原因。 l 纠正存在的问题。

监控应用系统

这主要是通过监控oracle的动态视图来完成。

各种有用的动态视图:如v$session_wait, v$session_event等。

第2章 有效的应用设计

我们通常将最常用的应用分为2种类型:联机事务处理类型(OLTP),决策支持系统(DSS)。

联机事务处理(OLTP)

该类型的应用是高吞吐量,插入、更新、删除操作比较多的系统,这些系统以不断增长的大容量数据为特

征,它们提供给成百用户同时存取,典型的OLTP系统是订票系统,银行的业务系统,订单系统。OTLP的主

要目标是可用性、速度、并发性和可恢复性。

当设计这类系统时,必须确保大量的并发用户不能干扰系统的性能。还需要避免使用过量的索引与

cluster 表,因为这些结构会使插入和更新操作变慢。

决策支持(DSS)

该类型的应用将大量信息进行提取形成报告,协助决策者作出正确的判断。典型的情况是:决策支持系统

将OLTP应用收集的大量数据进行查询。典型的应用为客户行为分析系统(超市,保险等)。

决策支持的关键目标是速度、精确性和可用性。

该种类型的设计往往与OLTP设计的理念背道而驰,一般建议使用数据冗余、大量索引、cluster table、

并行查询等。

近年来,该类型的应用逐渐与OLAP、数据仓库紧密的联系在一起,形成的一个新的应用方向。

2楼 06-01-12 17:25 [ 大 中 小 ]

SunnyXu 一般会员

注册日期: 2004 Nov 来自:

技术贴数:38 精华贴数:1 论坛积分:267 论坛排名:9743 论坛徽章:0

第3章 SQL语句处理的过程

在调整之前我们需要了解一些背景知识,只有知道这些背景知识,我们才能更好的去调整sql语句。

本节介绍了SQL语句处理的基本过程,主要包括: · 查询语句处理

· DML语句处理(insert, update, delete)

· DDL 语句处理(create .. , drop .. , alter .. , ) · 事务控制(commit, rollback)

SQL 语句的执行过程(SQL Statement Execution)

图3-1 概要的列出了处理和运行一个sql语句的需要各个重要阶段。在某些情况下,Oracle运行sql的过程

可能与下面列出的各个阶段的顺序有所不同。如DEFINE阶段可能在FETCH阶

段之前,这主要依赖你如何书

写代码。

对许多oracle的工具来说,其中某些阶段会自动执行。绝大多数用户不需要关心各个阶段的细节问题,然

而,知道执行的各个阶段还是有必要的,这会帮助你写出更高效的SQL语句来,而且还可以让你猜测出性

能差的SQL语句主要是由于哪一个阶段造成的,然后我们针对这个具体的阶段,找出解决的办法。

图 3-1 SQL语句处理的各个阶段

DML语句的处理

本节给出一个例子来说明在DML语句处理的各个阶段到底发生了什么事情。 假设你使用Pro*C程序来为指定部门的所有职员增加工资。程序已经连到正确的用户,你可以在你的程序

中嵌入如下的SQL语句:

EXEC SQL UPDATE employees SET salary = 1.10 * salary

WHERE department_id = :var_department_id;

var_department_id是程序变量,里面包含部门号,我们要修改该部门的职员的工资。当这个SQL语句执行

时,使用该变量的值。

每种类型的语句都需要如下阶段: · 第1步: Create a Cursor 创建游标 · 第2步: Parse the Statement 分析语句 · 第5步: Bind Any Variables 绑定变量 · 第7步: Run the Statement 运行语句 · 第9步: Close the Cursor 关闭游标

如果使用了并行功能,还会包含下面这个阶段: · 第6步: Parallelize the Statement 并行执行语句

如果是查询语句,则需要以下几个额外的步骤,如图 3所示: · 第3步: Describe Results of a Query 描述查询的结果集 · 第4步: Define Output of a Query 定义查询的输出数据 · 第8步: Fetch Rows of a Query 取查询出来的行

下面具体说一下每一步中都发生了什么事情:.

第1步: 创建游标(Create a Cursor)

由程序接口调用创建一个游标(cursor)。任何SQL语句都会创建它,特别在运行DML语句时,都是自动创

建游标的,不需要开发人员干预。多数应用中,游标的创建是自动的。然而,在预编译程序(pro*c)中游

标的创建,可能是隐含的,也可能显式的创建。在存储过程中也是这样的。

第2步:分析语句(Parse the Statement)

在语法分析期间,SQL语句从用户进程传送到Oracle,SQL语句经语法分析后,SQL语句本身与分析的信息

都被装入到共享SQL区。在该阶段中,可以解决许多类型的错误。

语法分析分别执行下列操作:

l 翻译SQL语句,验证它是合法的语句,即书写正确 l 实现数据字典的查找,以验证是否符合表和列的定义

l 在所要求的对象上获取语法分析锁,使得在语句的语法分析过程中不改变这些对象的定义

l 验证为存取所涉及的模式对象所需的权限是否满足 l 决定此语句最佳的执行计划 l 将它装入共享SQL区

l 对分布的语句来说,把语句的全部或部分路由到包含所涉及数据的远程节点 以上任何一步出现错误,都将导致语句报错,中止执行。

只有在共享池中不存在等价SQL语句的情况下,才对SQL语句作语法分析。在这种情况下,数据库内核重新

为该语句分配新的共享SQL区,并对语句进行语法分析。进行语法分析需要耗费较多的资源,所以要尽量

避免进行语法分析,这是优化的技巧之一。

语法分析阶段包含了不管此语句将执行多少次,而只需分析一次的处理要求。Oracle只对每个SQL语句翻

译一次,在以后再次执行该语句时,只要该语句还在共享SQL区中,就可以避免对该语句重新进行语法分

析,也就是此时可以直接使用其对应的执行计划对数据进行存取。这主要是通过绑定变量(bind

variable)实现的,也就是我们常说的共享SQL,后面会给出共享SQL的概念。

虽然语法分析验证了SQL语句的正确性,但语法分析只能识别在SQL语句执行之前所能发现的错误(如书写

错误、权限不足等)。因此,有些错误通过语法分析是抓不到的。例如,在数据转换中的错误或在数据中

的错(如企图在主键中插入重复的值)以及死锁等均是只有在语句执行阶段期间才能遇到和报告的错误或

情况。

查询语句的处理

查询与其它类型的SQL语句不同,因为在成功执行后作为结果将返回数据。其它语句只是简单地返回成功

或失败,而查询则能返回一行或许多行数据。查询的结果均采用表格形式,结果行被一次一行或者批量地

被检索出来。从这里我们可以得知批量的fetch数据可以降低网络开销,所以批量的fetch也是优化的技巧

之一。

有些问题只与查询处理相关,查询不仅仅指SELECT语句,同样也包括在其它SQL语句中的隐含查询。例如

,下面的每个语句都需要把查询作为它执行的一部分: INSERT INTO table SELECT...

UPDATE table SET x = y WHERE... DELETE FROM table WHERE... CREATE table AS SELECT... 具体来说,查询 · 要求读一致性

· 可能使用回滚段作中间处理

· 可能要求SQL语句处理描述、定义和取数据阶段

第3步: 描述查询结果(Describe Results of a Query)

描述阶段只有在查询结果的各个列是未知时才需要;例如,当查询由用户交互地输入需要输出的列名。在

这种情况要用描述阶段来决定查询结果的特征(数据类型,长度和名字)。

[/B]第4步: 定义查询的输出数据(Define Output of a Query) [/B]

在查询的定义阶段,你指定与查询出的列值对应的接收变量的位置、大小和数据类型,这样我们通过接收

变量就可以得到查询结果。如果必要的话,Oracle会自动实现数据类型的转换。这是将接收变量的类型与

对应的列类型相比较决定的。

第5步: 绑定变量(Bind Any Variables)

此时,Oracle知道了SQL语句的意思,但仍没有足够的信息用于执行该语句。Oracle 需要得到在语句中列

出的所有变量的值。在该例中,Oracle需要得到对department_id列进行限定的值。得到这个值的过程就

叫绑定变量(binding variables)

此过程称之为将变量值捆绑进来。程序必须指出可以找到该数值的变量名(该变量被称为捆绑变量,变量

名实质上是一个内存地址,相当于指针)。应用的最终用户可能并没有发觉他们正在指定捆绑变量,因为

Oracle 的程序可能只是简单地指示他们输入新的值,其实这一切都在程序中自动做了。

因为你指定了变量名,在你再次执行之前无须重新捆绑变量。你可以改变绑定变量的值,而Oracle在每次

执行时,仅仅使用内存地址来查找此值。

如果Oracle 需要实现自动数据类型转换的话(除非它们是隐含的或缺省的),你还必须对每个值指定数

据类型和长度。关于这些信息可以参考oracle的相关文档,如Oracle Call Interface Programmer's

Guide

第6步: 并行执行语句(Parallelize the Statement )

ORACLE 可以在SELECTs, INSERTs, UPDATEs, MERGEs, DELETEs语句中执行相应并行查询操作,对于某些

DDL操作,如创建索引、用子查询创建表、在分区表上的操作,也可以执行并

行操作。并行化可以导致多

个服务器进程(oracle server processes)为同一个SQL语句工作,使该SQL语句可以快速完成,但是会耗

费更多的资源,所以除非很有必要,否则不要使用并行查询。

第7步: 执行语句(Run the Statement) 到了现在这个时候,Oracle拥有所有需要的信息与资源,因此可以真正运行SQL语句了。如果该语句为

SELECT查询或INSERT语句,则不需要锁定任何行,因为没有数据需要被改变。然而,如果语句为UPDATE或

DELETE语句,则该语句影响的所有行都被锁定,防止该用户提交或回滚之前,别的用户对这些数据进行修

改。这保证了数据的一致性。

对于某些语句,你可以指定执行的次数,这称为批处理(array processing)。指定执行N次,则绑定变量

与定义变量被定义为大小为N的数组的开始位置,这种方法可以减少网络开销,也是优化的技巧之一。

第8步: 取出查询的行(Fetch Rows of a Query)

在fetch阶段,行数据被取出来,每个后续的存取操作检索结果集中的下一行数据,直到最后一行被取出

来。上面提到过,批量的fetch是优化的技巧之一。

第9步: 关闭游标(Close the Cursor)

SQL语句处理的最后一个阶段就是关闭游标

DDL语句的处理(DDL Statement Processing)

DDL语句的执行不同与DML语句和查询语句的执行,这是因为DDL语句执行成功后需要对数据字典数据进行

修改。对于DDL语句,语句的分析阶段实际上包括分析、查找数据字典信息和执行。

事务管理语句、会话管理语句、系统管理语句只有分析与执行阶段,为了重新执行该语句,会重新分析与

执行该语句。

事务控制(Control of Transactions)

一般来说,只有使用ORACLE编程接口的应用设计人员才关心操作的类型,并把相关的操作组织在一起,形

成一个事务。一般来说,我门必须定义事务,这样在一个逻辑单元中的所有工作可以同时被提交或回滚,

保证了数据的一致性。一个事务应该由逻辑单元中的所有必须部分组成,不应该多一个,也不应该少一个 。

· 在事务开始和结束的这段时间内,所有被引用表中的数据都应该在一致的状态(或可以被回溯到一致的

状态)

· 事务应该只包含可以对数据进行一致更改(one consistent change to the data)的SQL语句

例如,在两个帐号之间的转帐(这是一个事务或逻辑工作单元),应该包含从一个帐号中借钱(由一个SQL完

成),然后将借的钱存入另一个帐号(由另一个SQL完成)。这2个操作作为一个逻辑单元,应该同时成功或

同时失败。其它不相关的操作,如向一个帐户中存钱,不应该包含在这个转帐事务中。

在设计应用时,除了需要决定哪种类型的操作组成一个事务外,还需要决定使用

BEGIN_DISCRETE_TRANSACTIO存储过程是否对提高小的、非分布式的事务的性能有作用。

3楼 06-01-12 17:30 [ 大 中 小 ]

SunnyXu 一般会员

注册日期: 2004 Nov 来自:

技术贴数:38 精华贴数:1 论坛积分:267 论坛排名:9743 论坛徽章:0

第4章 ORACLE的优化器

优化器有时也被称为查询优化器,这是因为查询是影响数据库性能最主要的部分,不要以为只有SELECT语

句是查询。实际上,带有任何WHERE条件的DML(INSERT、UPDATE、DELETE)语句中都包含查询要求,在后面

的文章中,当说到查询时,不一定只是指SELECT语句,也有可能指DML语句中的查询部分。优化器是所有

关系数据库引擎中的最神秘、最富挑战性的部件之一,从性能的角度看也是最重要的部分,它性能的高低

直接关系到数据库性能的好坏。

我们知道,SQL语句同其它语言(如C语言)的语句不一样,它是非过程化(non-procedural)的语句,即当你

要取数据时,不需要告诉数据库通过何种途径去取数据,如到底是通过索引取数据,还是应该将表中的每

行数据都取出来,然后再通过一一比较的方式取数据(即全表扫描),这是由数据库的优化器决定的,这就

是非过程化的含义,也就是说,如何取数据是由优化器决定,而不是应用开发者通过编程决定。在处理

SQL的SELECT、UPDATE、INSERT或DELETE语句时,Oracle 必须访问语句所涉及的数据,Oracle的优化器部

分用来决定访问数据的有效路径,使得语句执行所需的I/O和处理时间最小。

为了实现一个查询,内核必须为每个查询定制一个查询策略,或为取出符合条件的数据生成一个执行计划

(execution plan)。典型的,对于同一个查询,可能有几个执行计划都符合要求,都能得到符合条件的数

据。例如,参与连接的表可以有多种不同的连接方法,这取决于连接条件和优化器采用的连接方法。为了

在多个执行计划中选择最优的执行计划,优化器必须使用一些实际的指标来衡量每个执行计划使用的资源

(I/0次数、CPU等),这些资源也就是我们所说的代价(cost)。如果一个执行计划使用的资源多,我们就说

使用执行计划的代价大。以执行计划的代价大小作为衡量标准,优化器选择代价最小的执行计划作为真正

执行该查询的执行计划,并抛弃其它的执行计划。

在ORACLE的发展过程中,一共开发过2种类型的优化器:基于规则的优化器和基于代价的优化器。这2种优

化器的不同之处关键在于:取得代价的方法与衡量代价的大小不同。现对每种优化器做一下简单的介绍:

基于规则的优化器 -- Rule Based (Heuristic) Optimization(简称RBO):

在ORACLE7之前,主要是使用基于规则的优化器。ORACLE在基于规则的优化器中采用启发式的方法

(Heuristic Approach)或规则(Rules)来生成执行计划。例如,如果一个查询的where条件(where clause)

包含一个谓词(predicate,其实就是一个判断条件,如”=”, “>”, ”<”等),而且该谓词上引用的列

上有有效索引,那么优化器将使用索引访问这个表,而不考虑其它因素,如表中数据的多少、表中数据的

易变性、索引的可选择性等。此时数据库中没有关于表与索引数据的统计性描述,如表中有多上行,每行

的可选择性等。优化器也不考虑实例参数,如multi block i/o、可用排序内存的大小等,所以优化器有

时就选择了次优化的计划作为真正的执行计划,导致系统性能不高。 如,对于

select * from emp where deptno = 10;

这个查询来说,如果是使用基于规则的优化器,而且deptno列上有有效的索引,则会通过deptno列上的索

引来访问emp表。在绝大多数情况下,这是比较高效的,但是在一些特殊情况下,使用索引访问也有比较

低效的时候,现举例说明: 1) emp表比较小,该表的数据只存放在几个数据块中。此时使用全表扫描比使用索引访问emp表反而要好

。因为表比较小,极有可能数据全在内存中,所以此时做全表扫描是最快的。而如果使用索引扫描,需要

先从索引中找到符合条件记录的rowid,然后再一一根据这些rowid从emp中将数据取出来,在这种条件下

,效率就会比全表扫描的效率要差一些。

2) emp表比较大时,而且deptno = 10条件能查询出表中大部分的数据如(50%)。如该表共有4000万行数据

,共放在有500000个数据块中,每个数据块为8k,则该表共有约4G,则这么多的数据不可能全放在内存中 ,绝大多数需要放在硬盘上。此时如果该查询通过索引查询,则是你梦魇的开始。

db_file_multiblock_read_count参数的值200。如果采用全表扫描,则需要

500000/db_file_multiblock_read_count=500000/200=2500次I/O。但是如果采用索引扫描,假设deptno

列上的索引都已经cache到内存中,所以可以将访问索引的开销忽略不计。因为要读出4000万x 50% =

2000万数据,假设在读这2000万数据时,有99.9%的命中率,则还是需要20000次I/O,比上面的全表扫描需

要的2500次多多了,所以在这种情况下,用索引扫描反而性能会差很多。在这样的情况下,用全表扫描的

时间是固定的,但是用索引扫描的时间会随着选出数据的增多使查询时间相应的延长。

上面是枯燥的假设数据,现在以具体的实例给予验证:

环境: oracle 817 + linux + 阵列柜,表SWD_BILLDETAIL有3200多万数据; 表的id列、cn列上都有索引

经查看执行计划,发现执行select count(id) from SWD_BILLDETAIL;使用全表扫描,执行完用了大约

1.50分钟(4次执行取平均,每次分别为1.45 1.51 2.00 1.46)。而执行select count(id) from

SWD_BILLDETAIL where cn <'6';却用了2个小时还没有执行完,经分析该语句使用了cn列上的索引,然后

利用查询出的rowid再从表中查询数据。我为什么不使用select count(cn) from SWD_BILLDETAIL where

cn <'6';呢?后面在分析执行路径的索引扫描时时会给出说明。

下面就是基于规则的优化器使用的执行路径与各个路径对应的等级: RBO Path 1: Single Row by Rowid(等级最高) RBO Path 2: Single Row by Cluster Join

RBO Path 3: Single Row by Hash Cluster Key with Unique or Primary Key RBO Path 4: Single Row by Unique or Primary Key RBO Path 5: Clustered Join RBO Path 6: Hash Cluster Key RBO Path 7: Indexed Cluster Key RBO Path 8: Composite Index

RBO Path 9: Single-Column Indexes

RBO Path 10: Bounded Range Search on Indexed Columns RBO Path 11: Unbounded Range Search on Indexed Columns RBO Path 12: Sort Merge Join

RBO Path 13: MAX or MIN of Indexed Column RBO Path 14: ORDER BY on Indexed Column RBO Path 15: Full Table Scan(等级最低)

上面的执行路径中,RBO认为越往下执行的代价越大,即等级越低。在RBO生成执行计划时,如果它发现有

等级高的执行路径可用,则肯定会使用等级高的路径,而不管任何其它影响性能的元素,即RBO通过上面

的路径的等级决定执行路径的代价,执行路径的等级越高,则使用该执行路径的

代价越小。如上面2个例

子所述,如果使用RBO,则肯定使用索引访问表,也就是选择了比较差的执行计划,这样会给数据库性能

带来很大的负面影响。为了解决这个问题,从ORACLE 7开始oracle引入了基于代价的优化器,下面给出了

介绍。

基于代价的优化器 -- Cost Based Optimization(简称CBO)

Oracle把一个代价引擎(Cost Engine)集成到数据库内核中,用来估计每个执行计划需要的代价,该代价

将每个执行计划所耗费的资源进行量化,从而CBO可以根据这个代价选择出最优的执行计划。一个查询耗

费的资源可以被分成3个基本组成部分:I/O代价、CPU代价、network代价。I/O代价是将数据从磁盘读入

内存所需的代价。访问数据包括将数据文件中数据块的内容读入到SGA的数据高速缓存中,在一般情况下

,该代价是处理一个查询所需要的最主要代价,所以我们在优化时,一个基本原则就是降低查询所产生的

I/O总次数。CPU代价是处理在内存中数据所需要的代价,如一旦数据被读入内存,则我们在识别出我们需

要的数据后,在这些数据上执行排序(sort)或连接(join)操作,这需要耗费CPU资源。

对于需要访问跨节点(即通常说的服务器)数据库上数据的查询来说,存在network代价,用来量化传输操

作耗费的资源。查询远程表的查询或执行分布式连接的查询会在network代价方面花费比较大。

在使用CBO时,需要有表和索引的统计数据(分析数据)作为基础数据,有了这些数据,CBO才能为各个执行

计划计算出相对准确的代价,从而使CBO选择最佳的执行计划。所以定期的对表、索引进行分析是绝对必

要的,这样才能使统计数据反映数据库中的真实情况。否则就会使CBO选择较差的执行计划,影响数据库

的性能。分析操作不必做的太频繁,一般来说,每星期一次就足够了。切记如果想使用CBO,则必须定期

对表和索引进行分析。

对于分析用的命令,随着数据库版本的升级,用的命令也发生了变换,在oracle 8i以前,主要是用

ANALYZE命令。在ORACLE 8I以后,又引入了DBMS_STATS存储包来进行分析。幸运的是从ORACLE 10G以后,

分析工作变成自动的了,这减轻的DBA的负担,不过在一些特殊情况下,还需要一些手工分析。

如果采用了CBO优化器,而没有对表和索引进行分析,没有统计数据,则ORACLE使用缺省的统计数据(至少

在ORACLE 9I中是这样),这可以从oracle的文档上找到。使用的缺省值肯定与系统的实际统计值不一致,

这可能会导致优化器选择错误的执行计划,影响数据库的性能。

要注意的是:虽然CBO的功能随着ORACLE新版本的推出,功能越来越强,但它不是能包治百病的神药,否

则就不再需要DBA了,那我就惨了!!!实际上任何一个语句,随着硬件环境与应用数据的不同,该语句

的执行计划可能需要随之发生变化,这样才能取得最好的性能。所以有时候不在具体的环境下而进行SQL

性能调整是徒劳的。

在ORACLE8I推出的时候,ORACLE极力建议大家使用CBO,说CBO有种种好处,但是在那是ORACLE开发的应用

系统还是使用基于规则的优化器,从这件事上我们可以得出这样的结论:1) 如果团队的数据库水平很高

而且都熟悉应用数据的特点,RBO也可以取得很好的性能。2)CBO不是很稳定,但是一个比较有前途的优

化器,Oracle极力建议大家用是为了让大家尽快发现它的BUG,以便进一步改善,但是ORACLE为了对自己

开发的应用系统负责,他们还是使用了比较熟悉而且成熟的RBO。从这个事情上给我们的启发就是:我们

在以后的开发中,应该尽量采用我们熟悉并且成熟的技术,而不要一味的采用新技术,一味采用新技术并

不一定能开发出好的产品。幸运的是从ORACLE 10G后,CBO已经足够的强大与智能,大家可以放心的使用

该技术,因为ORACLE 10G后,Oracle自己开发的应用系统也使用CBO优化器了。而且ORACLE规定,从

ORACLE 10G开始,开始废弃RBO优化器。这句话并不是指在ORACLE 10G中不能使用RBO,而是从ORACLE 10G

开始开始,不再为RBO的BUG提供修补服务。

在上面的第2个例子中,如果采用CBO优化器,它就会考虑emp表的行数,deptno列的统计数据,发现对该

列做查询会查询出过多的数据,并且考虑db_file_multiblock_read_count参数的设置,发现用全表扫描

的代价比用索引扫描的代价要小,从而使用全表扫描从而取得良好的执行性能。

判断当前数据库使用何种优化器:

主要是由optimizer_mode初始化参数决定的。该参数可能的取值为:first_rows_[1 | 10 | 100 | 1000]

| first_rows | all_rows | choose | rule。具体解释如下: RULE为使用RBO优化器。

CHOOSE则是根据实际情况,如果数据字典中包含被引用的表的统计数据,即引用的对象已经被分析,则就

使用CBO优化器,否则为RBO优化器。

ALL_ROWS为CBO优化器使用的第一种具体的优化方法,是以数据的吞吐量为主要目标,以便可以使用最少

的资源完成语句。

FIRST_ROWS为优化器使用的第二种具体的优化方法,是以数据的响应时间为

FROM v$sqlarea

WHERE executions>0 AND buffer_gets > 100000 ORDER BY 5;

从而对找出的语句进行进一步优化。当然我们还可以为一个正在运行的会话中运行的所有SQL语句生成执

行计划,这需要对该会话进行跟踪,产生trace文件,然后对该文件用tkprof程序格式化一下,这种得到

执行计划的方式很有用,因为它包含其它额外信息,如SQL语句执行的每个阶段(如Parse、Execute、

Fetch)分别耗费的各个资源情况(如CPU、DISK、elapsed等)。

3).用dbms_system存储过程生成执行计划

因为使用dbms_system存储过程可以跟踪另一个会话发出的sql语句,并记录所使用的执行计划,而且还提

供其它对性能调整有用的信息。因其使用方式与上面2种方式有些不太一样,所以在附录中单独介绍。这

种方法是对SQL进行调整比较有用的方式之一,有些情况下非它不可。具体内容参见附录。

如何分析执行计划

例1:

假设LARGE_TABLE是一个较大的表,且username列上没有索引,则运行下面的语句:

SQL> SELECT * FROM LARGE_TABLE where USERNAME = ‘TEST’; Query Plan

-----------------------------------------

SELECT STATEMENT Optimizer=CHOOSE (Cost=1234 Card=1 Bytes=14) TABLE ACCESS FULL LARGE_TABLE [:Q65001] [ANALYZED]

在这个例子中,TABLE ACCESS FULL LARGE_TABLE是第一个操作,意思是在LARGE_TABLE表上做全表扫描。

当这个操作完成之后,产生的row source中的数据被送往下一步骤进行处理,在此例中,SELECT

STATEMENT操作是这个查询语句的最后一步。

Optimizer=CHOOSE 指明这个查询的optimizer_mode,即optimizer_mode初始化

参数指定的值,它并不是

指语句执行时真的使用了该优化器。决定该语句使用何种优化器的唯一方法是看后面的cost部分。例如,

如果给出的是下面的形式,则表明使用的是CBO优化器,此处的cost表示优化器认为该执行计划的代价:

SELECT STATEMENT Optimizer=CHOOSE (Cost=1234 Card=1 Bytes=14)

然而假如执行计划中给出的是类似下面的信息,则表明是使用RBO优化器,因为cost部分的值为空,或者

压根就没有cost部分。

SELECT STATEMENT Optimizer=CHOOSE Cost= SELECT STATEMENT Optimizer=CHOOSE

这样我们从Optimizer后面的信息中可以得出执行该语句时到底用了什么样的优化器。特别的,如果

Optimizer=ALL_ROWS| FIRST_ROWS| FIRST_ROWS_n,则使用的是CBO优化器;如果Optimizer=RULE,则使

用的是RBO优化器。

cost属性的值是一个在oracle内部用来比较各个执行计划所耗费的代价的值,从而使优化器可以选择最好

的执行计划。不同语句的cost值不具有可比性,只能对同一个语句的不同执行计划的cost值进行比较。

[:Q65001] 表明该部分查询是以并行方式运行的。里面的数据表示这个操作是由并行查询的一个slave进

程处理的,以便该操作可以区别于串行执行的操作。

[ANALYZED] 表明操作中引用的对象被分析过了,在数据字典中有该对象的统计信息可以供CBO使用。

例2:

假定A、B、C都是不是小表,且在A表上一个组合索引:A(a.col1,a.col2) ,注意a.col1列为索引的引导

列。

考虑下面的查询: select A.col4

from A , B , C

where B.col3 = 10 and A.col1 = B.col1 and A.col2 = C.col2 and C.col3 = 5 Execution Plan

---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE 1 0 MERGE JOIN 2 1 SORT (JOIN) 3 2 NESTED LOOPS

4 3 TABLE ACCESS (FULL) OF 'B'

5 3 TABLE ACCESS (BY INDEX ROWID) OF 'A'

6 5 INDEX (RANGE SCAN) OF 'INX_COL12A' (NON-UNIQUE) 7 1 SORT (JOIN)

8 7 TABLE ACCESS (FULL) OF 'C'

Statistics

---------------------------------------------------------- 0 recursive calls 8 db block gets 6 consistent gets 0 physical reads 0 redo size

551 bytes sent via SQL*Net to client

430 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 2 sorts (memory) 0 sorts (disk) 6 rows processed

在表做连接时,只能2个表先做连接,然后将连接后的结果作为一个row source,与剩下的表做连接,在

上面的例子中,连接顺序为B与A先连接,然后再与C连接: B <---> A <---> C col3=10 col3=5

如果没有执行计划,分析一下,上面的3个表应该拿哪一个作为第一个驱动表?从SQL语句看来,只有B表

与C表上有限制条件,所以第一个驱动表应该为这2个表中的一个,到底是哪一个呢?

B表有谓词B.col3 = 10,这样在对B表做全表扫描的时候就将where子句中的限制条件(B.col3 = 10)用上

,从而得到一个较小的row source, 所以B表应该作为第一个驱动表。而且这样的话,如果再与A表做关联

,可以有效利用A表的索引(因为A表的col1列为leading column)。

当然上面的查询中C表上也有谓词(C.col3 = 5),有人可能认为C表作为第一个驱动表也能获得较好的性能

。让我们再来分析一下:如果C表作为第一个驱动表,则能保证驱动表生成很小的row source,但是看看

连接条件A.col2 = C.col2,此时就没有机会利用A表的索引,因为A表的col2列不为leading column,这

样nested loop的效率很差,从而导致查询的效率很差。所以对于NL连接选择正确的驱动表很重要。

因此上面查询比较好的连接顺序为(B - - > A) - - > C。如果数据库是基于代价的优化器,它会利用计

算出的代价来决定合适的驱动表与合适的连接顺序。一般来说,CBO都会选择正确的连接顺序,如果CBO选

择了比较差的连接顺序,我们还可以使用ORACLE提供的hints来让CBO采用正确的连接顺序。如下所示:

select /*+ ordered */ A.col4 from B,A,C

where B.col3 = 10 and A.col1 = B.col1 and A.col2 = C.col2 and C.col3 = 5

既然选择正确的驱动表这么重要,那么让我们来看一下执行计划,到底各个表之间是如何关联的,从而得

到执行计划中哪个表应该为驱动表:

在执行计划中,需要知道哪个操作是先执行的,哪个操作是后执行的,这对于判断哪个表为驱动表有用处

。判断之前,如果对表的访问是通过rowid,且该rowid的值是从索引扫描中得来得,则将该索引扫描先从

执行计划中暂时去掉。然后在执行计划剩下的部分中,判断执行顺序的指导原则

就是:最右、最上的操作

先执行。具体解释如下:

得到去除妨碍判断的索引扫描后的执行计划: Execution Plan

---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE 1 0 MERGE JOIN 2 1 SORT (JOIN) 3 2 NESTED LOOPS

4 3 TABLE ACCESS (FULL) OF 'B'

5 3 TABLE ACCESS (BY INDEX ROWID) OF 'A' 7 1 SORT (JOIN)

8 7 TABLE ACCESS (FULL) OF 'C'

看执行计划的第3列,即字母部分,每列值的左面有空格作为缩进字符。在该列值左边的空格越多,说明

该列值的缩进越多,该列值也越靠右。如上面的执行计划所示:第一列值为6的行的缩进最多,即该行最

靠右;第一列值为4、5的行的缩进一样,其靠右的程度也一样,但是第一列值为4的行比第一列值为5的行

靠上;谈论上下关系时,只对连续的、缩进一致的行有效。

从这个图中我们可以看到,对于NESTED LOOPS部分,最右、最上的操作是TABLE ACCESS (FULL) OF 'B',

所以这一操作先执行,所以该操作对应的B表为第一个驱动表(外部表),自然,A表就为内部表了。从图中

还可以看出,B与A表做嵌套循环后生成了新的row source ,对该row source进行来排序后,与C表对应的

排序了的row source(应用了C.col3 = 5限制条件)进行MSJ连接操作。所以从上面可以得出如下事实:B表

先与A表做嵌套循环,然后将生成的row source与C表做排序—合并连接。

通过分析上面的执行计划,我们不能说C表一定在B、A表之后才被读取,事实上,B表有可能与C表同时被

读入内存,因为将表中的数据读入内存的操作可能为并行的。事实上许多操作可能为交叉进行的,因为


通过分析SQL语句的执行计划优化SQL(总结).doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:最新 教科版六年级科学上册:第一单元练习题(答案)

相关阅读
本类排行
× 注册会员免费下载(下载后可以自由复制和排版)

马上注册会员

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信: QQ: