ORACLE 性能调优

xulz1984

贡献于2014-07-12

字数:0 关键词: Oracle 数据库服务器

ORACLE 交流第一群:48949977 ORACLE 性能调优 [群共享读物第三期] 3435626 不靠谱 2009-9-1 编者不靠谱按:天堂向左,DBA 往右,没啥说的,玩命学吧~~ ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 2 目录 序 言........................................................................................................................................... 8 第 1 章 性能优化概述 ................................................................................................................... 10 1.1 优化相关的问题 ..................................................................................................... 11 1.1.1 谁进行优化 ......................................................................................................... 11 1.1.2 为什么要优化 ..................................................................................................... 11 1.1.3 优化到什么程度 ................................................................................................. 11 1.2 优化阶段 ........................................................................................................................ 13 1.2.1 优化到什么程度 ................................................................................................. 13 1.2.2 数据库配置 ......................................................................................................... 13 1.2.3 添加新的应用程序 ............................................................................................. 13 1.2.4 运行过程中的优化 ............................................................................................. 13 1.3 优化目标 ........................................................................................................................ 14 1.3.1 优化的目标 ......................................................................................................... 14 1.3.2 可衡量的优化的目标 ......................................................................................... 14 1.4 常见优化问题 ................................................................................................................ 15 1.4.1 会话管理不佳 ..................................................................................................... 15 1.4.2 游标管理不佳 ..................................................................................................... 15 1.4.3 关系设计不佳 ..................................................................................................... 15 1.4.4 常见优化问题的后果 ......................................................................................... 15 1.5 生产过程中的优化步骤 ................................................................................................ 16 第 2 章 诊断与调优工具 ............................................................................................................... 17 2.1 告警日志 ........................................................................................................................ 18 2.1.1 告警日志主要内容 ............................................................................................. 18 2.1.2 在告警日志中快速查找信息 ............................................................................. 20 2.2 SQL 跟踪 ...................................................................................................................... 21 2.2.1 SQL_TRACE 的作用 ......................................................................................... 21 2.2.2 10046 事件 .......................................................................................................... 23 2.3 资料视图 ........................................................................................................................ 24 2.3.1 什么是 Oracle 资料 ............................................................................................ 24 2.3.2 v$statname 视图.................................................................................................. 26 2.3.3 v$mystat 视图 ..................................................................................................... 26 2.3.4 v$sesstat 视图 ..................................................................................................... 27 2.3.5 v$sysstat 视图 ..................................................................................................... 27 2.3.6 资料视图应用举例 ............................................................................................. 27 2.4 等待事件 ........................................................................................................................ 30 2.4.1 什么 Oracle 等待事件 ........................................................................................ 30 2.4.2 v$session_event 视图 .......................................................................................... 31 2.4.3 v$system_event 视图 .......................................................................................... 31 2.4.4 v$session_wait 视图 ........................................................................................... 31 2.5 STATSPACK .................................................................................................................. 34 2.5.1 STATSPACK 安装设置 ...................................................................................... 34 2.5.2 生成第一份 Statspack 报告 ............................................................................... 35 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 3 2.5.3 STATSPACK 报告介绍 ...................................................................................... 36 2.5.4 自动收集快照 ..................................................................................................... 37 第 3 章 I/O 调优 ............................................................................................................................ 39 3.1 进程与 I/O ..................................................................................................................... 40 3.2 调节 I/O 的指导方针 .................................................................................................... 41 3.3 相关 I/O 的视图与 STATSPACK 中的 I/O 资料 .......................................................... 43 3.4 全表扫描、索引扫描与物理读 .................................................................................... 48 3.4.1 全表扫描与索引扫描 ......................................................................................... 48 3.4.2 db_file_multiblock_read_count、区大小与全表扫描 ....................................... 49 3.4.3 全表扫描与非全扫的等待事件 ......................................................................... 50 3.4.4 全表扫描的资料 ................................................................................................. 50 3.5 DBWn 与物理写 ........................................................................................................... 51 3.6 日志文件与归档日志 .................................................................................................... 53 3.6.1 日志文件 ............................................................................................................. 53 3.6.2 日志文件的等待事件 ......................................................................................... 54 3.6.3 归档日志 ............................................................................................................. 55 第 4 章 共享池 ............................................................................................................................... 56 4.1 什么是共享池 ................................................................................................................ 57 4.1.1 共享池简介 ......................................................................................................... 57 4.1.2 设定、查看共享池的大小 ................................................................................. 58 4.1.3 10g 中设置共享池的大小 .................................................................................. 59 4.1.4 共享池调优 ......................................................................................................... 61 4.2 库缓存 ............................................................................................................................ 63 4.2.1 库缓存中的信息 ................................................................................................. 63 4.2.2 库缓存调优 ......................................................................................................... 63 4.2.3 补充:Library cache lock、Library cache pin 等待事件 .................................. 71 4.2.4 库缓存视图 ......................................................................................................... 71 4.2.5 OLAP 与 OLTP 的区别 ...................................................................................... 72 4.3 游标与共享 SQL ........................................................................................................... 73 4.3.1 游标 ..................................................................................................................... 73 4.3.2 关于游标的视图 ................................................................................................. 73 4.4 库缓存调优与 Pin 频繁使用的对象............................................................................. 82 4.4.1 使用视图进行库缓存大小测试 ......................................................................... 82 4.4.2 Pin 频繁使用的对象和大对象........................................................................... 83 4.5 保留区与 ORA-04031 ................................................................................................... 85 4.5.1 保留区 ................................................................................................................. 85 4.5.2 共享池碎片 ......................................................................................................... 87 4.6 共享池顾问 .................................................................................................................... 89 4.7 库缓存调优总结 ............................................................................................................ 91 4.8 调优字典缓存 ................................................................................................................ 92 4.9 大池 ................................................................................................................................ 93 4.10 共享池相关的闩 .......................................................................................................... 94 4.10.1 什么是闩 ........................................................................................................... 94 4.10.2 Shared pool 闩................................................................................................... 94 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 4 4.10.3 Library cache 闩 ................................................................................................ 95 第 5 章 Buffer cache 故障排除与调优 ......................................................................................... 97 5.1 Buffer cache 工作原理 .................................................................................................. 98 5.1.1 Buffer cache 作用 ............................................................................................... 98 5.1.2 Buffer cache 大小的设置 ................................................................................... 98 5.1.3 Buffer cache 工作原理简述 ............................................................................... 99 5.1.4 LRU 的应用 ...................................................................................................... 104 5.1.5 脏块与脏 LRU 链的应用 ................................................................................. 106 5.2 块的读 .......................................................................................................................... 109 5.3 块的写 .......................................................................................................................... 116 5.3.1 写脏块 ............................................................................................................... 116 5.3.2 增量检查点 ....................................................................................................... 116 5.3.3 检查点队列 ....................................................................................................... 117 5.3.4 检查点设置 ....................................................................................................... 121 5.3.5 通过视图了解与脏块有关的信息 ................................................................... 122 5.4 逻辑读的两种类型 ...................................................................................................... 128 5.5 CR 块 ........................................................................................................................... 131 5.5.1 CR 块的定义 .................................................................................................... 131 5.5.2 单一会话中的 CR 块 ....................................................................................... 131 5.5.3 多会话中的 CR 块:SELECT 篇 .................................................................... 134 5.5.4 多会话中的 CR 块:UPDATE 篇 ................................................................... 136 5.5.5 多会话中的 CR 块:Insert、Delete 篇 ........................................................... 137 5.5.6 补充说明 ........................................................................................................... 137 5.6 等待事件 ...................................................................................................................... 139 5.6.1 两个查看等待事件的脚本 ............................................................................... 139 5.6.2 cache buffers chains 与 cache buffers chains latch .......................................... 140 5.6.3 Cache Buffer LRU chain latches ....................................................................... 142 5.6.4 buffer busy waits 等待 ...................................................................................... 142 5.6.5 Free Buffer waits 等待事件 .............................................................................. 144 第 6 章 Redo Buffer 与 Java 池 ................................................................................................... 146 6.1 Redo Buffer .................................................................................................................. 147 6.1.1 重做缓存的作用 ............................................................................................... 147 6.1.2 重做缓存的作用 ............................................................................................... 147 6.1.3 重做缓存的作用 ............................................................................................... 148 6.1.4 重做缓存的作用 ............................................................................................... 149 6.2 Java 池................................................................................................................... 154 第 7 章 SGA 管理 ........................................................................................................................ 155 7.1 SGA 管理 ..................................................................................................................... 156 7.1.1 SGA_TARGET 与 SGA_MAX_SIZE .............................................................. 156 7.1.2 ASMM 内存组件和非 ASMM 内存组件的改变 ............................................ 159 第 8 章 PGA 与排序 .................................................................................................................... 161 8.1 UGA(The User Global Area):用户全局区 ............................................................ 162 8.2 CGA (The Call Global Area):调用全局区(会话区) ............................................ 163 8.3 PGA(Program Global Area):程序全局区 .............................................................. 164 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 5 8.4 PGA 的资料 ................................................................................................................. 165 8.5 PGA 的管理 ................................................................................................................. 166 8.6 工作区与工作方式 ...................................................................................................... 169 8.7 超过 PGA 目标值的情况 ............................................................................................ 173 8.8 PGA 内存的回收 ......................................................................................................... 176 8.9 相关视图 ...................................................................................................................... 177 8.10 手动 PGA 管理 .......................................................................................................... 182 8.11 再论游标 .................................................................................................................... 185 8.12 堆栈区管理 ........................................................................................................... 189 第 9 章 SQL 调优 ........................................................................................................................ 190 9.1 访问路径 ...................................................................................................................... 191 9.1.1 访问路径 ........................................................................................................... 191 9.1.2 全表扫描 ........................................................................................................... 191 9.1.3 ROWID 访问 .................................................................................................... 192 9.1.4 索引扫描 ........................................................................................................... 192 9.2 连接 .............................................................................................................................. 193 9.2.1 嵌套循环 ........................................................................................................... 193 9.2.2 排序合并连接 ................................................................................................... 194 9.2.3 HASH 连接 ....................................................................................................... 195 9.3 优化器 .......................................................................................................................... 197 9.2.1 优化器 ............................................................................................................... 197 9.2.2 CBO 生成执行计划的步骤 .............................................................................. 197 9.2.3 CBO 的组成部分.............................................................................................. 197 9.2.4 视图合并 ........................................................................................................... 198 9.4 执行计划与 Hints(提示) ........................................................................................ 203 9.4.1 使用工具查看计划 ........................................................................................... 203 9.4.2 Hints(提示) .................................................................................................. 206 9.5 大纲 .............................................................................................................................. 209 9.5.1 什么是大纲、大纲的作用、大纲如何才能被使用 ....................................... 209 9.5.2 创建大纲 ........................................................................................................... 210 9.5.3 大纲的使用 ....................................................................................................... 212 9.5.4 维护大纲 ........................................................................................................... 214 9.6 诊断工具 ...................................................................................................................... 215 9.6.1 Statspack 报告 .................................................................................................. 215 9.6.2 Explain plan for 工具 ........................................................................................ 217 9.6.3 SQL_Trace ........................................................................................................ 218 9.6.4 SQL*Plus autotrace ........................................................................................... 218 第 10 章 资料收集 ....................................................................................................................... 220 10.1 什么是资料 ................................................................................................................ 221 10.1.1 什么是资料 ..................................................................................................... 221 10.1.2 资料的收集与查看 ......................................................................................... 222 10.1.3 动态采样 ......................................................................................................... 226 10.1.4 系统资料 ......................................................................................................... 227 10.1.5 资料的导入、导出 ......................................................................................... 227 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 6 10.2 柱状图 ........................................................................................................................ 230 10.2.1 柱状图简介 ..................................................................................................... 230 10.2.2 为列创建柱状图 ............................................................................................. 231 10.2.3 实验柱状图的作用 ......................................................................................... 231 10.2.4 绑定变量窥视 ................................................................................................. 233 10.3 段层资料 .................................................................................................................... 235 10.4 Analyze ...................................................................................................................... 237 10.4.1 Analyze 简介 .................................................................................................. 237 10.4.2 收集索引资料 ................................................................................................. 238 第 11 章 存储空间管理 ............................................................................................................... 244 11.1 区的注意事项 ............................................................................................................ 245 11.2 大区的优、缺点 ........................................................................................................ 247 11.3 块 ................................................................................................................................ 248 11.4 大块的优点和缺点 .................................................................................................... 249 11.5 小块的优缺点 ............................................................................................................ 250 11.6 OLTP 与 OLAP 中区和块使用的不同 ..................................................................... 251 第 12 章 聚簇 ............................................................................................................................... 252 12.1 索引聚簇的应用与管理 ............................................................................................ 253 12.1.1 什么是索引聚簇 ............................................................................................. 253 12.1.2 索引聚簇的创建 ............................................................................................. 254 12.1.3 索引聚簇表与普通表的区别 ......................................................................... 256 12.2 HASH 聚簇的应用与管理 ........................................................................................ 260 12.2.1 什么是 HASH 聚簇 ........................................................................................ 260 12.2.2 HASH 簇的创建 ............................................................................................. 261 12.2.3 HASH 聚簇性能测试 ..................................................................................... 262 第 13 章 索引组织表 ................................................................................................................... 267 13.1 索引组织表 ................................................................................................................ 268 13.1.1 索引组织表与堆表的不同 ............................................................................. 268 13.1.2 索引组织表的创建 ......................................................................................... 268 13.1.3 索引组织表上的次要索引 ............................................................................. 272 13.1.4 映像表与位图索引 ......................................................................................... 276 13.1.5 溢出段 ............................................................................................................. 276 13.2 索引组织表相关视图 ................................................................................................ 278 第 14 章 重要的 ORACLE 特性 ................................................................................................. 279 14.1 非 B*树型索引 .......................................................................................................... 280 14.1.1 索引类型 ......................................................................................................... 280 14.1.2 压缩索引 ......................................................................................................... 280 14.1.3 反向键索引 ..................................................................................................... 282 14.2 在线重定义表 ............................................................................................................ 284 14.2.1 在线重定义表 ................................................................................................. 284 14.2.2 实例 ................................................................................................................. 284 14.3 OLTP 与 OLAP 的注意事项 ..................................................................................... 289 14.3.1 OLTP 型应用 .................................................................................................. 289 14.3.2 OLAP 型应用 ................................................................................................. 290 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 7 第 15 章 分区 ............................................................................................................................... 291 15.1 区间分区 .................................................................................................................... 292 15.1.1 分区 ................................................................................................................. 292 15.1.2 创建和管理区间分区 ..................................................................................... 292 15.1.3 分区消除 ......................................................................................................... 294 15.1.4 行移动 ............................................................................................................. 297 15.1.5 分区资料的收集 ............................................................................................. 298 15.2 HASH 分区 ................................................................................................................ 300 15.3 列表分区 .................................................................................................................... 303 15.4 组合分区 .................................................................................................................... 305 15.5 局部索引分区 ............................................................................................................ 306 15.5.1 局部索引 ......................................................................................................... 306 15.5.2 局部索引和唯一约束 ..................................................................................... 308 15.6 全局索引分区 ............................................................................................................ 312 15.6.1 全局索引 ......................................................................................................... 312 15.6.2 全局索引的应用 ............................................................................................. 316 第 16 章 实体化视图 ................................................................................................................... 318 16.1 实体化视图概述 ........................................................................................................ 319 16.2 创建实体化视图 ........................................................................................................ 320 16.2.1 创建实体化视图注意事项 ............................................................................. 320 16.2.2 创建实体化视图 ............................................................................................. 320 16.3 实体化视图刷新 ........................................................................................................ 321 16.3.1 自动刷新 ......................................................................................................... 321 16.3.2 手动刷新 ......................................................................................................... 322 16.4 查询重写 .................................................................................................................... 323 16.4.1 查询重写的概念 ............................................................................................. 323 16.4.2 查询重写示例 ................................................................................................. 323 16.4.3 启用、禁用和控制查询重写 ......................................................................... 324 16.5 DBMS_MVIEW 程序包 ........................................................................................... 326 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 8 序 言 你所管理的 Oracle 系统性能是否“极好”,你的回答可能是“否”。你的系统在正常运 行的情况下是否能降低资源的消耗?性能问题是 Oracle 系统都会碰到的问题,如何使有限 的计算机系统资源为更多的用户服务?如何保证用户的响应速度和服务质量?这些问题都 属于数据库性能优化的范畴。 为了保证 Oracle 数据库运行在最佳的性能状态下,在信息系统开发之前就应该考虑数 据库的优化策略。优化策略一般包括服务器操作系统参数调整、数据库参数调整、网络性能 调整、应用程序 SQL 语句分析及设计等几个方面,其中应用程序的分析与设计是在信息系 统开发之前完成的。 分析评价 Oracle 数据库性能主要有数据库吞吐量、数据库用户响应时间两项指标。数 据库用户响应时间又可以分为系统服务时间和用户等待时间两项,即: 数据库用户响应时间=系统服务时间+用户等待时间 因此,获得满意的用户响应时间有两个途径:一是减少系统服务时间,即提高数据库的 吞吐量;二是减少用户等待时间,即减少用户访问同一数据库资源的冲突率。 数据库性能优化包括如下几个部分: 1. 调整数据结构的设计 程序员需要考虑是否使用 Oracle 数据库的分区功能,对于经常 访问的数据库表是否需要建立索引等。 2. 调整应用程序结构设计 这一部分也是在开发信息系统之前完成的。程序员在这一步 需要考虑应用程序使用什么样的体系结构,是使用传统的 Client/Server 两层体系结构,还是 使用 Browser/Web/Database 的三层体系结构。不同的应用程序体系结构要求的数据库资源是 不同的。 3. 调整数据库 SQL 语句 应用程序的执行最终将归结为数据库中的 SQL 语句执行,因 此 SQL 语句的执行效率最终决定了 Oracle 数据库的性能。 Oracle 公司推荐使用 Oracle 语 句优化器(Oracle Optimizer)和行锁管理器(Row-Level Manager)来调整优化 SQL 语句。 4. 调整服务器内存分配 内存分配是在信息系统运行过程中优化配置的。数据库管理员 根据数据库的运行状况不仅可以调整数据库系统全局区(SGA 区)的数据缓冲区、日志缓 冲区和共享池的大小,而且还可以调整程序全局区(PGA 区)的大小。 5. 调整硬盘 I/O 这一步是在信息系统开发之前完成的。数据库管理员可以将组成同一 个表空间的数据文件放在不同的硬盘上,做到硬盘之间 I/O 负载均衡。 6. 调整操作系统参数 例如:运行在 Unix 操作系统上的 Oracle 数据库,可以调整 Unix 数据缓冲区的大小、每个进程所能使用的内存大小等参数。 实际上,上述数据库优化措施之间是相互联系的。Oracle 数据库性能恶化的表现基本 上都是用户响应时间比较长,需要用户长时间的等待。而性能恶化的原因却是多种多样的, 有时是多个因素共同造成了性能恶化的结果,这就需要数据库管理员有比较全面的计算机知 识,能够敏感地察觉到影响数据库性能的主要原因所在。另外,良好的数据库管理工具对于 优化数据库性能也是很重要的。Oracle 数据库常用的数据库性能优化工具有: 1. Oracle数据库在线数据字典 Oracle在线数据字典能够反映出Oracle的动态运行情况, 对于调整数据库性能是很有帮助的。 2. 操作系统工具 例如使用 Unix 操作系统的 Vmstat、 Iostat 等命令可以查看到系统级 内存和硬盘 I/O 的使用情况,这些工具能够帮助管理员弄清楚系统瓶颈出现在什么地方。 3. SQL 语言跟踪工具(SQL Trace Facility) SQL 语言跟踪工具可以记录 SQL 语句的执行情况,管理员可以使用虚拟表来调整实例, ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 9 并使用 SQL 语句跟踪文件调整应用程序性能。SQL 语言跟踪工具将结果输出成一个操作系 统的文件,管理员可以使用 TKPROF 工具查看这些文件。 4. Oracle Enterprise Manager(OEM) 这是一个图形的用户管理界面,用户可以使用它 方便地进行数据库管理而不必记住复杂的 Oracle 数据库管理的命令。 5. Explain Plan——SQL 语言优化命令 使用这个命令可以帮助程序员写出高效的 SQL 语言。 信息系统的类型不同,需要关注的数据库参数也是不同的。数据库管理员需要根据自己 的信息系统类型来着重考虑不同的数据库参数。在线事务处理信息系统(OLTP)、联机分析 处理 (OLAP)、数据仓库系统(Data Warehousing)的特点是什么?分别需要关注什么参数?数 据库是否需要下面的调整: 是否采用 Btree 索引或者 Bitmap 索引? 是否采用并行 SQL 查询以提高查询效率? 是否采用 PL/SQL 函数编写存储过程? 有必要的话,需要建立并行数据库以提高数据库的查询效率。 本文将从不同的角度对上面的内容进行解释和例证。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 10 第 1 章 性能优化概述 您将学习: 1. 列出数据库优化进程相关角色 2. 描述在不同的开发阶段进行的优化之间的相关性 3. 描述服务器协议 4. 列出优化目标 5. 列出最常见的优化问题 6. 描述开发和生产过程中的优化 7. 描述性能与安全性的平衡 第 一 章 性能优化概述 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 11 1.1 优化相关的问题 1.1.1 谁进行优化 与 Oracle 系统有关的任何人(系统体系结构设计者、设计人员、开发人员和数据库管理 员)在工作中都应该考虑性能和优化问题。 如果出现问题,通常首先由系统管理员(DBA)尝试解决问题。 1.1.2 为什么要优化 优化的最好方法是设计系统和应用程序,性能提高主要是通过优化应用获得的。如果 满足下列条件,则您很少会遇到性能问题: 硬件满足用户的需求、Oracle 数据库是经过认真设计的、应用程序开发人员编写了高 效的 SQL 程序。 如果在早期做了错误的决策,或者用户现在对系统的要求比原来提高了。则应该认真 考虑进一步优化以提高性能。应该定期监控数据库,以发现影响性能的瓶颈。 1.1.3 优化到什么程度 主要有两种优化形式: 速度:缩短响应时间。 第 一 节 优化相关的问题   谁进行优化   为什么优化(列出优化目标)   优化到什么程度 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 12 高吞吐量可伸缩性:在同等的响应时间或者吞吐量的基础上提高负载。 本文将讨论找出解决瓶颈的方法。用户应该可以看到优化的结果,执行任务所需要的 时间减少,并发会话的数量增加。 如果已经出现问题,或者 DBA 希望防止出现问题,就可以执行优化。例如要监控的项 目包括:临界表增长,用户执行的语句的更改,以及 I/O 在设备之间的分配。本课讨论确定 等待和瓶颈所在位置的方法,以及如何解决这些问题。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 13 1.2 优化阶段 1.2.1 优化到什么程度 只要可能,就应该从这一阶段开始优化。完善的设计可以避免很多优化问题。例如, 尽管将表完全规格划通常是减少冗余的最好办法,但是这会导致大量的表联接。取消表的规 格化,可以大大提高应用程序的性能。 1.2.2 数据库配置 即使在快速磁盘上,对其性能监视也是非常重要的。您应该规划好数据配置,使恢复时 间更短、数据访问更快。 1.2.3 添加新的应用程序 向现有系统中添加新的应用程序时,工作量会发生变化。工作量发生任何主要变化时, 都应该同时进行性能监视。 1.2.4 运行过程中的优化 建议对生产数据库使用此方法。它可以查找瓶颈,并加以解决。使用工具找出性能问 题。通过检查此数据,可以对造成瓶颈的原因进行假设,再根据假设开发并实施解决方案。 然后对数据库进行测试负载,以确定性能问题是否得到了解决。 第 二 节 优化阶段   应用程序设计和编码   数据库配置   添加新的应用程序   进行过程中的优化 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 14 1.3 优化目标 1.3.1 优化的目标 Oracle 数据库优化的主要目标时,确保用户能够尽快获得对其语句的响应。由于“尽快” 并不是一个准确的术语,因此必须通过某种方式来量化时间衡量。通常以响应时间、吞吐量、 负载或恢复时间来衡量优化目标。 可以根据服务级别来确定优化目标。例如,进程 A 必须在指定时间内完成,或者每秒 必须处理一定数量的事务。 1.3.2 可衡量的优化的目标 在优化 Oracle 数据库环境时,DBA 应该建立可衡量的优化目标。否则,很难确定何时 已经进行了足够的优化。 检查等待和瓶颈是判断是否可以提高性能的好方法。 响应时间是指用户在发出请求后多长时间收到数据,或者指更新报表或生成报表所用 的时间。 数据库可用性也是一个不错的优化目标。备份和恢复会影响可用性,关闭和启动数据 库例程来优化参数也会影响可用性。 内存利用率也是一种有效的衡量标准,因为过多的页面调度和交换会影响数据库和操 作系统的性能。内存使用情况也会影响数据库命中百分比。 例程命中百分比提供了一个很好的基准,根据它可以确定性能随时间提高还是降低。 第 三 节 优化目标   减少或消除等待   访问尽可能少的块   在内存中高速缓存块   响应时间   吞吐量   负载   恢复时间 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 15 1.4 常见优化问题 1.4.1 会话管理不佳 其中一种情况是,Web 页面频繁登陆并注销数据库,这种情况会耗费最终用户的时间 来进行登陆。 1.4.2 游标管理不佳 例如,一个应用程序在 where 子句中没有使用赋值变量。 1.4.3 关系设计不佳 例如,在将错误的列收集到表中时,可能需要许多表联接,才能生成本可以从一个表就 可以获得的输出。 1.4.4 常见优化问题的后果 会话管理不佳:使可伸缩性具有无法超越的限制,使系统比正常速度慢一两个数量级。 游标管理不佳:使可伸缩性更加有限 关系设计不佳:执行不必要的表联接,通常是由于试图使用过高的关系规格化等级造 成的(即第 n 泛式)。 第 四 节 常见优化问题   会话管理不佳(通常与中间件相关)   游标管理不佳(通常由程序员的错误引起)   关系设计不佳(通常由过分规格化引起)   常见优化问题的后果 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 16 1.5 生产过程中的优化步骤 生产系统的优化方法是在用户遇到问题之前解决问题: 1、使用 STATSPACK、Oracle Enterprise Manager 等工具来定位瓶颈或潜在的瓶颈。 2、瓶颈通常以等待事件的形式出现。请确定等待事件的原因。 3、解决等待事件的起因。这可能需要更改系统全局区的成员大小。 4、重新运行应用程序,然后使用第 1 步中所使用的工具,检查所做的更改是否对系统产生 了积极的效果。 5、如果未达到目标,请重复这个过程。 按照这个结构进行优化的原因是,重复在开发系统使用的相同优化方法不过是浪费时间。 当系统需要全面检查时,使用开发系统方法才更加有利。 第 五 节 生产过程中的优化步骤   使用工具定位瓶颈   确定瓶颈产生的原因   解决瓶颈的起因   检查该瓶颈是否已得到解决 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 17 第 2 章 诊断与调优工具 您将学习: 1. 告警日志 2. SQL 跟踪 3. 资料视图 4. 等待事件 5. STATSPACK 第 二 章 诊断与调优工具 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 18 2.1 告警日志 2.1.1 告警日志主要内容 告警日志中包括各种提示性日志信息和各种警告、错误信息。它的名字是 alert_数据库 SID.log,它的位置用初始化参数 background_dump_dest 设置,我们可以如下显示它位置: SQL> show parameter background_dump_dest NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ background_dump_dest string E:\ORACLE\PRODUCT\10.2.0\ADMIN \JJONE\BDUMP 下列重要的信息被记录在告警日志中: 1.内部错误,也就是 ORA-600 错误。这些错误通常是由 BUG 或一些 Oracle 的内部原因引起 的,所以被称为内部错误。 2.坏块错误 ORA-1578 或 ORA-1498。也就是数据文件产生了坏块。 3.影响数据库结构的操作或参数。例如 Create database 创建数据库,Startup 或 Shutdown 开始或关闭数据库,归档操作或恢复操作等。例如,当你创建表空间后,你可以在告警日志 中找到如下两部分信息: Tue Jul 22 12:15:38 2008 create tablespace exam1 datafile 'e:\oracle\exam1_1.dbaf' size 5m extent management local autoallocate blocksize 4k Tue Jul 22 12:15:39 2008 Completed: create tablespace exam1 datafile 'e:\oracle\exam1_1.dbaf' size 5m extent 第 一 节 告警日志 告警日志主要内容:  告警日志中包括各种提示性日志信息和各种警告、 错误信息 在告警日志中快速查找信息:  利用文本编辑器的搜索功能 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 19 management local autoallocate blocksize 4k 第一部分信息说明了开始创建表空间信息的时间以及你的创建表空间的语句。第二部分 说明了 Oracle 完成此语句的时间。 4.在实例启动时的非缺省参数。我们可以在告警日志中找到类似下面的信息,这些就是非 缺省参数: System parameters with non-default values: processes = 150 sga_target = 448790528 control_files = E:\ORACLE\PRODUCT\10.2.0\ORADATA\JJONE\CONTROL01.CTL, E:\ORACLE\PRODUCT\10.2.0\ORADATA\JJONE\CONTROL02.CTL, E:\ORACLE\PRODUCT\10.2.0\ORADATA\JJONE\CONTROL03.CTL db_block_size = 8192 compatible = 10.2.0.1.0 db_file_multiblock_read_count= 16 db_recovery_file_dest = E:\oracle\product\10.2.0/flash_recovery_area db_recovery_file_dest_size= 104857600 undo_management = AUTO undo_tablespace = UNDOTBS1 remote_login_passwordfile= EXCLUSIVE db_domain = dispatchers = (PROTOCOL=TCP) (SERVICE=jjoneXDB) job_queue_processes = 10 audit_file_dest = E:\ORACLE\PRODUCT\10.2.0\ADMIN\JJONE\ADUMP background_dump_dest = E:\ORACLE\PRODUCT\10.2.0\ADMIN\JJONE\BDUMP user_dump_dest = E:\ORACLE\PRODUCT\10.2.0\ADMIN\JJONE\UDUMP core_dump_dest = E:\ORACLE\PRODUCT\10.2.0\ADMIN\JJONE\CDUMP db_name = jjone open_cursors = 300 pga_aggregate_target = 148897792 5.其他 Oracle 认为需要让 DBA 知道的警告和错误信息。Oracle 尽量用最浅白的语言来描 述这些警告和错误信息,只要略懂英语,看明白这些信息的意义是很简单的。如: Mon Oct 13 20:03:27 2008 ARC0: Archiving not possible: No primary destinations ARC0: Failed to archive thread 1 sequence 1636 (4) 这段信息的第一行说明了在 2008 年 10 月 13 日的 20 点 03 分 27 秒(那天是星期一), ARC0 进程在归档序列号为 1636 的日志文件时,发生了错误。错误的原因是 No primary destinations,也就是归档目的地发生了错误。 注意,告警日志中的每项信息都以时间开头。Oracle 这样做的目的是让你更容易了解 相关的操作发生在什么时候,这是在调查错误时很重要的一点。 6.检查点信息。当 log_checkpoints_to_alert 参数设为真时,检查点信息会被记入告警日 志。如果你想详细观察检查点的行为,将此参数设为真是很有帮助的。如果你并不打算详细 了解检查点的行为,保留此参数为假就行。因为当此参数为真时,这加重了 Oracle 对告警 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 20 日志写操作的负担。 总的来说,DBA 应该经常查看告警日志,有的公司甚至用各种编程语言开发软件,定时 搜索告警日志,发现警告、错误即时的通报 DBA。我们再强调一下,告警日志中大部分的信 息,只要略懂英文就能看懂。 2.1.2 在告警日志中快速查找信息 是告警日志中快速查找感兴趣的信息是非常简单的操作,告警日志是普通的文本型文件, 我们可以用任何文本编辑软件打开它。在文本编辑软件中,都提供有搜索功能,我们只需要 利用搜索功能查找感兴趣的关键字即可。比如,想在告警日志中查找最后一次启动数据库的 时间,我们可以从告警日志尾向前搜索关键字“Start”。很快我们就可以找到类似下面的信 息: Wed Oct 15 09:47:54 2008 Starting ORACLE instance (normal) 这说明最后一次启动数据库是在 2008 年 10 月 15 日星期三的 9 点 47 分 54 秒。 第 二 节 SQL 跟踪  SQL_TRACE 的作用  10046 事件  跟踪SQL的执行可以进一步了解SQL语句执行时的 细节,这是进行精细化调化的最佳手段,也是了解、 分析 Oracle 运行原理的入门方式 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 21 2.2 SQL 跟踪 2.2.1 SQL_TRACE 的作用 使 SQL_TRACE 设置 SQL 跟踪非常简单,SQL_TRACE 是一个参数,只需要在会话层将它设 置为 TRUE,就开启了 SQL 跟踪,Oracle 将会把 SQL 语句的执行过程记录到跟踪文件中。通 过查阅跟踪文件,将利于你了解 SQL 语句的执行过程,这将帮助你调优、排故 SQL 语句。 跟踪文件的位置在 user_dump_dest 初始化参数中,如下方法可以显示出跟踪文件的位 置: SQL> show parameter user_dump_dest NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ user_dump_dest string E:\ORACLE\PRODUCT\10.2.0\ADMIN \JJONE\UDUMP 在我的主机中,跟踪文件在 E:\ORACLE\PRODUCT\10.2.0\ADMIN\JJONE\UDUMP 中。 下面我们练习一次 SQL 跟踪。 步 1:启用跟踪: SQL> alter session set sql_trace=true; 会话已更改。 注意要在会话层设置此参数,不要在实例层设置。 步 2:执行需要跟踪的 SQL 语句 SQL> select * from ui1 where id=1; ID NAME ---------- ----- 1 1 我们在此随变执行个什么语句都行,Oracle 会将此语句的执行过程记录到跟踪文件中。 步 3:关闭跟踪 SQL> alter session set sql_trace=false; 会话已更改。 注意,在执行完想要跟踪的语句后,要马上关闭跟踪。以免不必要的语句的执行过程也 被记录到跟踪文件中。这将影响阅读跟踪文件的结果。 步 4:查阅跟踪文件: 到 E:\ORACLE\PRODUCT\10.2.0\ADMIN\JJONE\UDUMP 目录中,找到日期最新的文件,它 就是 Oracle 刚刚生成的跟踪文件。在本例中,它的名字是“jjone_ora_3600.trc”。打开它 查看,此时它的信息还不是太容易理解,Oracle 为了帮助我们阅读 SQL 的跟踪文件,专门 提供了一个 TKPROF 工具,在操作系统命令提示符下(注意不是在 SQLPLUS 中),输入如下命 令: C:\>tkprof E:\ORACLE\PRODUCT\10.2.0\ADMIN\JJONE\UDUMP\jjone_ora_3600.trc sql1.txt TKPROF: Release 10.2.0.1.0 - Production on 星期三 10 月 15 12:01:00 2008 Copyright (c) 1982, 2005, Oracle. All rights reserved. tkporf 工具的使用非常简单,只需输入:“tkprof 跟踪文件 目标文件”即可,Tkprof 将会把跟踪文件处理为更容易阅读的格式,并把处理结果保存到你指定的目标文件中。目标 文件也是普通的文本型文件,可以使用任何文本编辑软件打开。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 22 在目标文件中,我们搜索 UI1,可以方便的找到所跟踪的语句,你将看到如下信息: select * from ui1 where id=1 call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 1 0.00 0.02 0 0 0 0 Execute 1 0.00 0.00 0 0 0 0 Fetch 2 0.00 0.02 3 4 0 1 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 4 0.00 0.04 3 4 0 1 Misses in library cache during parse: 1 Optimizer mode: ALL_ROWS Parsing user id: 66 Rows Row Source Operation ------- --------------------------------------------------- 1 TABLE ACCESS BY INDEX ROWID UI1 (cr=4 pr=3 pw=0 time=27320 us) 1 INDEX RANGE SCAN UI1_ID (cr=3 pr=2 pw=0 time=23332 us)(object id 52405) 在你的语句之下,首先是一个表格,它通常有三行组成,它显示该查询各阶段的重要的 执行统计数据。这三行代表查询的三个主要阶段: 解析(Parse):此阶段是 Oracle 的优化器为 SQL 语句生成执行计划的阶段。 执行(Execute):此阶段是服务器进程按照执行计划执行语句的阶段。 抓取(Fetch):此阶段是服务器进程从表中抓取结果的阶段。只有 Select 语句才需要此阶 段,Update、Insert、Delete 等这些 DML 语句并不需要抓取行。 这个表格通常包括八列,这八列意义如下: Call:说明了语句执行的每个阶段。 Count:此阶段的执行次数。 CPU:完成此阶段工作所耗的 CPU 时间,单位是毫秒。 Elapsed:完成此阶段工作所耗费的 CPU 时间再加上等待的时间,单位也是毫秒。 Disk:完成此阶段工作所用的物理读次数。 Query:完成此阶段工作所用的一致读次数。 Current:完成此阶段工作所用的当前读次数。Query 和 Current 加起来就是逻辑读。 Rows:完成此阶段工作所操作的行数。 接下来一行:“Misses in library cache during parse:”说明硬解析的次数,本例中语句 的执行进行了一次硬解析。 “Optimizer mode”是优化器模式,这个我们会在 SQL 调优章节中详细讲述它的意义。 最后几行是执行计划,这也是后面章节的内容。 另外,我们在目标文件中还会发现大量的其他语句,注意,我们在跟踪其间,只执行了 一条 SQL 语句,跟踪文件中其他的语句,其实就是所谓的递归调用。也就是为了完成我们的 语句,Oracle 内部执行的一些其他的 SQL 语句。 通过观察跟踪文件的目标文件,我们可以观察到比较精确的 CPU 时间、物理读逻辑读等 信息,这有助于我们了解一条 SQL 语句是否有效。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 23 2.2.2 10046 事件 我们用 10046 事件也能完成 SQL 跟踪,只需要将“alter session set sql_trace=true” 换成“alter session set events "10046 trace name context forever, level 1" ”, 就可以完成一模一样的工作,这条语句也是开启 SQL 跟踪。结束跟踪的语句是: alter session set events "10046 trace name context off"; ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 24 2.3 资料视图 2.3.1 什么是 Oracle 资料 一个好的软件,必然是可探知的。它会提供各种手段,让使用者了解软件的运行状态, 以使使用者可以更好的调节软件的运行。作为大型数据库的佼佼者,Oracle 当然也是可探 知的。Oracle 会在运行时记录很多运行数据,这些数据我们可以称其为“资料”。虽然记录 资料这个操作也需要消耗额外的 CPU、I/O 等重要资源,而且这必然对数据库的性能有些影 响,但是,你是想出了问题一筹莫展好,还是在有了问题可以有众多线索供你查寻好呢?答 案当然是后者了。而且Oracle记录的资料越多,这将越有利于我们了解Oracle的运行状态, 也越有利于调优、排故,但同时额外的性能负担也越大。因此,我们可以通过初始化参数 statistics_level 控制 Oracle 记录、收集的资料数量。这个参数有三个值:ALL、TYPICAL、 BASIC。ALL 是收集所有资料,BASIC 是只收集最基本的资料。TYPICAL 是默认值,它将收集 大多数重要的资料,并且它对性能的负面影响比较小。在绝大多数情况下保留此参数的值为 TYPICAL 就行,因为在这级别下所收集的资料是足够丰富的。有一个视图 V$STATISTICS_LEVEL,其中记录着各种资料是否被收集,它有如下的列: SQL> desc V$STATISTICS_LEVEL; 名称 ------------------------------ STATISTICS_NAME : 资料类型名。 DESCRIPTION :对资料类型的简单解释。 SESSION_STATUS : 在会话层,是否启用资料收集。它有两个值,ENABLED(开启)、DISABLED (关闭)。 第 三 节 资料视图  什么是 Oracle 资料  v$statname 视图  v$mystat 视图  v$sesstat 视图  v$sysstat 视图  资料视图应用举例 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 25 SYSTEM_STATUS :在实例层,是否启用资料收集。同上,它也有两个值,ENABLED(开启)、 DISABLED(关闭)。 ACTIVATION_LEVEL:说明此类型资料的收集在 statistics_level 资料设为何种值时被开启。 STATISTICS_VIEW_NAME:应该在哪种视图中查寻资料。 SESSION_SETTABLE:说明此类型的资料收集的开启或关闭是否可以在会话层设置。 如下语句显示各种类型资料的收集是否开启: SQL> set linesize 500 SQL> col DESCRIPTION for a120 SQL> col STATISTICS_VIEW_NAME for a30 SQL> col STATISTICS_NAME for a40 SQL> select STATISTICS_NAME, SESSION_STATUS,SYSTEM_STATUS, ACTIVATION_LEVEL, STATISTICS_VIEW_NAME, SESSION_SETTABLE, DESCRIPTION from v$statistics_level; STATISTICS_NAME SESSION_ SYSTEM_S ACTIVAT STATISTICS_VIEW_NAME SE ---------------------------------------- -------- -------- ------- ------------------------------ -- Buffer Cache Advice ENABLED ENABLED TYPICAL V$DB_CACHE_ADVICE NO MTTR Advice ENABLED ENABLED TYPICAL V$MTTR_TARGET_ADVICE NO Timed Statistics ENABLED ENABLED TYPICAL YES Timed OS Statistics DISABLED DISABLED ALL YES Segment Level Statistics ENABLED ENABLED TYPICAL V$SEGSTAT NO PGA Advice ENABLED ENABLED TYPICAL V$PGA_TARGET_ADVICE NO Plan Execution Statistics DISABLED DISABLED ALL V$SQL_PLAN_STATISTICS YES Shared Pool Advice ENABLED ENABLED TYPICAL V$SHARED_POOL_ADVICE NO Modification Monitoring ENABLED ENABLED TYPICAL NO Longops Statistics ENABLED ENABLED TYPICAL V$SESSION_LONGOPS NO Bind Data Capture ENABLED ENABLED TYPICAL V$SQL_BIND_CAPTURE NO STATISTICS_NAME SESSION_ SYSTEM_S ACTIVAT STATISTICS_VIEW_NAME SE ---------------------------------------- -------- -------- ------- ------------------------------ -- Ultrafast Latch Statistics ENABLED ENABLED TYPICAL NO Threshold-based Alerts ENABLED ENABLED TYPICAL NO Global Cache Statistics ENABLED ENABLED TYPICAL NO Active Session History ENABLED ENABLED TYPICAL V$ACTIVE_SESSION_HISTORY NO Undo Advisor, Alerts and Fast Ramp up ENABLED ENABLED TYPICAL V$UNDOSTAT NO 已选择 16 行。 以上的显示结果因版面关系,省略了 DESCRIPTION 列。通过 SESSION_STATUS 列和 SYSTEM_STATUS 列,我们可以看到除了 Timed OS Statistics(操作系统时间资料)和 Plan Execution Statistics(执行计划资料)外,在 TYPICAL 状态下,Oracle 收集其他所有类 型的资料。这两种类型的资料,只在 statistics_level 为 ALL 时才会收集。有三种资料可 以在会话层修改,它们是:Timed Statistics、Timed OS Statistics 和 Plan Execution Statistics。除了 STATISTICS_VIEW_NAME 列所记录的视图外,STATISTICS_VIEW_NAME 列为 空的资料大多数都被记录在 V$sysstat、V$sesstat 系列资料视图中。Oracle 中大部分的资 料都被记录在 V$sysstat 等系列资料视图中,在 10gR2 中,共包括 347 项资料,这一系列资 料的视图将是我们下面介绍的重点。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 26 2.3.2 v$statname 视图 此视图中记录 347 项资料每项具体的名字、资料编号、资料 ID 和资料所属类型。它的 列为: SQL> desc v$statname 名称 ------------------- STATISTIC# :资料编号。 NAME :资料名。 CLASS : 资料所属类型。 STAT_ID :资料 ID。 其中,资料有如下八种类型:  1 – User :用户相关资料。  2 – Redo :相关重做方面的资料。  4 – Enqueue :相关队列锁方面的资料。  8 – Cache :相关缓存方面的资料。  16 – OS :相关操作系统方面的资料。  32 - Real Application Clusters :相关 RAC(Real Application Clusters)方面的 资料。  64 – SQL :相关 SQL 语句方面的资料。  128 – Debug :相关排故方面的资料。 想看例子的话先不要急,我们下面马上就会有一个例子。 2.3.3 v$mystat 视图 此视图中只记录相关当前会话的资料。 SQL> desc v$mystat 名称 ------------------- SID : 当前会话的 SID(会话编号,Seesion identifier)。 STATISTIC# :资料编号 VALUE :当前会话中此资料的具体值。 此资料中没有资料名,如果你想知道资料名,通常可以和 V$statname 联合查询,以 STATISTIC#列为条件进行等值连接。 此视图还有一个经常用到的作用,就是查看当前会话的 SID。只需要发布如下语句,就 可看到当前会话的 SID: SQL> select sid from v$mystat where rownum=1; SID ---------- 156 当前会话的 SID 为 156。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 27 2.3.4 v$sesstat 视图 此视图分别记录了所有会话的资料。此视图的列和 V$mystat 一样。可以说 V$mystat 是 V$sesstat 的一个子集。如果你只想在 V$sesstat 中查找某一会话的资料,可以发布类似 如下的语句: select * from v$sesstat where sid=会话 SID; 和 V$mystat 一样,此视图没有资料名列,你通常需要和 V$statname 联合查询。 2.3.5 v$sysstat 视图 此视图记录所有会话中资料的合计值,它相当于 V$sesstat 视图的合计。此视图的列比 上两个视图多一些: SQL> desc v$sysstat 名称 ---------------------- STATISTIC# :资料编号。 NAME :资料名。 CLASS :资料类型。 VALUE :资料值。 STAT_ID :资料 ID。 2.3.6 资料视图应用举例 下面我们以软、硬解析为例,练习一下这些 V$systat 系列视图的使用。 步 1:查看练习会话的 SID: SQL> select sid from v$mystat where rownum=1; SID ---------- 156 步 2:在另一会话查寻 156 号会话当前的解析资料: SQL> select name,value from v$sesstat a , v$statname b where a.statistic#=b.statistic# and b.name like 'parse count%' and a.sid=156; NAME VALUE ---------------------------------------------------------------- ---------- parse count (total) 54 parse count (hard) 31 parse count (failures) 0 通过查寻,可知在 156 号会话中,总共进行过 54 次语句解析,此项资料记录在 parse count (total)中。而 parse count (hard)中记录这其中的硬解析次数,在本例中是 31 次。 parse count (failures)记录解析无效的次数。本例中为 0 次。资料中没有软解析的资料, 其实只要用 parse count (total) 减去 parse count (hard),结果就是软解析次数。另外, 注意一定要在另一会话中观察 156 号会话的资料,否则,在 156 号会话中观察自己会话中的 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 28 资料,观察语句本身会对结果有一定的影响。 步 3:在 156 号会话中执行一条从未执行过的语句: SQL> select * from ui2 where id=1; ID NAME ---------- ------------------------------ 1 1 步 4:再次在另一会话中观察 156 号会话的解析次数: SQL> select name,value from v$sesstat a , v$statname b where a.statistic#=b.statistic# and b.name like 'parse count%' and a.sid=156; NAME VALUE ---------------------------------------------------------------- ---------- parse count (total) 68 parse count (hard) 32 parse count (failures) 0 可以看到硬解析增加了一次,从 31 增加到了 32。而软解析从 54 增加到了 68,增加了 14 次。 步 4:再次在 156 号会话中执行和刚才同样的语句: SQL> select * from ui2 where id=1; ID NAME ---------- ------------------------------ 1 1 这一次的解析,就应该是软解析了,让我们查看资料视图来确认一下。 步 5:在另一会话中查看资料: SQL> select name , value from v$sesstat a , v$statname b where a.statistic#=b.statistic# and b.name like 'parse count%' and a.sid=156; NAME VALUE ---------------------------------------------------------------- ---------- parse count (total) 69 parse count (hard) 32 parse count (failures) 0 硬解析次数没有增加,总的解析次数从 68 增加到了 69,增加了一次。这一次就是软解 析。 步 6:顺带观察一次 parse count (failures):解析无效 先如下在 156 号会话中发出一条错误的语句。 SQL> select * from ui where id=1; select * from ui where id=1 * 第 1 行出现错误: ORA-00942: 表或视图不存在 再查看资料: SQL> select name , value from v$sesstat a , v$statname b where a.statistic#=b.statistic# and b.name like 'parse count%' and a.sid=156; NAME VALUE ---------------------------------------------------------------- ---------- ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 29 parse count (total) 72 parse count (hard) 33 parse count (failures) 1 可以看到 parse count (failures)增加了一次。同时,总的解析次数增加了 3 次,而 硬解析也增加了一次。这是因为语句没有执行成功,但语句所引发的一部分递归调用被成功 解析了,因此,软、硬解析次数都有所增加。 好了实例结束了,希望通过这个实验,可以让你对 V$sysstat 这个系列的资料视图有个 大概的了解。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 30 2.4 等待事件 2.4.1 什么 Oracle 等待事件 等待事件和资料有很大的类似之处,它们都是 Oracle 记录的运行时的状态信息,它们 都可以算做 Oracle 的运行资料,只不过等待事件可以说是一种特殊的、作用巨大的资料, 它还可以简称为事件。在 Oracle 10gR2中,提供了872个等待事件。我们可以在 V$EVENT_NAME 中看到所有的等待事件,此视图有如下的列: SQL> desc V$EVENT_NAME; 名称 ------------------------- EVENT# :等待事件编号。 EVENT_ID :等待事件 ID。 NAME :等待事件名。 PARAMETER1 :等待事件的参数 1。 PARAMETER2 :等待事件的参数 2。 PARAMETER3 :等待事件的参数 3。 WAIT_CLASS_ID :等待事件的类型 ID。 WAIT_CLASS# :等待事件的类型编号。 WAIT_CLASS :等待事件的类型。 需要注意的是 PARAMETER1、PARAMETER2 和 PARAMETER3 这三列,它们可以简称为 P1、 P2 和 P3。它们是在事件发生时,Oracle 记录下来,用于帮助 DBA 解决问题的三个数据,又 称三个事件参数。有些事件有可能只记录其中的一或两个数据。也就是这三个参数有可能只 第 四 节 等待事件  什么是 Oracle 等待事件  等待事件相关的视图: - v$session_event 视图 - v$system_event 视图 - v$session_wait 视图 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 31 有一、两个有值。 2.4.2 v$session_event 视图 此视图记录每个会话中相关的等待事件。它有如下重要的列:  SID:session 标识  EVENT:session 等待的事件  TOTAL_WAITS:此 session 当前事件的总等待数  TIME_WAITED:此 session 总等待时间(单位,百分之一秒)  AVERAGE_WAIT:此 session 当前事件平均等待时间(单位,百分之一秒)  TOTAL_TIMEOUTS:等待超时次数 2.4.3 v$system_event 视图 此视图是 V$session_event 视图信息的汇总。此视图除没有 SID 列外,其他列和 V$session_event 相同。此视图可以用来从总体上确定问题。 2.4.4 v$session_wait 视图 这是一个寻找性能瓶颈的关键视图。它提供了任何情况下会话在数据库中当前正在等待 什么(如果会话当前什么也没在做,则显示它最后的等待事件)。当系统存在性能问题时,本 视图可以做为一个起点指明探寻问题的方向。 V$SESSION_WAIT 中,每一个连接到实例的会话都对应一条记录。V$SESSION_WAIT 中的 常用列有:  SID: 会话标识  EVENT: 会话当前等待的事件,或者最后一次等待事件。  WAIT_TIME: 会话等待事件的时间(单位,百分之一秒)如果本列为 0,说明会话当前还 未有任何等待。  SEQ#: session 等待事件将触发其值自增长  P1, P2, P3: 等待事件中等待的详细资料  P1TEXT, P2TEXT, P3TEXT: 解释说明 p1,p2,p3 事件 附注: 1.State 字段有四种含义﹕ (1)Waiting:SESSION 正等待这个事件。 (2)Waited unknown time:由于设置了 timed_statistics 值为 false,导致不能得到时间 信息。表示发生了等待,但时间很短。 (3)Wait short time:表示发生了等待,但由于时间非常短不超过一个时间单位,所以没有 记录。 (4)Waited knnow time:如果会话等待然后得到了所需资源,那么将从 waiting 进入本状态。 2.Wait_time 值也有四种含义: (1)值>0:最后一次等待时间(单位:10ms),当前未在等待状态。 (2)值=0:session 正在等待当前的事件。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 32 (3)值=-1:最后一次等待时间小于 1 个统计单位,当前未在等待状态。 (4)值=-2:时间统计状态未置为可用,当前未在等待状态。 3.Wait_time 和 Second_in_wait 字段值与 state 相关: (1)如果 state 值为 Waiting,那么 wait_time 值无用。Second_in_wait 值是实际的等待时 间(单位:秒)。 (2)如果 state 值为 Wait unknow time,那么 wait_time 值和 Second_in_wait 值都无用。 (3)如果 state 值为 Wait short time,那么 wait_time 值和 Second_in_wait 值都无用。 (4)如果 state 值为 Waiting known time,那么 wait_time 值就是实际等待时间(单位:秒), Second_in_wait 值无用。 示例 1:列出当前系统的等待事件 SELECT event, sum(decode(wait_time,0,1,0)) "Curr", sum(decode(wait_time,0,0,1)) "Prev", count(*)"Total" FROM v$session_wait GROUP BY event ORDER BY count(*); EVENT Prev Curr Tot --------------------------------------------- ---- ----- ----- PL/SQL lock timer 0 1 1 SQL*Net more data from client 0 1 1 smon timer 0 1 1 pmon timer 0 1 1 SQL*Net message to client 2 0 2 db file scattered read 2 0 2 rdbms ipc message 0 7 7 Enqueue 0 12 12 pipe get 0 12 12 db file sequential read 3 10 13 latch free 9 6 15 SQL*Net message from client 835 1380 2215 这个按事件和 wait_time 的分组查询列出下列的信息:  多数的会话都是空闲事件如:SQL*Net message from client, pipe get, PMON timer 等。  会话的 cpu 占用可以通过上次会话的非等待事件大致算出,除此问题外:看起来多数会 话没有在等待什么事情,但其最后等待事件都是 SQL*Net message from client。 示例 2:列出指定 ID 的等待事件 select * from v$session_wait where sid=100; 示例 3:应用 p1,p2,p3 进行等待事件的分析 v$session_wait 视图的列代表的缓冲区忙等待事件如下: P1—与等待相关的数据文件的全部文件数量。 P2—P1 中的数据文件的块数量。 P3—描述等待产生原因的代码。 例: select p1 "File #", p2 "Block #", p3 "Reason Code" from v$session_wait ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 33 where event = 'buffer busy waits'; 如果以上查询的结果显示一个块在忙等待,以下的查询将显示这一块的名称和类型: select owner, segment_name, segment_type from dba_extents where file_id = &P1 and &P2 between block_id and block_id + blocks -1; 我们也可以查询 dba_data_files 以确定等待的文件的 file_name,方法是使用 v$session_wait 中的 P1。 从 v$session_wait 中查询 P3(原因编码)的值可以知道会话等待的原因。原因编码的范 围从 0 到 300,下列为部分编码所代表的事项: 0 块被读入缓冲区。 100 我们想要 NEW(创建)一个块,但这一块当前被另一会话读入。 110 我们想将当前块设为共享,但这一块被另一会话读入,所以我们必须等待 read()结束。 120 我们想获得当前的块,但其他人已经将这一块读入缓冲区,所以我们只能等待他人的读 入结束。 130 块被另一会话读入,而且没有找到其它协调的块,所以我们必须等待读的结束。缓冲区 死锁后这种情况也有可能产生。所以必须读入块的 CR。 200 我们想新创建一个 block,但其他人在使用,所以我们只好等待他人使用结束。 210 会话想读入 SCUR 或 XCUR 中的块,如果块交换或者会话处于非连续的 TX 模式,所以等 待可能需要很长的时间。 220 在缓冲区查询一个块的当前版本,但有人以不合法的模式使用这一块,所以我们只能等 待。 230 以 CR/CRX 方式获得一个块,但块中的更改开始并且没有结束。 231 CR/CRX 扫描找到当前块,但块中的更改开始并且没有结束。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 34 2.5 STATSPACK 2.5.1 STATSPACK 安装设置 Oracle Statspack 从 Oracle8.1.6 开始被引入 Oracle,并马上成为 DBA 和 Oracle 专 家用来诊断数据库性能的强有力的工具。通过 Statspack 我们可以很容易的确定 Oracle 数 据库的瓶颈所在,记录数据库性能状态,也可以使远程技术支持人员迅速了解你的数据库运 行状况。因此了解和使用 Statspack 对于 DBA 来说至关重要。 下面我们来讲一讲 Statspack 的安装,配置,使用和解读。 1.设置初始化参数 为了能够顺利安装和运行 Statspack 你可能需要设置以下初始化参数:  job_queue_processes :为了能够建立自动任务,执行数据收集,该参数需要大于 0。 你可以在初试化参数文件中修改该参数,设置方法如下: SQL> alter system set job_queue_processes = 6; System altered  timed_statistics :收集操作系统的计时信息,这些信息可被用来显示时间等统计信 息、优化数据库和 SQL 语句。要防止因从操作系统请求时间而引起的开销,请将该值 设置为 False。使用 statspack 收集统计信息时建议将该值设置为 TRUE,否则收集的 统计信息大约只能起到 10%的作用,将 timed_statistics 设置为 True 所带来的性能影 响与好处相比是微不足道的。该参数使收集的时间信息存储在在 V$SESSTATS 和 V$SYSSTATS 等动态性能视图中。Timed_statistics 参数可以在实例级进行更改。修改 语句如下: SQL> alter system set timed_statistics = true; 第 五 节 STATSPACK STATSPACK,Oracle 中最重要的调优、诊断工具   STATSPACK 安装设置   生成第一份 Statspack 报告   STATSPACK 报告介绍   自动收集快照 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 35 System altered 如果你担心一致启用 timed_statistics 对于性能的影响,你可以在使用 statspack 之 前在实例层更改此参数,采样过后把该参数动态修改成 false。 2.创建相应表空间 创建存储数据的表空间,如果采样间隔较短,周期较长,打算长期使用,那么你可能需 要一个大一点的表空间,如果每个半个小时采样一次,连续采样一周,数据量是很大的。本 例创建一个 150M 的测试表空间,这已经是 10g 中所支持的最小表空间了。 SQL> create tablespace stpk datafile 'D:\oracle\product\10.2.0\oradata\one1\stpk1.dbf' size 150m; 表空间已创建。 (表空间至少 150M 左右) 接下来我们就可以开始安装 Statspack 了。这期间会提示你输入缺省表空间和临时表空 间的名字,输入我们为 perfstat 用户创建的表空间和你的临时表空间。 SQL> @?\rdbms\admin\spcreate 在这一步,如果出现错误,那么你可以运行 spdrop.sql 脚本来删除这些对象。然后重 新运行 spcreate.sql 来创建这些对象。下面的语句将删除 spcreate 创建的对象: SQL> @?\rdbms\admin\spdrop ; 删除 STATSPACK 所有相关的组件 2.5.2 生成第一份 Statspack 报告 在生成报告之前,要先有快照。运行 statspack.snap 可以产生系统快照,运行两次, 然后执行 spreport.sql 就可以生成一个基于两个时间点的报告。 SQL> exec statspack.snap PL/SQL 过程已成功完成。 (隔一会儿再生成一个快照) SQL> exec statspack.snap PL/SQL 过程已成功完成。 生成报告: SQL> @?\rdbms\admin\spreport Current Instance ~~~~~~~~~~~~~~~~ DB Id DB Name Inst Num Instance ----------- ------------ -------- ------------ 2601013134 ONE1 1 sone1 Specify the Begin and End Snapshot Ids ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 输入 begin_snap 的值: 1 Begin Snapshot Id specified: 1 输入 end_snap 的值: 2 Specify the Report Name ~~~~~~~~~~~~~~~~~~~~~~~ The default report file name is sp_1_2. To use this name, ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 36 press to continue, otherwise enter an alternative. 输入 report_name 的值: aaa.txt ( „„„„ 略 „„„„ ) 2.5.3 STATSPACK 报告介绍 1.数据库概要信息 DB Id DB Name Inst Num Instance ----------- ------------ -------- ------------ 2601013134 ONE1 1 sone1 2.数据库采样时段 这一部分记录了数据库采样的时间,以及采样点数,这部分信息对于报告来说是十分重 要的。任何统计数据都需要通过时间纬度来衡量,离开了时间,任何数据都失去了意义。 Snap Id Snap Time Sessions Curs/Sess Comment ------- ------------------ -------- --------- ------------------- Begin Snap: 508 10-Nov-03 15:27:29 76 39.4 End Snap: 511 10-Nov-03 15:57:42 66 35.4 Elapsed: 30.22 (mins) 3.主要性能指标说明: (1)、Execute to Parse %执行分析比率 Instance Efficiency Percentages (Target 100%) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Buffer Nowait %: 100.00 Redo NoWait %: 100.00 Buffer Hit %: 99.81 In-memory Sort %: 100.00 Library Hit %: 98.75 Soft Parse %: 97.05 Execute to Parse %: 44.21 Latch Hit %: 94.79 Parse CPU to Parse Elapsd %: 11.74 % Non-Parse CPU: 96.08 执行分析比率计算公式如下:100 * (1 - Parses/Executions) = Execute to Parse 所以如果系统 Parses > Executions,就可能出现该比率小于 0 的情况.该参数计算来自以 下部分: Instance Activity Stats for DB: ONE1 Instance: one1 Snaps: 1-2 Statistic Total per Second per Trans --------------------------------- ------------------ -------------- ------------ exchange deadlocks 481 0.2 0.0 execute count 4,873,158 1,968.2 94.4 „„„„„ parse count (failures) 542 0.2 0.0 parse count (hard) 80,281 32.4 1.6 parse count (total) 2,718,643 1,098.0 52.6 parse time cpu 44,009 17.8 0.9 parse time elapsed 374,902 151.4 7.3 „„„„„„„„ . 通过公式及以上两个数值: 100 * (1 - Parses/Executions) = Execute to Parse ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 37 100 * (1 - 2,718,643/4,873,158) = 0.44211884777797067117462 * 100 = 44.21 该值<0 通常说明 shared pool 设置或效率存在问题造成反复解析,reparse 可能较严重, 或者可是同 snapshot 有关。如果该值为负值或者极低,通常说明数据库性能存在问题。 (2)、Parse CPU to Parse Elapsd % :来自 parse time cpu 和 parse time elapsed 100*(parse time cpu / parse time elapsed)= Parse CPU to Parse Elapsd % 100*(44,009 / 374,902)= 11.7388010733471680599196590% = 11.74% (3)、Rollback per transaction 平均事务回滚率 % Blocks changed per Read: 0.37 Recursive Call %: 1.14 Rollback per transaction %: 38.22 Rows per Sort: 11.83 如果回滚率过高,可能说明你的数据库经历了太多的无效操作。过多的回滚可能还会带 来 Undo Block 的竞争该参数计算公式如下: Round(User rollbacks / (user commits + user rollbacks) ,4)* 100% „„„„„ . user commits 31,910 12.9 0.6 user rollbacks 19,740 8.0 0.4 „„„„„ . 对于本例: Round(19740 / (31910 + 19740),4) = .3822 2.5.4 自动收集快照 Statspack 正确安装以后,我们就可以设置定时任务,开始收集数据了。可以使用 spatuo.sql 来定义自动任务。先来看看 spauto.sql 的关键内容: dbms_job.submit(:jobno , 'statspack.snap;' , trunc(sysdate+1/24 ,'HH') , 'trunc(SYSDATE+1/24,''HH'')', TRUE, :instno); 这个 job 任务定义了收集数据的时间间隔: 一天有 24 个小时,1440 分钟,那么: 1/24 HH 每小时一次 1/48 MI 每半小时一次 1/144 MI 每十分钟一次 1/288 MI 每五分钟一次 我们可以修改 spauto.sql 来更改执行间隔,如: dbms_job.submit(:jobno, 'statspack.snap;', trunc(sysdate+1/48,'MI'), 'trunc(SYSDATE+1/48,''MI'')', TRUE, :instno); 然后我们执行 spauto,这样我们就建立了一个每 30 分钟执行一次的数据收集计划。你 可以查看 spauto.lis 来获得输出信息: SQL> @spauto PL/SQL procedure successfully completed. Job number for automated statistics collection for this instance ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Note that this job number is needed when modifying or removing the job: JOBNO ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 38 ---------- 28 Job queue process ~~~~~~~~~~~~~~~~~ Below is the current setting of the job_queue_processes init.ora parameter - the value for this parameter must be greater than 0 to use automatic statistics gathering: NAMETYPEVALUE ------------------------------------ ----------- ------------------------------ job_queue_processesinteger 5 Next scheduled run ~~~~~~~~~~~~~~~~~~ The next scheduled run for this job is: JOB NEXT_DATE NEXT_SEC ---------- --------- ---------------- 28 15-AUG-0316:00:00 关于采样间隔,我们通常建议以 1 小时为时间间隔,对于有特殊需要的环境,可以设置 更短的,如半小时作为采样间隔,但是不推荐更短。因为 statspack 的执行本身需要消耗资 源,对于繁忙的生产系统,太短的采样对系统的性能会产生较大的影响(甚至会使 statspack 的执行出现在采样数据中)。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 39 第 3 章 I/O 调优 您将学习: 1.进程与 I/O 2.调节 I/O 的指导方针 3.相关 I/O 的视图与 STATSPACK 中的 I/O 资料 4.全表扫描、索引扫描与物理读 5.DBWn 与物理写 6.日志文件与归档日志 第 三 章 I/O 调优 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 40 3.1 进程与 I/O I/O 问题是数据库最基本的问题,这一节我们讲述有关 I/O 方面的调优与设置。首先, 我们先来了解一下 Oracle 中核心进程都会读写什么样的文件。Oracle 的主要进程与 I/O 的 关系可以参见下表: 进程名 Oracle File I/O 数据文件 日志文件 归档文件 控制文件 CKPT Write Write DBWn Write LGWR Write ARCn Read Write Read /Write SERVER Read/Write 从上表中可以看出,CKPT 进程将会写数据文件和控制文件。CKPT 进程一般在检查点时 写这两种文件。 DBWn 进程比较专一,只用来写数据文件。LGWR 也是一个专一的进程,它只写日志文件。 而 ARCn 进程将会读日志文件的内容,并写到归档日志文件中,这个操作其实就是归档操作。 同时,在归档完成后,ARCn 为更新控制文件的归档信息。SERVER 是服务器进程,它负责为 用户读写数据文件。 第 一 节 进程与 I/O 进程与 I/O  CKPT 进程的 I/O  DBWn 进程的 I/O  LGWR 进程的 I/O  ARCn 进程的 I/O  SERVER 进程的 I/O ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 41 3.2 调节 I/O 的指导方针 为了优化 I/O,我们要在操作系统层将频繁访问的文件分开存放。例如,重做日志不要 和数据文件放在一起。重做日志也不要和归档日志放在一起,还有,各个数据文件间,繁忙 的数据文件也不要放在一起。除了在操作系统层分散文件外,在数据库中,频繁访问的表, 也不要放在同一表空间中。因为我们在讲表空间那一章时就说过了,表空间在一定程度上决 定了表被存在哪个数据文件中,如果你将频繁访问的表存放在同一表空间中,它们必将被记 录到相同的数据文件中去,这将加大某些数据文件的负担,造成 I/O 性能问题。还有,表与 表相关的索引由于每次都是同时读写,因此最好也不要放在同一表空间中。关于表、索引的 设计,这一点是非常重要的,如果你参与到了应用程序的设计中,要注意提醒程序员,减少 或分散表的 I/O,关于这一点,难度较高,需要你对开发也有一定的经验,但这是最有效的 解决 I/O 性能问题的方式。一个设计上不好的应用,DBA 在后期再怎么调节、配置,性能也 不可能好到哪去。 总之,在配置、调节 I/O 时的总指导方针,就是均衡、分散 I/O。不要让某一块磁盘、 某一个数据文件、某一个表空间甚至某个表、某个块成为热点(热点,就是频繁访问的意思, 和热线意思差不多)。 除了在操作系统层合理的分布文件、在数据库中合理的分配表空间、数据文件外,均衡、 分散 I/O 的最好方法,就是使用条带了。对于数据库相关的数据文件和日志文件,已证明性 能与安全性最好就是 RAID1+0 了,特别是日志文件,由于要连续的写,RAID1+0 可以提供 最快的性能。注意日志文件一定不能使用 RAID5,这样很容易造成日志文件的性能问题,从 而影响整个数据库的工作。如果操作系统使用了 LVM,我们还可以进行二次条带, 二次条带的条带大小对数据库的 I/O 性能也有一定影响。一般的经验表明,条带大小应 该是数据库块大小的倍数,至于多大的条带大小才是合适的值,这不一定,你必须在你的存 第 二 节 调节 I/O 的指导方针 调节 I/O 的根本原则是:  均衡 I/O  分散 I/O 注意:  在操作系统层将频繁访问的文件分开存放  利用表空间分散频繁访问的对象 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 42 储设备上,以不同的条带大小做一些模拟实际应用的实验,通过实验,才能确定合适的条带 大小。 如果你的 OS 不支持条带,我们可以创建有多个数据文件的表空间,这些数据文件分布 在不同的磁盘中。在此表空间创建段,段将被平均分布到多个数据文件上。这也可以起到均 衡、分散 I/O 的效果。这种方式被 Oracle 称为手动条带。需要注意的是,对于统一区大小 的表空间,段将完全平均分布在表空间的多个数据文件中,而对于系统管理区大小的表空间, 除段的 64KB 大小的区外,其他大小的区也是平均分布在各个数据文件上的。 在前期规化完表空间后,做为合格的 DBA,关于 I/O 方面的主要工作是,随时监测表空 间、数据文件的 I/O 情况,以便可以尽早发现潜在的 I/O 性能问题。我们检测 I/O 问题主要 使用 STATSPACK 和有关 I/O 的等待事件、资料视图。注意,无论检测什么问题,等待事件和 资料视图总是我们要使用的工具,Statspack 就是建立在等待事件和资料视图的基础上的。 下面,我们先来看看 I/O 相关的资料视图。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 43 3.3 相关 I/O 的视图与 STATSPACK 中的 I/O 资料 I/O 相关的资料没有被记录在 V$SYSSTAT 系列视图中,而是单独另设了两个视图: V$FILESTAT、V$TEMPSTAT。这两个视图的列基本上相同,前一个是针对普通数据文件的,后 一个看名字就知道,是针对临时文件的。我们先来看看它们有什么重要的列: *FILE# :文件编号 *PHYRDS:物理读数量 *PHYWRTS:物理写数量 *PHYBLKRD:物理读块数 *PHYBLKWRT:物理写块数 *SINGLEBLKRDS:单块读块数 *READTIM:花费在读操作上的时间,单位是厘秒 *WRITETIM:写操作的总时间,单位是厘秒 *SINGLEBLKRDTIM:单块读的总时间,单位厘秒 AVGIOTIM:平均的 I/O 时间 LSTIOTIM:最后一次 I/O 操作的时间 MINIOTIM:最快的 I/O 操作时间 MAXIORTM:最慢的 I/O 操作时间 MAXIOWTM:写操作最慢的时间 v$tempstat 和 v$filestat 列意义完全相同,只不过它是衡量临时文件 I/O 信息的。关 于它我就不再详细说了。 注意 V$filestat 只是衡量对于数据库来说的物理 I/O。如果要访问的块在 Buffer cache 中,读写此块所消耗的时间,不计算在 V$filestat 之内。但如果要读写的块并不在 Buffer cache 中,而是在操作系统或是存储设备的缓存中,哪么访问此块所耗时间是会被记入 第 三 节 相关 I/O 的视图与 STATSPACK 中的 I/O 资料 与 I/O 相关的重要视图:  V$FILESTAT、V$TEMPSTAT STATSPACK 中的 I/O 资料:  Tablespace IO Stats  File IO Stats  File Read Histogram Stats ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 44 V$filestat 中的。 从 V$filestat 中,可以得到许多有用的信息,比如:READTIM 除以 PHYRDS,可以得到 每次物理读的平均时间。READTIM 除以 PHYBLKRD,是平均读每个块所用的时间,这个值是 衡量 I/O 性能最主要的值。通常来说,读一个块每需时间,一般来说,大部分的磁盘系统都 可以在十几毫秒左右读一个块,正常情况下最多也应该在 20 毫秒以内,超过 20 毫秒的话, 要不就是负载过大,要不就是 I/O 有问题了。注意,V$filestat 视图中记录的是实例启动 一来的累计值。直接看这些累计值,对于了解数据库 I/O 情况意义不大,最好的方法还是取 某一段时间的差值。比如,如果数据库是在上午 9 点到 11 点最为繁忙,我们可以在 9:30 到 10:00 分别显示 V$filestat,仍后用 10:00 时的值减去 9:30 的值,就可以得到 9:30 到 10:00 之间这段时间数据库的 I/O 情况。STATSPACK 已经帮我们这样做了,我们完全可以在 9:30 时抓取一个 STATSPACK 快照,再在 10:00 时再抓取一个 STATSPACK 快照,然后用这两 个快照生成报告。下面我们看一下 STATSPACK 报告中,关于 I/O 的部分: Tablespace IO Stats for DB: MYTWO Instance: mytwo Snaps: 1 -2 ->ordered by IOs (Reads + Writes) desc Tablespace ------------------------------ Av Av Av Av Buffer Av Buf Reads Reads/s Rd(ms) Blks/Rd Writes Writes/s Waits Wt(ms) ----------------- --------- ------------- -------- ------------ ----------- ----------- ------ --- ---------- UNDOTBS1 0 0 0.0 29 1 0 0.0 SYSTEM 8 0 8.8 1.0 7 0 0 0.0 USERS 2 0 10.0 7.0 0 0 0 0.0 ------------------------------------------------------------- 这些资料都是来源于 V$filestat 视图。在这里主要关注 Av Rd(ms)列 (reads per millisecond)的值,该值应该在 20ms 以内。如果该值超过 100ms,基本可以肯定存在 I/O 的 性能瓶颈。 如果上面的某些列上出现######,一般是此列数据过大,超过了显示宽度。我们可以直 接到SpReport.sql中修改列宽度的设置。在Oracle_home\rdbms\admin中,有一个sprepins.sql, 在其中找到有关 I/O 信息的语句,我的方法是在其中搜索“Tablespace IO Stats for”这串字 符,在其中你能看到如下的列格式设置: col tsname format a30 heading 'Tablespace'; col reads format 9,999,999,990 heading 'Reads' newline; col atpr format 90.0 heading 'Av|Rd(ms)' just c; col writes format 999,999,990 heading 'Writes'; col waits format 9,999,990 heading 'Buffer|Waits' col atpwt format 990.0 heading 'Av Buf|Wt(ms)' just c; col rps format 99,999 heading 'Av|Reads/s' just c; col wps format 99,999 heading 'Av|Writes/s' just c; col bpr format 999.0 heading 'Av|Blks/Rd' just c; col ios noprint 哪一列显示为####号了,只需把相关的列格式设置中,列宽度选项改的大一点就可以了。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 45 以后我们碰到同样的问题,处理方法是一样的。好,下面我们开始介绍 STATSPACK 报告中, 相关列的意义:  Reads,快照 1-2 间物理读次数(简称物理读):快照 2 时某数据文件累计物理读次数- 快照 1 时某数据文件累计物理读次数  Av Reads/s,每秒物理读:物理读/(建立快照 2 的时间-建立快照 1 的时间)  Av Rd(ms),读每块所耗时间,单位毫秒 ms:(快照 2 时某数据文件累计读时间-快照 1 时某数据文件累计读时间)/ 某数据文件物理读  Av Blks/Rd,每次物理读平均读几块:(快照 2 时的某数据文件物理读块数-快照 1 时 的某数据文件物理读块数)/快照 1-2 间物理读次数 Av Reads/s 它不并是按照某数据文件累计读时间计算出来的,而是按两个快照的间隔时 间计算出来的了。如果两个快照的间隔时间较长,且在间隔时间内读并不是太多,这个数字 可能就会比较低。其他几个衡量“读”操作的选项都是以某数据文件的累计值计算的,和快 照的间隔时间无关。也就是说,如果在快照 1 建立后马上发生了一个密集读操作,之后,直 到快照 2 建立,都没有再发生读操作,这样,快照 2 越迟建立,Av Reads/s 的值就越小,但 对其他几列没有影响。此列更多的是代表在两个快照期间读操作是否繁忙,并不为反映 I/O 的性能。  Writes,快照 1-2 间请求 DBWR 写的次数(简称物理写):快照 2 时某数据文件累计请 求 DBWR 物理写次数-快照 1 时某数据文件请求 DBWR 累计物理写次数。  Av Writes/s,每秒物理写:物理写/(建立快照 2 的时间-建立快照 1 的时间)  Buffer Waits,等待总次数:快照 2 时某数据文件累计等待次数-快照 1 时某数据文件 累计等待次数  Av Buf Wt(ms),每次等待平均等待时间,单位毫秒 ms:(快照 2 时某数据文件的累计 等待时间-快照 1 时某数据文件的累计等待时间)/两快照间累计等待次数。 注意,以上的计时单位,在基本表中都是厘秒(百分之一秒),在计算中直接将其乘以 10 化为毫秒(千分之一秒)。需要明白其基本资料的统计,都是以厘秒为单位的。 在表空间 I/O 资料后,还有数据文件 I/O 资料: File IO Stats for DB: MYTWO Instance: mytwo Snaps: 1 -2 ->ordered by Tablespace, File Tablespace Filename ------------------------ ---------------------------------------------------- Av Av Av Av Buffer Av Buf Reads Reads/s Rd(ms) Blks/Rd Writes Writes/s Waits Wt(ms) ----------------- --------- ------------- -------- ------------ ----------- ----------- ------ --- ---------- SYSTEM E:\ORACLE\ORADATA\MYTWO\SYSTEM01.DBF 8 0 8.8 1.0 7 0 0 UNDOTBS1 E:\ORACLE\ORADATA\MYTWO\UNDOTBS01.DBF 0 0 29 1 0 USERS E:\ORACLE\ORADATA\MYTWO\USERS01.DBF 2 0 10.0 7.0 0 0 0 ------------------------------------------------------------- 将数据文件的资料按表空间汇总一下,就是上面的表空间 I/O 资料了。因此,这里列的 意义和上面表空间 I/O 资料中是一样的,我就不再说了。 在 10G 中 STATSPACK 报告中,多出了如下的柱状图资料: File Read Histogram Stats DB/Inst: JJONE/jjone Snaps: 1-3 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 46 ->Number of single block reads in each time range ->ordered by Tablespace, File Tablespace Filename ------------------------ ---------------------------------------------------- 0 - 2 ms 2 - 4 ms 4 - 8 ms 8 - 16 ms 16 - 32 ms 32+ ms ------------ ------------ ------------ ------------ ------------ ------------ SYSTEM E:\ORACLE\PRODUCT\10.2.0\ORADATA\JJONE\SYSTEM01.DBF 79 1 13 32 17 11 UNDOTBS1 E:\ORACLE\PRODUCT\10.2.0\ORADATA\JJONE\UNDOTBS01.DBF 0 0 0 0 0 1 USERS E:\ORACLE\PRODUCT\10.2.0\ORADATA\JJONE\USERS01.DBF 0 0 0 1 0 2 ------------------------------------------------------------- 这里,显示两个快照期间,最为活跃的三个数据文件的 I/O 柱状图资料。意义非常明确, “0 - 2 ms”是在 0 - 2 ms 内完成的 I/O 数量。“2 - 4 ms”是在 2 到 4ms 内完成的 I/O 数量,等等,以此类推。 观察 STATSPACK 报告方法,除了要看 Av Rd(ms)是否在 20 毫秒以内外,比这一点更重 要的是注意比较历史资料。每天 DBA 都应该定时获取 Statspack 快照,比如,我们可以每隔 两小时,都以 15 分钟为间隔,来抓取快照。每天,我们都应该比对一下当时的数据和历史 数据,以 Reads 为例,如果有一天某个数据文件的 Reads 突然增加了很多,而当天你所负 责的数据库并没有增加新的应用,这很可能就代表着问题的隐患。 很不幸 I/O 问题涉及数据库的方方面面,我们没办法在这一章中讲清楚所有 I/O 问题的 解决方法,这一章的主要目的就是告诉你如何判断 I/O 出现了异常。这非常简单,就是比对 Statspack 中的资料。 很多人可能还有一个问题,就是既然有了 Statspack,我们是不是就不必要去了解 V$filestat 之类的视图了。这是不对的,这些视图仍然有用。Statspack 的快照中抓取了太 多的资料、等待事件,这影响了 Statspack 的执行速度,如果你想以比较小的时间粒度抓取 某种资料或等待事件的快照的话,就要使用视图了。比如我想以 1 分钟为间隔,观察某个数 据文件的 I/O 变化情况,我们按如下步骤进行: 步 1:创建你自己的保存抓取的快照资料表: create table my_io_stat as select * from v$filestat where 1=0; 注意,这里我的条件是 1=0,这是一个绝对无法成立的条件,我的目的,只是按照 V$filestat 的结构创建表 my_io_stat,让 my_io_stat 表和 V$filestat 表有一样的列。 然后,我再为 My_io_stat 添加一个时间列: alter table my_io_stat add stat_date date default sysdate; 步 2:然后,每隔一分钟,将第 N 号数据文件的 I/O 情况,抓取到 My_io_stat 中: insert into my_io_stat select a.*,sysdate from v$filestat a where file#=N; 这条语句完成的非常快,你可以以很高的频率,抓取所需的资料。下面是我抓取的结果: SQL> select stat_date,file#,phyrds,phywrts,phyblkrd,phyblkwrt,readtim,writetim, readtim*10/phyblkrd from my_io_stat; STAT_DATE FILE# PHYRDS PHYWRTS PHYBLKRD PHYBLKWRT READTIM WRITETIM ------------------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- 2008-09-01 16:26:59 4 454 17 5810 25 409 4 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 47 2008-09-01 16:27:00 4 456 18 5812 33 410 4 2008-09-01 16:31:56 4 456 19 5812 34 410 4 2008-09-01 16:32:01 4 456 20 5812 35 410 4 2008-09-01 16:33:31 4 872 22 12042 37 693 5 2008-09-01 16:33:43 4 1216 25 17404 40 949 7 使用这种方法,我们甚至可以以更小的频率抓取信息,比如一秒抓取一次等等。很粗的 时间粒度,只能让你在大方向上找到问题,有时候必须以更细的时间粒度来进一步定位问题。 在我的实验机上,我抓取一次 Statspack 快照: SQL> set timing on SQL> exec statspack.snap PL/SQL 过程已成功完成。 已用时间: 00: 00: 07.25 共需用时 7 秒多,这说明,当你需要更细的时间粒度时,Statspack 是不太合适的。而 我们自己的抓取工具: SQL> insert into my_io_stat select a.*,sysdate from v$filestat a where file#=4; 已创建 1 行。 已用时间: 00: 00: 00.04 只需要 0.04 秒就可完成。因此,它可以胜任细粒度的抓取。 注意,不要让你自己的资料表影响观察结果。例如,这里我要抓取 4 号文件的 I/O 情况, 哪么,我的 MY_IO_STAT 表,就不能存放在 4 号数据文件中,以免抓取资料的操作,影响 4 号文件的 I/O。 好,关于如何发现 I/O 异常,我们就讲到这里。下面我们从全表扫描、检查点、日志等 几个方面,说一下解决 I/O 问题的思路。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 48 3.4 全表扫描、索引扫描与物理读 物理读就是从磁盘上读数据。Oracle 中最多的物理读就是从磁盘上读取表数据。表中 查询数据通常有如两种执行方式:全表扫描与索引扫描。下面,我们就讨论一下这两种扫描 方式。 3.4.1 全表扫描与索引扫描 全表扫描非常简单,就是依次读取表的每一个块,查找满足条件的行,取出并反回给用 户进程。索引扫描是先在索引 B 树中查找满足条件的行,找到后根据索引中存贮的行 ROWID 信息,直接定位表中相应的行。查询少量记录时,这是一种最为有效的方法。我们在讲索引 相关章节中已经提到过了,甚至在表有上亿行的情况下,通过索引取出表中相应行,所需 I/O 取决于索引树的层数,对于上亿行的大表,索引树高度通常也就是 3 至 5 层左右。假设 索引树高 4 层,通过索引取出表中相应行,只需 5 个 I/O 而已。但是,当需要从表中取出大 量行时,通常行数占表中总行数的 10%以上时,再通过索引取行,不但不会加快速度,反而 只会影响性能,原因看下图: 第 四 节 全表扫描、索引扫描与物理读  相关 I/O 的视图与 STATSPACK 中的 I/O 资料  db_file_multiblock_read_count、区大小与全表 扫描  全表扫描与非全扫的等待事件  全表扫描的资料 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 49 上图中,第 1 个叶块中的第 1 个索引条目和第 2 个叶块中的第 10 个索引条目,指向表 中同一块:第 5 号块。索引是按顺序访问的,当访问到第 1 个索引条目时,Oracle 将表中 5 号块读进 Buffer cache,读后面的索引条目时,因为后面的索引条目并不一定也指向表中 5 号块,在此例中,后面的索引条目都指向其他的块,Oracle必须将其他的块读进Buffer cache, 而这后读进 Buffer cache 中的块,可能会使表的第 5 号块老化(也就是被移出缓存),当读 到第 10 个索引条目时,又必须重新物理读取一次表的第 5 号块。而全表扫描则不同,它每 读取一个块,必将此块中所有满足条件的行全部取出,才会物理读取下一个块。因此,如果 是取表中大量行,通过索引扫描反而会使物理读增加,因而造成性能降低。 3.4.2 db_file_multiblock_read_count、区大小与全表扫描 根据索引访问表时,访问的每一行,都可能来自不同的表块,我们无法一次读取多个块。而 在全表扫描时,因为是依次读取表的所有块,Oracle 完全可以一次读多个块,这就是多块 读,就是一次读多个块。而访问索引和根据索引访问表,都是单块读,也就是一次 I/O 只读 一个块。读同样数量的块,多块读比单块读要快。比如同样是读 10 个块,多块读只需要定 位一次磁头,就可以顺序读 10 个块了。而单块读的话,必须定位 10 次磁头。对于磁盘来说, 速度最慢环节就是磁头的定位了,因为这是一个机械操作,而其他操作都是电子操作。而机 械操作要比电子操作慢一个数量级。由于多块读可以减少磁头定位的次数,因此多块读可以 比单块读更有效率。 根据多块读的特性,它有一个限制,就是只能读连续的块。这一点很好理解,多块读本 身就是定位磁头一次,然后读多个块。如果多个块不连续,怎么能够只定位一次呢。在访问 索引或根据索引访问表时,如果需要读多个块,是不能保证这多个块连续的,因此只能一块 一块的读,每读一个块,都重新定一次位,也就是单块读。在索引中,索引列值为 1 和值为 2 的块,有可能分布在磁盘中相距甚远的位置,如果你需要访问索引列值为 1 和 2 的行,是 没办法多块读的。根据索引访问表时,我们上面已经有图演示过了,同样没办法连续的访问 块,也只能单块读。索引的访问还有根据索引访问表,都是按照索引列的顺序访问块,而无 法按照它们在磁盘上物理存放顺序访问块。只有在全表扫描时,它不会按照任何列的顺序, 而是基本上按照块在磁盘的物理存放位置,依次访问,因此,只有在全表扫描时,才可以使 用多块读。多块读一次读块的数量,用参数 db_file_multiblock_read_count 来控制。另外, ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 50 由于多块读的连续性,它还要受区的制约。因为只有区内的块,在磁盘上才是连续的。而一 个段所属区与区之间,可能并不相连。因此,如果区大小是 10 个块,多块读一次最多只能 读 10 个块。除了区的制约外,多块读还要受操作系统 I/O 大小的限制。如果操作系统一次 I/O 操作最多只能读 1MB 的数据,假设数据库的块大小是 8KB,哪么多块读一次最多只能读 128 个块。 3.4.3 全表扫描与非全扫的等待事件 db file scattered read 是全表扫描时的等待事件,db file sequential read 是索引 扫描时的等待事件。这两个事件的 P1 参数代表正读取的文件号,P2 参数是正读取的块号, P3 参数是一次读取的块数量。对于单块读的等待事件 db file sequential read,P3 参数 的值将一直都是 1。 如果这两个事件频繁出现,而且等待时间比较长,就代表着全表扫描或索引扫描出了异 常。遇到这个情况,排故的关键是要找到哪个表或索引的扫描出现了异常。要找出问题表或 索引,最好的方法就是在等待事件再次出现时,查询 V$session_wait 视图,通过 P1 和 P2 参数,确定问题表或索引。 当发现问题表或索引后,解决方法我们到后面相关的章节中说,不过有一点 DBA 要知道, 并不是所有的性能问题 DBA 都可以解决,如果性能问题的原因是你的硬件速度达到了极限, 这个时候如果不更换更好的硬件设备,单靠 DBA 是没办法解决问题了。正所谓巧夫难为无米 之炊,没有好的硬件设备的支持,单是靠 DBA 的调优技巧,是没办法解决所有问题了。 3.4.4 全表扫描的资料 在 V$sysstat 系列资料视图中,有两个资料相关于全表扫描 table scans (short tables) 和 table scans (long tables),也就是长表扫描和短表扫描,这两个资料的合计就是全表 扫描总的次数。那么,什么是短表扫描、什么是长表扫描呢?Oracle 中有一个参数控制着 这一点:_small_table_threshold,在 10g 中,它的默认值是 837,这个值的单位是块数。 只要表的大小超过 837 个块,全扫描这个表这是长表扫描。否则,就是短表扫描。 通常来说,对于 OLTP 系统,长表扫描的次数如果过高,这也代表可能有异常发生。你 应该注意随时检测 db file scattered read 等待事件,发现情况好即时处理。那么,长表 扫描的次数为多少才算是高呢,关于这一点,不同的系统有不同的值,你仍要根据历史数据 来判断。如果在以往的历史中,每天长表扫描扫描的次数大概在 500 次左右,某一天突然增 加至 800 次,这肯定就是有异常了。这种异常并不总是代表着有问题,你应该先去了解一下 公司各个部门,有没有突然增加某些新应用程序,如果数据库是在没添加任何新应用的情况 下,出现这种异常,那基本上可以说是有问题了。关于 db file scattered read 等待事件, 在 V$session_wait 中查到问题表,这有助于你进一步解决问题。 好了,这部分内容我就说到这儿,我再说两点注意事项,一是历史数据是非常重要的。 二是本章的目的是教会大家如何发现 I/O 异常,至于问题的解决,这些涉及很多方面,我们 会在后面的章节中将思路讲述给你。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 51 3.5 DBWn 与物理写 上一节我们主要描述了如何发现物理读的异常,这一节,我们讲述物理写。通过上两节 的描述,我们应该总结一下,发现 Oracle 问题的一般方法是什么。其实很简单,就是将资 料、等待事件的数据与历史数据进行对比,如果发现某项数据大幅增大或减少,就说明某个 地方有异常了,就这么简单。作为合格的 DBA,必须要十分的清楚常用资料、等待事件的意 义。我们可以在文档中查到常用资料和等待事件的意义。注意,Oracle 提供了成百上千个 资料、等待事件,我们并不需要掌握所有的,事实上文档中的介绍,也只列出了部分资料和 等待事件。对于 DBA 来说,只需掌握重要的资料、等待事件的意义就行了。下面,我们讨论 一下有关物理写的资料和等待事件。 数据库的物理写,主要有针对数据文件、日志文件和归档日志文件的,此一小节我们讨 论针对数据文件的物理写。 数据文件的物理写主要由 DBWn 进程完成。如果 DBWn 写的缓慢了,我们可以从两个事件 上看到这点。一个是 db file parallel write,直译就是数据文件并行写。注意这里面虽 然带有并行(Parallel)这个单词,但实际上这个事件和并行无关。DBWn 在写脏块到磁盘 数据文件中时,如果写操作的完成时间超过了 1 微秒(us,百万分之一秒),就会记入这个 等待事件。如果这个等待事件出现的比较多,这就说明 DBWn 有问题,或是你的存储设备太 慢。 我们如何制造一个人为的 Db file parallel write 事件呢?这一点我就不再试了,作 为一个练习大家自己试试。 除此事件外,还有一个 Write complete waits 事件,写完成等待。当一个服务器进程 想要修改正在被 DBWn 写入磁盘的块时,服务器进程必须等待 DBWn 写操作的完成,才可以修 改此块。如果服务器进程等待时间超过了 1us,就会出现“写完成等待”。大量的写完成等 待,说明了 DBWn 的缓慢,这也可能是存储设备缓存造成的。来自 Oracle 的建议是使用多个 第 五 节 DBWn 与物理写 注意以下等待事件:  Db file parallel write  Write complete waits 注意以下参数:  db_writer_processes  DBWn 进程数量 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 52 DBWn 进程。db_writer_processes 初始化参数设定了 DBWn 进程的数量。如果你有 2 个 DBWn 进程,一个叫 DBW1,另一个就叫 DBW2。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 53 3.6 日志文件与归档日志 3.6.1 日志文件 日志文件的配置,注意事项只有一个,就是同一组中的成员由于要同时写,绝对不能安 排在同一磁盘中。 重做日志文件的大小,对性能也有些细微的影响。多大的日志文件才适合你的企业,这 没有定论。只能通过观察历史数据来分析。不过,较小的日志文件将必然使日志切换更为频 率,而日志切换又会触发日志切换时的检查点(也可以说是增量检查点),而此检查点将会 写数据文件头、控制文件,同时,也会触发 DBWn 写脏块。也就是说,日志文件的切换将会 导致一连串的针对数据文件的 I/O,从增加 I/O 效率的角度上讲,日志文件是不能过小的, 以免频繁切换。一般的准则,以 20 分钟左右切换一次日志为益。而日志文件太大的话,每 次日志切换,归档进程(ARCn)必须对很大的日志文件进行归档,这将造成归档进程要么是 不忙,要么是很忙。这种间歇式的忙碌,可能会使 ARCn 进程完成的比较慢。总之,最合适 的值还要你在真实环境中测试一下才能知道。 有三个视图可以查看日志文件的信息:V$logfile、V$log 和 V$log_history。其中在 V$log_history 的 FIRST_TIME 列,可以看到每次日志切换的时间。 SQL> alter session set nls_date_format='yyyy-mm-dd hh24:mi:ss'; 会话已更改。 SQL> select sequence#, FIRST_TIME from v$log_history; SEQUENCE# FIRST_TIME ---------- ------------------- 656 2008-09-02 22:02:29 第 六 节 日志文件归档日志  日志文件  日志文件的等待事件 - log file parallel write - log file sync - log file switch completion - log file switch (checkpoint incomplete)  归档日志 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 54 657 2008-09-02 22:02:41 658 2008-09-02 22:02:49 659 2008-09-02 22:03:02 660 2008-09-03 06:44:19 661 2008-09-03 07:44:38 662 2008-09-03 09:00:39 „„„„„„„„ 可以看到,通过 First_time 列可以清楚的发现日志的切换时间。 3.6.2 日志文件的等待事件 1. log file parallel write 这个等待事件发生在 LGWR 从重做缓存中把重做记录写到日志文件中时,如果写操作完 成时间超过 1us,就会被记录在等待事件视图中。如果这个事件等待时间比较长,说明 LGWR 工作缓慢,这一般是日志文件的 I/O 问题造成的。在操作系统上用 iostat 类的命令,检测 日志文件所在磁盘的性能,可以帮助进一步确认问题。 如果问题的原因确是缓慢的存储设备引起的,DBA 对此是无能为力的,要解决问题只有 更换更快存储设备才行。 2. log file sync 这个等待事件发生在提交时。当用户发出 Commit 命令时,LGWR 必须将重做缓存中的重 做记录写入到日志文件中。而如果这个写操作不能及时的完成,就会发生此等待事件。 这个等待事件的原因有可能是日志的 I/O 速度比较慢,也有可能是提交的太过频繁了。 我们可以在 V$sysstat 系列资料视图中的 user commits 资料中,观察用户提交的次数。注 意,这个资料只有当有事务后再提交,才会增加 1。如果没有事务,你直接发出 Commit 命 令,此资料不会增加。还有 DDL 命令中的隐式提交不会导致此资料的增加。但是如果你发出 了 DML 语句,但你没有 Commit,而是在同一会话中又发出了一条 DDL 语句,DDL 语句将会自 动提交你之前开启的事务,这种提交也会让 User commits 资料值增加。 查看你记录的历史资料,你就可以知道你的提交次数是否出现异常。假如你每隔两个小 时,都做一次间隔 15 分钟的 Statspack 报告,对比以前相同时间段报告中的资料值,如果 User commits 出现了异常的增加,说明 Log file sync 等待事件是由用户提交的大量增加 引起的。有一种很常见的应用程序编写方式很容易造成这种情况,就是在循环中提交。很多 程序员认为在循环中提交有助于减少回滚段的使用,从而提高性能。告诉他们这种想法是错 误的。在循环中提交只会增加 LGWR 进程的负担,从而引出大量的 Log file sync 等待,进 而影响性能。如果你要插入、更新或删除大量行,你担心超出回滚段的负担,你可以每操作 一定数量的行再提交,千万不要每行都提交。不过,使用这样的方式要注意保证事务的完整 性,这个已经是另外一个话题了,有机会我们再说。 3. log file switch completion 此等待事件的汉字意思是日志切换完成,也就是发生日志切换时的等待。如果你有 1、 2、3,三组日志。2 号是当前联机重做日志文件,从 2 切换到 3 时,这个切换操作不能立即 完成,而发生了等待,这个等待事件就是此“日志切换完成事件”。等待的原因可能是 3 还 没有完成归档,也可能是其他原因。不过,此事件说明了日志切换太过频繁。之所以切换频 繁,可能是日志文件比较小因起的。解决此问题的方法很简单,增大日志文件,或增加新的 日志文件组。 3. log file switch (checkpoint incomplete) ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 55 这个事件也是关于日志文件切换的,括号中的意思是检查点未完成。这个事件的原因是, 当你修改数据块时,块变脏,同时会产生修改操作的重做记录,此重做记录会被很快的写进 日志文件 A 中,但它对应的脏块可能要在 Buffer cache 中停留好久。当日志文件 A 要被新 的当前联机日志文件覆盖时,此重做记录对应的脏块还没有被刷新到磁盘,那么,日志切换 就要先等待了。被覆盖的日志文件中,所有重做记录所对应的脏块都应该被刷新到磁盘数据 文件后,此日志文件才可以被覆盖。此时,就会发生 log file switch (checkpoint incomplete)等待事件。 此等待事件的原因是 DBWn 过慢,或增量检查点太不频繁。如果是增量检查点太不频繁, 我们可以增加增量检查点的频率来解决此问题。如果不是增量检查点的原因,而是 DBWn 过 慢,这一般就是存储设备的性能问题了。只有更换更快的存储设备才能解决。查看 V$instance_recovery 视图的 Estimated_mttr 列,看看估计的实例恢复时间是否过长,这 能帮助我们确定是否是增量检查点的问题。如果 Estimated_mttr 列的值过大,就说明增量 检查点太不频繁了。另外,查看 db file parallel write 和 Write complete waits 等待事 件的值是否过高,也可以判断是否是 DBWn 的问题。 3.6.3 归档日志 归档日志最好不要和日志文件存放在一起。因为在读日志文件的同时,需要写归档日志 文件。除这一点外,归档日志没有什么特别的配置。 归档的操作,是由 ARCn 进程完成的。通常进程名字后面带个小写 n,都代表这种进程 的数量可以有多个。ARCn 进程最多可以有 30 个(文档 Database Concepts 中写的有 10 个, 这是错误的)。当前 ARCn 进程的数量,可以由 LGWR 控制。当 LGWR 发觉重做记录大增,现有 的 ARCn 可能无法即时的完成归档时,LGWR 可以自动的开启更多的 ARCn 进程。当然,如果 需要的话,DBA 也可以自己动手,开启更多的 ARCn 进程。有一个 log_archive_max_processes 参数可以控制 ARCn 进程的数量。我们可以在 v$archive_processes 视图中看到 ARCn 活动的 进程的数量。 另外还有两个关于归档的视图是 V$archive_dest,此视图中记录归档目的地信息。 V$archive_log,此视图记录控制文件中所记载的所有归档日志信息。这两个视图非常简单, 大家可以自己查阅文档,我就不再说了。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 56 第 4 章 共享池 您将学习: 1. 什么是共享池 2. 库缓存 3. 游标与 SQL 4. 库缓存调优与 Pin 频繁使用的对象 5. 保留区与 ORA-04031 6. 共享池顾问 7. 库缓存调节总结 8. 调优字典缓存 9. 大池 10. 共享池相关的闩 第 四 章 共享池 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 57 4.1 什么是共享池 4.1.1 共享池简介 共享池是 SGA 中最重要的内存段之一,特别是对于性能和可扩缩性来说。共享池如果 太小,会严重影响性能,甚至导致系统看上去好像中止了一样。如果共享池太大,也会有同 样的效果。共享池使用不当会导致灾难性的后果。 那么,到底什么是共享池?共享池就是 Oracle 缓存一些“程序”数据的地方。在解析 一个查询时,解析得到的表示(representation)就缓存在那里。在完成解析整个查询的任务 之前, Oracle 会搜索共享池,看看这个工作是否已经完成。你运行的 PL/SQL 代码就在共 享池中缓存,所以下一次运行时,Oracle 不会再次从磁盘重新读取。PL/SQL 代码不仅在这 里缓存,还会在这里共享。如果有 1 000 个会话都在执行同样的代码,那么只会加载这个代 码的一个副本,并由所有会话共享。Oracle 把系统参数存储在共享池中。数据字典缓存(关 于数据库对象的已缓存信息)也存储在这里。简单地讲,就像是厨房的水池一样,什么东西 都往共享池里放。 共享池的特点是有大量小的内存块(chunk),一般为 4 KB 或更小。要记住,4 KB 并不 是一个硬性限制,可能有的内存分配会超过这个大小,但是一般来讲,我们的目标是使用小 块的内存来避免碎片问题。如果分配的内存块大小显著不同(有的很小,有的却相当大), 就可能出现碎片问题。共享池中的内存根据 LRU(最近最少使用)的原则来管理。在这方 面,它类似于缓冲区缓存,如果你不用某个对象,它就会丢掉。为此提供了一个包,名叫 DBMS_SHARED_POOL,这个包可用于改变这种行为,强制性地“钉住”共享池中的对象。 可以使用这个过程在数据库启动时加载频繁使用的过程和包,并使它们不至于老化。不过, 通常如果过一段时间共享池中的一段内存没有得到重用,它就会老化。甚至 PL/SQL 代码(可 第 一 节 什么是共享池  共享池简介  设定、查看共享池大小  10g 中设置共享池的大小  共享池的调优 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 58 能相当大)也以一种分页机制来管理,这样当你执行一个非常大的包中的代码时,只有所需 的代码会加载到共享池的小块中。如果你很长时间都没有用它,而且共享池已经填满,需要 为其他对象留出空间,它就会老化。 4.1.2 设定、查看共享池的大小 在 9i 中我们用参数 Shared_pool_size 设置共享池的大小,10g 中的设置我们下面再讲。 另需注意,无论在 9i 还是 10g 中,Shared_pool_size 参数的值都不一定代表共享池的真正 大小,实际共享池大小会和此参数的值有一些出入。如果要查看共享池的大小,可以使用如 下两种方式: 1)、使用 Show sga 下面是 Show sga 的运行结果: SQL> show sga Total System Global Area 448790528 bytes Fixed Size 1249488 bytes Variable Size 79695664 bytes Database Buffers 360710144 bytes Redo Buffers 7135232 bytes 此命令显示了 SGA 各部分的大小。这其中 Fixed size 是固定区域,这块区域用于存贮 一些管理其他内存结构的管理性信息。DBA 是不需要调节这块内存的。Database buffers 是 Buffer cache 的大小。Redo buffers 是重做缓存的大小。Variable Size 是可变区域, 共享池占了它约百分之八、九十的大小,它其中还包括 Java 池、大池等 SGA 除固定区、Buffer cache 和重做缓存之外的所有其他池。另外,也有一些管理性信息在此可变区域中。比如我 们经常使用的 V$系列动态性能视图,就源自此可变区域中的管理性信息(V$的信息来自于 X$,而 X$中的数据来自于可变区域中的一些结构)。因为共享池占了可变区域的大部分,因 此我们一般可以通过它来大概的了解共享的大小。 除 Show Sga 外,我们还可以用 V$sgastat 视图显示 SGA 各内存结构的大小,在此视图 中,我们可以精确的看到共享池的大小,下面是这个视图的显示信息的样式: SQL> select * from v$sgastat where rownum<=10; POOL NAME BYTES ------------ -------------------------- ---------- fixed_sga 1249488 buffer_cache 360710144 log_buffer 7135232 shared pool dpslut_kfdsg 256 shared pool hot latch diagnostics 80 shared pool ENQUEUE STATS 8360 shared pool transaction 264528 shared pool KCB buffer wait statistic 3352 shared pool invalid low rba queue 320 shared pool KQF optimizer stats table 2396 „„„„„„„„„„„„„„„„„„„„„„„„„„ „„„„„„„„„„„„„„„„„„„„„„„„„„ 在 10G 中,这个视图显示 600 多行,所有 POOL 列不为空的行,就是可变区域的各个部 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 59 分。POOL 为空的行,从 NAME 列可以看到,分别是固定区域、Buffer cache 和重做缓存。这 三行的大小和 Show sga 中显示的大小是一样的。下面我们按 POOL 分组,看看可变区域中各 内存池的大小: SQL> select pool,sum(bytes)/1024/1024||' MB' from v$sgastat where pool is not null group by pool; POOL SUM(BYTES)/1024/1024||'MB' ------------ ------------------------------------------- java pool 4 MB shared pool 64.00447845458984375 MB large pool 4 MB 可以看到可变区域中,有 Java pool 和 Large pool,大小都是 4MB,还有共享池大小为 64MB 多一点。将这三个池加起来,仍不到 Show sga 中显示的可变区域大小。那是因为除这 三个池外,可变区域还有着如 X$结构这样的管理性信息。 这个视图可以用在 9i 和 10g 中,不过在 10g 中另有一个视图可以详细更准确的显示共 享池大小方面的信息。下面我们来了解一下在 10g 中,对内存管理做了什么改进。 4.1.3 10g 中设置共享池的大小 在 10g 中,共享池的简单设置更加方便,我们只需要定义 SGA_TARGET 参数的值,也就 是目标 SGA 大小,Oracle 就会根据此目标值自动设置共享池的大小。而且,如果发现共享 池内存不够了,如果系统还有空闲的内存,Oracle 将可以自动扩展共享池的大小。注意, 我们刚才说了,这是共享池的简单设置,这种简单设置其实是根本不用设置。你只要告诉它 SGA 有多大,它可以自动设置 SGA 中的所有内存组件。这是 10g 相比 9i 的一大新特色,它 使 DBA 的工作更加简单化了。Oracle 本身越来越智能,再往下发展下去,很多人担心 DBA 会不会失业。因为软件越来越智能、越来越简单,总有一天将会淘汰掉 DBA。持这种观点的 人,大多数是对计算机了解的不够深入,当深入的学习下去之后,就会发现我们现在的人工 智能水平是多么的幼稚可笑,这个话题我们就不在这里讨论了。有兴趣的同学可以去看看各 种人工智能算法方面的书,如神经网络、遗传算法等等,自然就会明白我们当前的人工智能 发展水平。回到我们本身的话题,不可否认,软件是向着智能化、自动化的方面发展,但是 智能化程度越高,其本身也越复杂。就像人脑,复杂的多少科学家多少代的努力都无法完全 了解其运作原理。这么复杂的系统一旦出现问题,需要程度更高的人,才可解决。比如汽车, 由你自己驾驶,汽车出了毛病,很多地方都可以修。而我们的神舟火箭呢,它的运行轨道早 就设置好的,它不需要飞行员亲自驾驶,自动化程度比汽车高了一大截。但是,神舟火箭要 是出了问题呢,谁能修?我们数据库将来也是这样发展,随着智能化程度的提高,必将淘汰 掉大批的初级 DBA,但对高级 DBA 的需求,只会比以前更大。而且高级 DBA 的薪资水平,也 会比以前更高。因为刚才我们已经说了,智能化水平越高,软件本身越复杂。越来越复杂的 软件,对 DBA 的要求也一定会越来越高。就像我们正要讲的共享池,它的简单设置简单到不 必再设置。但是,在确定了 SGA_TARGET 大小后,如果 Oracle 自动定义的共享池大小不合适 怎么办,有很多时候我们不能完全依赖软件的智能,显然 Oracle 公司也清楚的明白这一点, 因此,它还是保留了一些让 DBA 手动调整共享池等 SGA 内存组件的权利,10G 下的调整比 9i 理解起来稍微复杂一点,下面我们说一下 10g 下共享池的调整注意事项。 在 10g 中还是要用 Shared_pool_size 参数调整共享池,但它已经不再是共享池大小的 决定因素。在 10g 中它只决定了共享池最小有多大,也就是它是共享大小的下限。如果你将 它设置为 1G,那么 Oracle 绝不会分配低于 1G 内存的共享池。它可能正好按你的要求分配 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 60 1G,也可能分配多于 1G 内存的共享池。通常,如果你使用此参数将共享池设置的比以前更 大,那么这个设置将会马上生效。而如果你发现共享池有些大了,应该设置的更小些才合适, 你用此参数将共享池设置的小了一些,那么这个设置将不会马上生效。为什么呢?因为此参 数只是共享池大小的下限。你的共享池本来有 1.5G,你将此参数设为了 1G,Oracle 会认为 你只是想让共享池最小不要低于 1G,那么当前共享池的大小 1.5G 满足你的要求,因此, Oracle 不会调低共享池大小。什么时候它自己也认为 1.5G 太大了,应该小一些,这时它才 会将共享池调小。也就是说在 10G 中,Oracle 加强了自身对共享池的掌控权,只保留给 DBA 设置下限的权力。 我们可以通过一个视图 V$SGA_DYNAMIC_COMPONENTS 来了解 SGA 中各内存组件的大小: 步 1:我先手动将 shared_pool_size 参数值设为 50M: SQL> alter system set shared_pool_size=50m; 系统已更改。 步 2:显示 V$SGA_DYNAMIC_COMPONENTS 视图: SQL> SELECT COMPONENT,CURRENT_SIZE,MIN_SIZE,USER_SPECIFIED_SIZE from V$SGA_DYNAMIC_COMPONENTS; COMPONENT CURRENT_SIZE MIN_SIZE USER_SPECIFIED_SIZE ------------------------------ ------------ ---------- ------------------- shared pool 92274688 92274688 54525952 large pool 8388608 8388608 8388608 java pool 4194304 4194304 0 streams pool 0 0 0 DEFAULT buffer cache 247463936 243269632 209715200 KEEP buffer cache 0 0 0 RECYCLE buffer cache 0 0 0 DEFAULT 2K buffer cache 0 0 0 DEFAULT 4K buffer cache 8388608 8388608 8388608 DEFAULT 8K buffer cache 0 0 0 DEFAULT 16K buffer cache 0 0 0 COMPONENT CURRENT_SIZE MIN_SIZE USER_SPECIFIED_SIZE ------------------------------ ------------ ---------- ------------------- DEFAULT 32K buffer cache 0 0 0 ASM Buffer Cache 0 0 209715200 已选择 13 行。 这些列的意义我们简单介绍如下: COMPONENT:SGA 中内存组件的名称 CURRENT_SIZE:当前所占用内存大小 MIN_SIZE:通过分析资料,Oracle 认为的该内存组件的最小大小 USER_SPECIFIED_SIZE:用户指定的大小。也就是 DBA 通过设置 Shared_pool_size 这些参数 设置的大小。 上面是我在将 shared_pool_size 定为 50M 后的显示结果,从上面的结果可以看出来, USER_SPECIFIED_SIZE 为 52MB,因为内存是按块分配的,在 10g 中是 4MB 一个块,52M 正好 是 13 个内存块,Oracle 没办法分配 50M,因为 50 不能被 4 整除。从 MIN_SIZE 列可以看出, Oracle 认为 88M 的共享池应该最小的共享池大小,因此 CURRENT_SIZE 列中记录的当前大小 就是 88MB。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 61 步 3:继续实验,如果我调高共享池内存会怎样呢: SQL> alter system set shared_pool_size=100m; 系统已更改。 将共享池内存设为 100MB 步 4:显示结果 SQL> SELECT COMPONENT,CURRENT_SIZE,MIN_SIZE,USER_SPECIFIED_SIZE from V$SGA_DYNAMIC_COMPONENTS where COMPONENT='shared pool'; COMPONENT CURRENT_SIZE MIN_SIZE USER_SPECIFIED_SIZE ------------------------------ ------------ ---------- ------------------- shared pool 104857600 92274688 104857600 这一次我只显示共享池,可以看到用户指定大小(USER_SPECIFIED_SIZE)已经是 100MB 了,当前大小也是 100MB。Oracle 认为的最小大小 MIN_SIZE 并没有变,还是 88M,这是因 为我们并没有进行太多的操作,Oracle 认为数据库的负载并不重,最小大小因此没有增加。 现在的共享池大小是 100M,如果你将共享池大设定为 88M,当前大小也不会马上变为 88MB。因为 shared_pool_size 只是用来告诉 Oracle 用户希望共享池的最小大小是多少,但 Oracle 不会立即减小共享池。Oracle 会在它认为合适的时候,减少共享池的大小。 4.1.4 共享池调优 这是迄今为止最具有争议的一部分内存设置。按照很多文档的描述,这部分内容应该几 乎和数据缓冲区差不多大小。但实际上情况却不是这样的。首先我们要考究一个问题,那就 是这部分内存的作用,它是为了缓存已经被解析过的 SQL,而使其能被重用,不再解析。 这样做的原因是因为,对于一个新的 SQL(shared_pool 里面不存在已经解析的可用的相同 的 SQL),数据库将执行硬解析,这是一个很消耗资源的过程。而若已经存在,则进行的仅 仅是软分析(在共享池中寻找相同 SQL),这样消耗的资源大大减少。所以我们期望能多共 享一些 SQL,并且如果该参数设置不够大,经常会出现 ora-04031 错误,表示为了解析新的 SQL,没有可用的足够大的连续空闲空间,这样自然我们期望该参数能大一些。但是该参数 的增大,却也有负面的影响,因为需要维护共享的结构,内存的增大也会使得 SQL 的老化 的代价更高,带来大量的管理的开销,所有这些可能会导致 CPU 的严重问题。 在一个充分使用绑定变量的比较大的系统中,shared_pool_size 的开销通常应该维持在 300M 以内。除非系统使用了大量的存储过程、函数、包,比如 oracle erp 这样的应用,可 能会达到 500M 甚至更高。于是我们假定一个 1G 内存的系统,可能考虑设置该参数为 100M, 2G 的系统考虑设置为 150M,8G 的系统可以考虑设置为 200—300M。 对于一个没有充分使用或者没有使用绑定变量系统,这可能给我们带来一个严重的问题。 所谓没有使用 bind var 的 SQL,我们称为 Literal SQL.也就是比如这样的两句 SQL 我们认为 是不同的 SQL,需要进行 2 次硬解析: select * from EMP where name = ‘TOM’; select * from EMP where name = ‘JERRY’; 假如把‘TOM’ 和 ‘JERRY’ 换做变量 V,那就是使用了 bind var,我们可以认为 是同样的 SQL 从而能很好地共享。共享 SQL 本来就是 shared_pool_size 这部分内存存在的 本意,oracle 的目的也在于此,而我们不使用 bind var 就是违背了 oracle 的初衷,这样将给 我们的系统带来严重的问题。当然,如果通过在操作系统监控,没有发现严重的 cpu 问题, 我们如果发现该共享池命中率不高可以适当的增加 shred_pool_size。但是通常我们不主张这 部分内存超过 800M(特殊情况下可以更大)。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 62 事实上,可能的话我们甚至要想办法避免软分析,这在不同的程序语言中实现方式有差 异。我们也可能通过设置 session_cached_cursors 参数来获得帮助(这将增大 PGA)。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 63 4.2 库缓存 4.2.1 库缓存中的信息 1.什么是执行计划 2.为什么要共享执行计划 执行计划的生成要耗费许多 CPU 时间,而且优化器会将生成的执行计划存放到共享池中。 如果你执行很多相同的语句,但没有共享执行计划,优化器每次都要搜索共享池、查找有没 有可以共享的执行计划,没有找到后它自己生成,再把生成的执行计划存入共享池。就是说 相同的语句如果你没有共享计划,不但消耗了更多的 CPU 生成执行计划,而且每次还要搜索 共享池、保存新生成的执行计划,并且,管理共享池中的执行计划还会有一些额外的负担。 这些工作都会拖慢 SQL 语句的执行速度。Oracle 并不会因为你没有共享执行计划而取消有 关执行计划的一些管理性工作。打个比方,你有一辆自行车,你本来可以骑着它加快速度, 但你不但不骑它,反而扛着它,结果是速度大大减慢。Oracle 明明提供了一块内存叫做库 缓存,希望你可以在其中共享执行计划,就算你不共享,库缓存还是要存在的,这个时候, 你就相当于扛着自行车在走了。你没有把库缓存的优势发挥出来,你却承受了库缓存的管理 负担。因此,共享执行计划是最优化使用共享池的最重要一点。 4.2.2 库缓存调优 库缓存的调优最重要一点就是确做用户可以共享执行计划。这应该从程序员和 DBA 两个 角度去作。作为程序员,应该学会使用绑定变量,这可以使本来相似的语句变得一模一样, 第 二 节 库缓存  库缓存中的信息  库缓存的调优  补充:Library cache lock、Library cache pin 等待事 件  库缓存视图  OLAP 和 OLTP 的区别 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 64 从而使它们可以共享执行计划。下面,让我们先来了解一下什么样的情况下,执行计划才能 被共享。 1.共享执行计划 要共享执行计划,语句的文本必须一模一样,比如,如下的语句就不能共享执行计划: 语句 1:Select * from tab1 where id=1; 语句 2:select * from tab1 where id=1; 为什么语句 1 和语句 2 不能共享执行计划呢?第一个语句的第一个字母是大写,而第二 个语句第一个字母是小写。不但大小写,就算多了一个空格,也不能共享执行计划。 假设上面两条语句的文本完全一样的,但语句 1 在用户 USER1 下发出,语句 2 在用户 USER2 中发出。并且这两个用户下都有自己的 TAB1 表,那么这两个语句也不能共享计划。 如果两条语句要想共享计划,两条语句的文本不但要完全相同,语句执行时的环境也必 须完成相同才行。这里所说的“环境”,指的是一些初始化参数的值。并不是指不同的用户。 当然,如果两个用户分别操作不同的表,在表名相同的情况下,是不会共享执行计划的。如 果两个用户发出文本相同的语句,操作的又是同一个表,那么,是可以共享执行计划的。那 么,不同的用户,如何用同样的名字操作同一表呢?接着上面的例子,假设 USER1 中有一个 TAB1 表,而 USER2 中没有,USER1 的查询语句形式如下: select * from tab1 where id=1; USER2 想要查询 USER1 中的表,形式如下: select * from user1.tab1 where id=1; 这两条语句也不能共享执行计划,因为语句文本有很大的不同。USER2 的语句多了一个 “USER1.”。对于这样的情况,我们可以使用公用同义词使 USER2 访问 TAB1 表时,不必在表 名前加“USER1.”来解决。 也就是说,只要语句文本一模一样,执行语句时的环境一模一样,两条语句就可以共享 执行计划。 2.绑定变量 再看下面一种情况。假设有一个大型网站,每天要有大量的用户登录,用户信息存储在 一个 User_info 表中,每个用户登录时,都要输入用户 ID 和密码,数据库根据用户 ID 在 User_info 中进行查询,取出用户密码和其他的一些用户基本信息,等等,后面的工作我就 不说了,就是每个用户在登录时,数据库都要根据用户 ID 进行一次查询,假设又有两个用 户登录了,一个用户的 ID 是 1 另一个用户 ID 是 2。这两个用户登录时的查询语句如下: 用户 1 的查询语句:select * from user_info where id=1; 用户 2 的查询语句:select * from user_info where id=2; 这两条语句是不会共享执行计划的。我们可以实验一下: 步 1:在 139 号会话中发布查询语句: SQL> select * from tab1 where id=1; ID NAME ---------- ---------- 1 ICOL$ 1 I_OBJ# 此语句是首次查询 TAB1 表,这将引出大量的递归调用,这些递归调用将会进行多次硬解析。 以后我们在发布以 TAB1 的查询时,将不会有递归调用。这一步,是会下面的实验做准备, 下面开始实验。 步 2:在另一会话中查询会话 139 的解析情况: SQL> select name,value from v$sesstat a ,v$statname b ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 65 where a.statistic#=b.statistic# and a.sid=139 and b.name like '%parse%'; NAME VALUE ---------------------------------------------------------------- ---------- parse count (total) 386 parse count (hard) 60 parse count (failures) 0 步 3:在 139 会话发出查询语句: SQL> select * from tab1 where id=2; ID NAME ---------- ---------- 2 I_USER1 2 PROXY_ROLE 步 4:在另一会话中再次查询会话 139 的解析情况: SQL> select name,value from v$sesstat a ,v$statname b where a.statistic#=b.statistic# and a.sid=139 and b.name like '%parse%'; NAME VALUE ---------------------------------------------------------------- ---------- parse count (total) 387 parse count (hard) 61 parse count (failures) 0 步 5:在 139 人再次发出相似的查询语句: SQL> select * from tab1 where id=3; ID NAME ---------- ---------- 3 CON$ 3 I_IND1 步 6:再次查询 139 会话中的解析情况: SQL> select name,value from v$sesstat a ,v$statname b where a.statistic#=b.statistic# and a.sid=139 and b.name like '%parse%'; NAME VALUE ---------------------------------------------------------------- ---------- parse count (total) 388 parse count (hard) 62 parse count (failures) 0 这个实验的结果我们已经看到了,这两条语句不会共享执行计划。试想如果每天有大量 的用户登录,每个用户在登录时,都无法共享相似语句的执行计划,这将白白耗费多少 CPU 时间啊。这就是绑定变量派上用场的时候了。下面我们先来看个使用绑定变量的例子: 步 1:在 139 会话中定义绑定变量 User_id,并将它的值赋为 4。 SQL> var user_id number; SQL> exec :user_id:=4; PL/SQL 过程已成功完成。 步 2:在 139 会话中使用绑定变量进行查询: SQL> select * from tab1 where id=:user_id; ID NAME ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 66 ---------- ---------- 4 UNDO$ 4 I_CDEF2 步 3:观察 139 会话的解析次数: SQL> select name,value from v$sesstat a ,v$statname b where a.statistic#=b.statistic# and a.sid=139 and b.name like '%parse%'; NAME VALUE ---------------------------------------------------------------- ---------- parse count (total) 395 parse count (hard) 69 parse count (failures) 0 步 4:将绑定变量 User_id 的值变为 5,再次执行查询: SQL> exec :user_id:=5; PL/SQL 过程已成功完成。 注意,此语句也要解析,因此,我们在这里还要查看一下解析次数: 步 5:查看 139 会话的解析次数: SQL> select name,value from v$sesstat a ,v$statname b where a.statistic#=b.statistic# and a.sid=139 and b.name like '%parse%'; NAME VALUE ---------------------------------------------------------------- ---------- parse count (total) 396 parse count (hard) 70 parse count (failures) 0 步 6:在 139 会话中,用绑定变量再次做一次查询: SQL> select * from tab1 where id=:user_id; ID NAME ---------- ---------- 5 C_COBJ# 5 I_PROXY_RO 查询的结果,是 ID 为 5 的行。这里,我们使用了同样的语句分别查询出了 ID 为 4 的行 和 ID 为 5 的行。 步 7:查看 139 会话的解析次数: SQL> select name,value from v$sesstat a ,v$statname b where a.statistic#=b.statistic# and a.sid=139 and b.name like '%parse%'; NAME VALUE ---------------------------------------------------------------- ---------- parse count (total) 397 parse count (hard) 70 parse count (failures) 0 可以看到最后一次执行查询后,硬解析没有增加。这就是绑定变量的作用。 在这里只能使用绑定变量,不能使用普通变量。绑定变量是 Oracle 的一种特殊变量, 对它赋值的过程,是在 Oracle 优化器解析过语句之后,解析语句的任务就是确定语句的执 行计划。当使用绑定变量后,优化器在解析语句时,解析的是“select * from tab1 where id=:user_id”,而不是“select * from tab1 where id=5;”。优化器将按照“select * from ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 67 tab1 where id=:user_id”确定语句的执行计划,而不是“select * from tab1 where id=5”。 在解析过语句已经确定了语句的执行计划后,优化器将执行计划交给服务器进程去执行时, 再用实际的值替换绑定变量。如果你使用普通的变量,Oracle 优化器将在解析前用变量的 值替换变量,这样在解析语句、生成执行计划时,优化器看到的语句就是“select * from tab1 where id=4”或“select * from tab1 where id=5”等等,因为它们的文本不同,优化器 将选择重新为它们生成执行计划。而不是取出已经存入库缓存的执行计划直接执行。 3.库缓存的大小 我们上面从程序员的角度上讲述了如何共享执行计划。下面再来看看作为 DBA 可以为共 享执行计划做什么事。首先我们要知道,每条语句的执行计划是保存在库缓存中的,优化器 在解析语句时,先要到库缓存中,以语句的文本为条件,查找有没有此语句的执行计划,如 果已经有了,就直接取出来交给服务器进程执行,这就是软解析。如果库缓存中不存在相同 的语句,优化器就为此语句生成执行计划,再把生成的计划存入库缓存,这就是硬解析。那 么库缓存的大小是有一定限制的,如果你有非常多的语句,不可能每条语句的执行计划都能 被存放到库缓存中。假设用户又发出了一条新的语句 A,优化器经过查找,没有在库缓存中 发现同样的语句,优化器开始硬解析,生成了执行计划 A。优化器将计划 A 存入库缓存时, 发现库缓存已经没有空闲空间了,优化器就会把原来的某条语句的执行计划从库缓存中清除 掉,腾出可用的空间以容纳计划 A。被清除的计划我们称为牺牲者,清除操作我们称为语句 的“老化”。老化的语句再次执行时,又要重新硬解析。如果你的库缓存大小设置的比较小, 就会频繁的有语句被老化。这无形中增加了硬解析的次数。因此,库缓存不能设置的太小。 如果库缓存太多了呢,这也不行,因为白白的占用了宝贵的内存资源。那么,库缓存到底多 大的大小才算合适呢?这没有统一的标准,你仍然只能借助历史数据观察。观察的标准就是 软、硬解析的数量。 资料视图中的软、硬解析资料我们已经说过了。下面,来看看 STATSPACK 报告中的软、 硬解析数据。在报告中,有个 Load profile 部分,我们称之为概要信息,在概要信息中就 包含有软、硬解析的信息: Load Profile ~~~~~~~~~~~~ Per Second Per Transaction --------------- --------------- Redo size: 16,233.08 422,060.00(每秒或每事务产生的日志数量,单位字节) redo size Logical reads: 1,413.08 36,740.00(每秒或每事务的逻辑读块数,单位数据库块) session logical reads Block changes: 43.19 1,123.00(每秒或每事务块改变的数量)db block changes Physical reads: 1,198.92 31,172.00(每秒或每事务的物理读数量,单位:块) physical reads Physical writes: 0.00 0.00(每秒或每事务的物理写数量,单位:块)physical writes User calls: 0.96 25.00(每秒或每事务用户调用次数)user calls Parses: 0.65 17.00(每秒或每事务的解析数量,包括软软、软和硬 解析)parse count (hard) Hard parses: 0.04 1.00(每秒或每事务和硬解析解析数量)parse count (hard) Sorts: 2.38 62.00(每秒或每事务产生的排序次数)sorts (memory)、 sorts (disk) ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 68 Logons: 0.00 0.00(每秒或每事务登录的次数)logons cumulative Executes: 1.88 49.00(每秒或每事务执行的次数)execute count Transactions: 0.04(每秒产生的事务数) 这里 Hard parses 就是硬解析的次数。在一般的中型规模的 OLTP 应用中,此值应该控 制在 100 以内。如果超过了 100,说明硬解析太多,执行计划没有共享。没有共享计划的原 因可能是没有使用绑定变量,或者是库缓存太小,语句老化的太快。这个值只是给你一个参 考,准确的你还应该根据历史资料来分析。 Parses 减去 Hard parses 就是软解析的次数了,这个值也不应该太多,中型规模的 OLTP 一般每秒也就是几百次,大型 OLTP 应用每秒软解析可能很有上千次。(这个值太大的话,应 该使用无解析) 除概要信息外,还有一部分“实例有效性”中,也包含解析数据: Instance Efficiency Percentages (Target 100%) Buffer Nowait %: 100.00 Redo NoWait %: 100.00 Buffer Hit %: 15.16 In-memory Sort %: 100.00 Library Hit %: 99.01 Soft Parse %: 94.12 Execute to Parse %: 65.31 Latch Hit %: 100.00 Parse CPU to Parse Elapsd %: 100.00 % Non-Parse CPU: 99.41 这其中和库缓存、软硬解析相关的有: Library Hit %:Library cache 中的命中比率,软解析就是库缓存命中。这个比例通常 应该保持在 90%以上,否则就是库缓存太小或没有使用绑定变量。 Soft Parse %:计算公式 100×(1-parse count (hard) / parse count (total)),软解析在所有 解析中的比例。这个值小于<95%说明硬解析有点多,需要注意。如果低于 80%,执行计划 的共享就出了严重问题,解决方法当然还是加大库缓存或使用绑定变量。 Execute to Parse %:语句执行和分析了次数的度量。这个资料对我们这节课的内容没 什么帮助。写在这里是想让大家了解他一下就行了。解析次数/执行次数 最后还有两个解析时间的比例,这个对我们帮助也不是太大: Parse CPU to Parse Elapsd %:100×parse time cpu / parse time elapsed,即解析时间/解 析时墙上壁钟时间。 % Non-Parse CPU:计算公式:100×(1-(parse time cpu / CPU used by this session)) 。表 示了非解析时间在会话所占用 CPU 总时间的比例。如此值太低,表示解析消耗时间过多。 最后,还应该注意报告中的共享资料部分: Shared Pool Statistics Begin End ------ ------ Memory Usage %: 52.76 54.75 % SQL with executions>1: 65.90 77.17 % Memory for SQL w/exec>1: 86.34 92.30 1) Memory Usage %:这一项资料虽和解析无关,不过它也是共享池资料的一部分,我 们也在这里介绍一下。它是正在使用的共享池的百分率。这个数字应该稳定在 75%至 90% 左右。就是我们不能让共享池占用太多内存而又闲置着,这将带来更多的管理负担。大共享 池的管理负担也更大,如果你用不着这么大,还是调小点好。反过来,我们也不能让共享池 内存占用的太多而没有一点的空余,这会使共享池内部的数据出现你不希望的老化。根据经 验,在通常的 OLTP 应用中,此数值应该在 75%到略低于 90%的范围内。 2) % SQL with executions>1:此资料的计算公式是:100×(1-只执行一次的 SQL 数量 / 所有 SQL 数量),它是共享池中执行次数大于一次的 SQL 语句在所有 SQL 语句的百分比。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 69 这个数字当然越大越好。越大说明你的执行计划共享的越有效。 3) % Memory for SQL w/exec>1:此资料的计算公式是:100×(1-只执行一次的 SQL 所占内存 / 所有 SQL 所占内存)。此资料很好理解了,执行次数大于一次的语句所占内存与 总语句所占内存的一个比例。 好了,STATSPACK 中有关解析的资料就说的这里,软、硬解析的比例,在一定程度上代 表了你的库缓存是否够大。还有一点,如果库缓存不够大,DBA 是不能直接调大库缓存的, 库缓存是共享池的重要部分之一。我们可以通过调节共享池的大小,来改变库缓存或共享池 中其他部分的大小。 4.库缓存命中率与 V$librarycache 视图 在上面的 Statspack 报告中,已经提到了库缓存命中率这个概念,下面我们用视图说一 下库缓存的命中率,视图中的信息比 Statspack 报告中的更详细一些。我们可以通过 V$librarycache 视图来查看库缓存的一些情况。在介绍此视图前,我们要先来介绍几个有 关库缓存的概念。 (1)、库缓存句柄和库缓存内存块。 每一个进入库缓存的对象,在库缓存中都被按照本身内容分割成多块进行存贮,这就好 像一只整鸡被分割成鸡腿、鸡翅、鸡爪等等。Oracle 这样做的目的是为了更灵活的内存管 理,因为在内存寻找大块连续的内存,总比寻找小块连续内存更慢一些,这个我们在此处就 不深入讨论了。继续刚才的话题,如果一个库缓存对象(如一条 SQL 语句的执行计划),它 所占的内存被切割成 4 个小块,它们分别被存放在库缓存的各处,并且互不相连。为了将这 4 个小块组合起来,Oracle 另外为这个库缓存对象分配一小块内存,这块内存中存有其他 4 个小块内存的地址,并且,还有一些有关此库缓存对象的基本信息,如名字、类型等等,这 块内存就叫库缓存对象句柄。其形式如下图: 在访问库缓存对象时,比如软解析时,要从库缓存中读取执行计划。Oracle 首先找到 句柄,读取句柄中的信息,这就叫做一次库缓存 Get。如果库缓存中不包括对象的句柄信息, Oracle 就要重新在库缓存中分配内存、构造句柄,这就是库缓存句柄未命中(Get Miss)。 相反,如果在库缓存中找到了对象句柄,就是库缓存句柄命中(Get Hit)。硬解析时,就会 发生 Get Miss。而软解析则是 Get Hit。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 70 在取出句柄中的其他内存块地址后,每访问一个内存块,都叫做一次库缓存 Pin。如果 相应的内存块已经不在内存中了,这就是 Pin Miss,Pin 的未命中。相反就是 Pin Hit。 当库缓存中对象发生改变后,会引起其他一些对象的无效(Invalidation)。比如,你 修改了一个表的结构,那么,选择了这个表的 SQL 语句的执行计划就会变的无效。其实大部 分对表的 DDL 操作,都会造成相关 SQL 语句执行计划的无效。就连 Grant(授权)、Revoke (撤消权限)这样看来跟执行计划耗无关系的操作,都会引起无效。如果有一个表 TAB1, 你发布了 “Grant 某权限 on tab1 to 某用户”,或“revoke 某权限 on tabl from 某用 户”这样的操作,那么,所有和 TAB1 表有关的执行计划都将无效。无效之后,库缓存对象 除句柄外的所有内存块,都将被清除。再使用到对象后,必须重新加载对象。如上例,如果 你对 TAB1 使用了 DDL 语句,那么所有使用了 TAB1 表语句的执行计划都将无效,你再使用这 些语句时,就必须重新硬解析语句。 因此,使用 DDL 语句是需要注意的,如果没有要求立即使用 DDL,我们最好等到数据库 并不繁忙的时刻,再执行 DDL。作为 DBA,这一点一定要牢记,除非要求立即执行,否则等 到数据库并不繁忙时再执行任何 DDL。 再补充一点,无效和库缓存对象的老化并不一样。老化是连句柄带对象的所有内存块都 被清除出去了。而无效是只在库缓存中保留对象句柄,但所有其他内存块都被清除出去。对 于无效的对象,当再次重新调用到它时,Oracle 会再次将它调入到库缓存中,这个操作被 称为 Reload。一般说来,Reload 与 Get 是无关的,因为 Reload 是在对象无效后才发生的, 而对象的无效并不影响对象句柄。好,说了这么多,下面我们来看这些值的意义。 我们已经讲述了 Get、Pin 与它们的命中、未命中,还有什么是 Invalidation 和 Reload。 在 OLTP 系统中,Get 的命中率应该在 90%之上。而 Reload 和 Pin 的次数的比值,应该小于 1%。如果超出了这些数值范围,就说明库缓存的使用有问题。问题的原因主要有两个,没有 使用绑定变量或是库缓存太小。不过 Reload 和 Pin 的比例过高,也可能是在繁忙时执行了 DDL 所导致的。 另外,V$librarycache 的第一列是 NAMESPACE,也就是名称空间。它相当于存储在库缓 存中对象的类型。具体的名称空间是什么意思呢?就是对象的名字所在的范围,比如表和列 的名字就不在一个范围内。也就是说表名和列名可以重复,还有用户名和表、列也不在同一 范围内,用户名也可以和表、列名相同。我可以有个叫做 AA 的表,也可以有叫做 AA 的用户。 这两个 AA 处在不同的范围内,也就明它们的名称空间不同。这就好像在北京有个电话号码 是 12345678,在上海也有个 12345678,这两个电话号码虽然相同,但不会引起问题,因为 它们在不同的范围内。这个范围,也可以说是类型。 我显示一下 V$librarycache 的所有行(列只有一部分): SQL> select * from v$librarycache; NAMESPACE GETS GETHITS GETHITRATIO PINS PINHITS --------------- ---------- ---------- ----------- ---------- ---------- SQL AREA 21811 4149 .190225116 120258 105272 TABLE/PROCEDURE 25152 16372 .650922392 60649 46008 BODY 4360 4098 .939908257 5931 5537 TRIGGER 320 251 .784375 1655 1576 INDEX 453 128 .282560706 2065 1531 CLUSTER 755 728 .964238411 2339 2296 OBJECT 0 0 1 0 0 PIPE 0 0 1 0 0 JAVA SOURCE 0 0 1 0 0 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 71 JAVA RESOURCE 0 0 1 0 0 JAVA DATA 0 0 1 0 0 可以看在库缓存中共有11个名称空间,我们基本上可以说库缓存中有11种类型的信息。 有一个非常重要的指标,就是库缓存命中率,这个命中率就是库缓存中所有对象的 GETHITRAIO,它的计算方法如下: select 1-sum(gethits)/sum(gets) from v$librarycache; 这个比率应该在 90%以上。 还有一个比率也很重要,就是 Reload 和 Pin 的比值,这个 比值应该小于 1%。 如果没有达到这些比例,解决方法很简单,问题或者是 SQL 语句没有共享,或者是共享 池太小。 有关库缓存命中率,也可以查看 STATSPACK 报告中的 Library Hit %项。这个我们上面 已经说过了。 4.2.3 补充:Library cache lock、Library cache pin 等待事 件 我们再补充一点,SG 中没提,我们在调优的课程中,有很多内容将不再按 SG 顺序讲。 当然我们也会补充很多实践性比较强的内容。所以我们一定要多记才行。 等待事件在 Oracle 中无处不在,为了记录这些数据,是损失了一些性能。但是你是想 出了问题一愁么展好,还是可以利用等待事件或各种资料轻松的查找出问题在哪好呢?而且, 这些等待事件和资料你即使不去用它,Oracle 一样会消耗 CPU 去记载它们。因此,一个好 的 DBA 一定要对各种等待事件、资料了如指掌。下面,我们介绍两个和库缓存相关的等待事 件,如题,就是 Library cache lock 和 Library cache pin。 我们上面说到库缓存中的对象在库缓存中被切割成多个内存块,另有一个对象句柄记录 了各个内存块的地址和其他的一些信息。当你要修改句柄中的信息时,需要在句柄上加独占 锁,而如果另一个进程恰好在这时要求读、写句柄中的信息,它就必须等待。此时的等待就 被 Oracle 记入 Library cache lock 事件。而读、写对象内存块也是无法同时进行的,有人 如果正在写,你的读操作就必须等待,读写内存块的等待事件就是 Library cache pin。如 果这两个等待事件过多,同样说明了库缓存过小或没有共享执行计划。或者,当你在数据库 繁忙时使用 DDL 时,也会有这两个等待事件。 4.2.4 库缓存视图 有一个视图可以看到缓存在库缓存中对象的信息,它是 V$db_object_cache。 OWNER :对象所有者 NAME :对象名 DB_LINK:数据库链接名 NAMESPACE:名称空间。 如果是 SQL 游标,它的名称空间是 CURSOR。名称空间和类型的意义是差不多的。除 了 CURSOR 外,还有 TABLE、INDEX 等等。 TYPE:类型 SHARABLE_MEM:所占用的内存 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 72 LOADS:对象被加载的次数 EXECUTIONS:对象被执行次数 LOCKS:正在锁定对象的会话数 PINS:正在 Pin 对象的会话数 KEPT:对象是否用 DBMS_SHARED_POOL.KEEP 保持在共享池中 CHILD_LATCH:对应的子闩 INVALIDATIONS:对象的无效次数 4.2.5 OLAP 与 OLTP 的区别 我们上面所说的库缓存的调节适合于OLTP,但并不适合大部分的OLAP系统。什么是OLTP 呢,它又叫联机事务处理。各种网站、BBS,或者银行的 ATM 机上的应用,银行前台电脑中 的应用等等,这些都是 OLTP 型的应用。OLTP 偏重于资料收集,但它不会对资料进行分析。 OLAP,又叫联机分析处理(也可称为联机应用程序),它主要指哪些根据以往资料进行分析、 处理,查找规律或预测趋势的应用。像数据仓库和 DSS(企业决策系统)或数据挖掘型的应 用,都是 OLAP 型应用程序。例如,不知道我们听说过一个啤酒和尿裤的故事没有。就是沃 尔玛超市利用OLAP型的程序,分析出啤酒和尿裤的销量有同比的增减关系,沃尔玛于是 将啤酒与尿裤的柜台,紧挨着摆放,结果啤酒和尿裤的销量当月就有所上升。经过调查,很 多婴儿的父亲在为婴儿买尿裤的同时,会为自己再买些啤酒。 在这个故事中,最后分析出啤酒和尿裤有关联的程序,就是一个 OLAP 应用。而沃尔玛 超市的收银台中的应用,就是 OLTP 应用。OLTP 收集资料,OLAP 分析处理。 OLTP 的并发会话数可能非常多,但都是执行短小的事务或查询,而且大多数语句都是 类似。因此,共享执行过的相似的语句,对 OLTP 是非常重要的。 而 OLAP 的并发会话可能很少,而且,以查询为主。因为 OLAP 的主要任务就是分析数据。 但OLAP的查询往往需要执行很长时间。对于OLAP来说,共享语句的执行计划是没有必要的。 因此,对于 OLAP,共享池的大小可以尽量的小。 我们在讲到后面的内容时,会讲述更多 OLTP 和 OLAP 这二者的区别,和各种 Oracle 的 特性分别是针对谁设计的。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 73 4.3 游标与共享 SQL 4.3.1 游标 我们要先说一下游标这个概念。 从 Oracle 数据库管理员的角度上说,游标是对存储在库缓存中的可执行对象的统称。 SQL 语句是存储在库缓存中的,它是游标。除了它之外,还有 Oracle 的存储过程也是存储 在库缓存中的可执行对象,从 Oracle DBA 的角度上说,它也是游标。Oracle 也把它算为游 标,在某些和游标相关的视图中,也会显示存储过程的一些信息的。但从开发者的角度说, 只有 SQL 语句才是游标。 4.3.2 关于游标的视图 你的应用程序或许是用 Java、Pro*C 等语言开发的,也可能有中件间,等等,对于 DBA 来说,我们不必过多的关心这些。以一个常见的三层应用为例, 如下图: 第 三 节 游标与共享 SQL  游标:从 DBA 的角色说,游标是对存储在库缓存中 可执行对象的统称  关于游标的视图和参数 - V$SQL、V$SQLAREA、V$sql_text - V$open_cursor 与 Open_cursor 参数 - CURSOR_SHARING 参数 - session_cached_cursors ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 74 类似的图我们在很多地方都可以看到,假设这是一个三层 J2EE 应用。客户端调用的 Java 应用程序存放在中间的应用服务器层,应用程序的执行由应用服务器负责。 如上图这段 Java 应用程序,它的执行就是应用应用服务器的任务。但是,当执行到 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 75 executeQuery ("select * from Test")语句时,这条 Java 语句要求从数据库服务器中查询 表 Test。发下图: 这条语句的执行,是由数据库服务器负责的。数据库服务器只负责以最快的速度将 “Select * from test”执行完毕。其他的它一概不负责。我们作为 DBA,只要保证 SQL 语 句可以更快的执行就行了,至于应用程序逻辑方面的问题,不由我们负责。也就是说,作为 DBA,我们不必负责具体代码的问题,我们只负责 SQL 语句的执行。每条送交 Oracle 执行的 SQL 语句,无论这条语句是你手动在 SQL*Plus 命令窗口中敲入的,还是应用服务器传送给 Oracle 要求执行的,它们都以一样的方式被传递到 Oracle 中,由服务器进程执行。这些 SQL 语句的执行情况、具体的执行计划等数据资料会在一些视图中被记录下来,以供 DBA 追踪问 题、调优 SQL 的执行。下面,我们就介绍一下这些相关 SQL 执行情况的视图。我们再强调一 个名词,对于从任何地方传递给 Oracle 数据库服务器要求执行的东西,我们都称为游标。 它主要包括 SQL 语句和 PL/SQL 程序段。 1.V$SQL SQL_TEXT:SQL 语句的文本 SQL_FULLTEXT:SQL 语句的完全文本 SQL_ID SHARABLE_MEM:游标所占共享内存 PERSISTENT_MEM:游标持续期所占用的 Fixed(固定)内存 RUNTIME_MEM:游标在运行期所占用的 Fixed(固定)内存 SORTS:游标完成的排序次数 LOADED_VERSIONS:游标在库缓存所占的内存堆是否被加载 OPEN_VERSIONS:游标是否被锁定。 USERS_OPENING:打开游标的会话数。也就是当前正在缓存游标到 PGA 中的会话数。游标被 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 76 执行三次后,就会被缓存到 PGA 中。此数值就加 1。 FETCHES:抓取的次数 EXECUTIONS:执行次数 PX_SERVERS_EXECUTIONS:以并行方式执行的总次数 END_OF_FETCH_COUNT:抓取全部行的次数 USERS_EXECUTING:当前正在执行此游标的会话数 LOADS:游标被加载或重新加载到库缓存中的次数。游标只所以被重新加载有可能是游标 无效或库缓存内存不足。 FIRST_LOAD_TIME:游标被第一次被加载的时间。也就是生成执行计划的时间 INVALIDATIONS:游标的无效次数 PARSE_CALLS:游标的解析次数,包括硬解析与软解析 DISK_READS:游标执行了多少次物理读 DIRECT_WRITES:游标直接写的次数 BUFFER_GETS:逻辑读的次数 APPLICATION_WAIT_TIME:应用程序的等待时间,单位微秒 CONCURRENCY_WAIT_TIME:并行的等待时间,单位微秒 CLUSTER_WAIT_TIME:Cluster 等待时间 USER_IO_WAIT_TIME:用户 I/O 等待时间 PLSQL_EXEC_TIME:PL/SQL 执行时间 JAVA_EXEC_TIME:Java 执行时间 ROWS_PROCESSED:游标一共抓取了多少行。同样的行,每抓取一次此列都会增加 COMMAND_TYPE:命令类型 OPTIMIZER_MODE:优化器模式 OPTIMIZER_COST:执行计划的成本 OPTIMIZER_ENV:执行时的环境 OPTIMIZER_ENV_HASH_VALUE:环境的 HASH 值 PARSING_USER_ID:最先解析此游标的用户的 ID PARSING_SCHEMA_ID:最先解析此游标的方案 ID PARSING_SCHEMA_NAME:最先解析此游标的方案 ID KEPT_VERSIONS:是否使用 DBMS_SHARED_POOL 包将游标 Pin 到库缓存中 ADDRESS:父游标句柄的地址 TYPE_CHK_HEAP: HASH_VALUE:游标的 HASH 值 OLD_HASH_VALUE:老 HASH 值 PLAN_HASH_VALUE:执行计划的 HASH 值。(上述三个 HASH 值并不相同) CHILD_NUMBER:子游标数量 SERVICE: SERVICE_HASH MODULE :第一次解析游标的应用程序名。可以在应用程序中通过调用 DBMS_APPLICATION_INFO.SET_MODULE 设置。 MODULE_HASH:应用程序名的 HASH 值 ACTION :第一次解析时的动作名。可以在应用程序中通过调用 DBMS_APPLICATION_INFO.SET_ACTION 设置。 ACTION_HASH:动作名的 HASh 值 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 77 SERIALIZABLE_ABORTS:每个游标产生 ORA-08177 errors 错误(事务串行化无效)的次数。 OUTLINE_CATEGORY:大纲类型 CPU_TIME:游标解析、执行、抓取时所用的 CPU 时间。单位是微秒。 ELAPSED_TIME:游标解析、执行、抓取时所用的总时间。单位是微秒。 OUTLINE_SID:大纲会话的 SID CHILD_ADDRESS:游标本身的地址 SQLTYPE:游标所用的 SQL 语言的版本 REMOTE:游标是否是远端映像的 OBJECT_STATUS:对象状态 LITERAL_HASH_VALUE:游标文本的 HASH 值 LAST_LOAD_TIME:执行计划最后一次被加载到库缓存中的时间。 IS_OBSOLETE:当子游标太多时,此子游标是否被荒废。 CHILD_LATCH:保护游标的子闩编号 SQL_PROFILE:SQL 的概要文件 PROGRAM_ID:过程 ID PROGRAM_LINE# EXACT_MATCHING_SIGNATURE FORCE_MATCHING_SIGNATURE LAST_ACTIVE_TIME:最后一次使用执行计划的时间。 BIND_DATA:绑定变量的信息 这个视图中 DISK_READS、BUFFER_GETS、CPU_TIME、ELAPSED_TIME 这四个列在调优 SQL 语句时最为重要。在数据库系统的速度不是太另人满意时,如果你已经确定过了,不是其他 方面的原因,而是 SQL 语句性能的问题,只是无法确定是那条、或那些条语句拖慢了整体的 速度。那么此时选择调优物理读、逻辑读最多的,或最耗 CPU 时间的 SQL 语句进行调节,往 往可以取得今人满意的性能增长。 我们也可以以 EXECUTIONS(执行次数)最多的 SQL 语句为调优对象。另外,PARSE_CALLS 是解析次数,对于此列值最多的 SQL 语句,我们可以看看是否可以降低语句的解析次数。 关于 SQL 调优,和程序的调优是一样的。如果我们从事过代码优化这样的工作,就会知 道,对于一个大型的应用程序来说调优的方法也是要从执行次数最多的那部分代码、或从最 消耗资源的代码入手。 还有一个问题,就是文档中关于这个视图会经常提到一个概念:子游标与父游标。如果 两个游标的文本一模一样,但由于环境不同,比如,游标所操作的表是不同用户下的同名表, 这两个游标是不能共享执行计划的。它们都有各自的执行计划存在库缓存中。这两个游标就 是子游标,Oracle 还会建立一个父游标,父游标中没有执行计划,它只是文本相同但执行 计划不同的所有游标的代表。 其实在库缓存中,即使没有文本相同的子游标,Oracle 也会为每个游标都创建父游标。 因为父游标是文本相同的子游标的代表吗,所有文本相同的游标共享同一个父游标。 也就是说,只要你执行 SQL 语句,Oracle 都会在库缓存中保存一父一子两个游标。如 果你执行了文本相同但环境不同因而不能共享执行计划的 SQL 语句,那么一个父游标可能就 对应多个子游标。 父游标没有执行计划,它只有一些信息管理性数据,Oracle 添加它的目的就是为了管 理文本相同的游标。有一个视图是专门针对父游标的,就是 V$sqlarea。下面我们说一下这 个视图。 2. V$SQLAREA ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 78 V$SQLAREA 和 V$SQL 的列几乎是一模一样的。在 V$SQLAREA 中汇总了子游标的数据。如 果有两个语句:语句 A 和语句 B,它们文本一模一样,但是由于环境不同没有共享执行计划, 而是有各自的执行计划。也就是语句 A 和语句 B 是同一父游标下的子游标。在 V$SQL 视图中, 因为它是显示子游标的,所以语句 A 和语句 B 各占一行,假设语句 A 的 DISK_READS(物理 读)是 100,语句 B 的物理读是 3000。V$SQLAREA 是显示父游标信息的,语句 A 和语句 B 因 为文本相同,它们两个对应同一个父游标,在 V$SQLAREA 中占一行。在 V$SQLAREA 中,语句 A 和语句 B 父游标行中的 DISK_READS 就是 3100,也就是语句 A 和语句 B 的和。V$SQLAREA 中的其它列也是如此,都是 V$SQL 中相应子游标的合计。 有一个列是 V$SQL 中没有的,就是:VERSION_COUNT,它是对应同一父游标的子游标的 数量。如果这个数字太高,可能代表由于某些原因使本可以共享执行计划的游标没有共享。 3.V$sql_text、 这个视图的目的是显示过长的 SQL 语句文本。对于这些文本过长的 SQL 语句,在 V$sql_text 中将分多行显示完整的 SQL 语句,每行显示 64 字节。效果如下: 步 1:我发布一个文本很长的 SQL 语句,这个语句没什么意义,只是想让它文本长一点: SQL> select case id when 1 then 'one' when 2 then 'two' end case, name from (select * from ui1 where id<=1) where id=1 or id=2; CAS NAME --- ----- one 1 步 2:我需要知道此语句的 HASH 值,然后以此 HASH 值为条件在 V$SQL_TEXT 中查询: SQL> select sql_text, hash_value from v$sqlarea where sql_text like 'select case id when 1 then%'; SQL_TEXT HASH_VALUE ---------------------------------------------------------------------- ---------- select case id when 1 then 'one' when 2 then 'two' end case, name from 4270697726 (select * from ui1 where id<=1) where id=1 or id=2 此语句的 HASH 值是 4270697726。按照此 HASH 值在 V$sql_text 中查询: SQL> select rownum, sql_text from v$sqltext where hash_value=4270697726; ROWNUM SQL_TEXT ---------- ---------------------------------------------------------------------- 1 e from (select * from ui1 where id<=1) where id=1 or id=2 2 select case id when 1 then 'one' when 2 then 'two' end case, nam 我显示 ROWNUM 的目的是让你看到这条语句被分为了两行显示。而且,第二行是语句的 前半部分,而第一行是语句的后半部分。 这个视图我们就说到这儿。 4.V$open_cursor 与 Open_cursor 参数 这个视图和参数涉及游标的打开。什么是游标的打开,就是在库缓存中,用户在软、硬 解析游标时,会在游标对象的句柄上加一个锁,也就是 Library cache lock。在解析并执 行完游标后,这个锁并不会马上去掉,而是会一直保留着,直到用户发出了 Close 命令关闭 游标时为止。我们在 SQL*Plus 命令窗口中发出的命令,在抓取完所有行后,SQL*Plus 将自 动为我们发出 Close 命令来关闭游标。 当游标打开时,Library cache lock 将一直保持,这样,即使库缓存内存紧张,需要 老化对象,也不会老化这些还正在加锁的对象。因此,如果用户不停的要求数据库服务器打 开游标、执行 SQL,但却忘了关闭游标,这很容易耗尽共享池的内存。为此,Oracle 准备了 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 79 一个参数,就是 Open_cursor,它的默认值在 9i 下是 50,在 10g 中是 300,也就是说,在 10g 下,每个会话最多只能同时打开 300 个游标。有了这个限制,就不用害怕用户不停的打 开游标但又不关闭它,而耗尽共享池内存了。 如果会话同时打开的游标数量超出了 Open_cursor 参数的限制,Oracle 将禁止会话打 开新的游标。同时报出错误:ORA-01000: 超出打开游标的最大数 。 在用户断开会话的连接后,会话打开的这些游标将自动关闭。 V$open_cursor 视图专用来查看当前会话打开的游标信息。它只能查看当前会话打开的 游标。 5.CURSOR_SHARING 参数 如果应用程序中有很多类似下面这样的 SQL 语句: select * from 某表 where id=1; select * from 某表 where id=2; select * from 某表 where id=50; 等等,这些 SQL 语句严格来说是无法共享游标(也就是共享执行计划)的,但是这些语句所 需要的执行计划其实都是一样的。无论你在表中查询 ID 为 1 的行还是查询 ID 为 100 的行, 执行方式应该是一样的。如果你想让这样的语句共享游标,那么,你可以改变 Cursor_sharing 参数的值。 此参有三个值:  EXACT:这个值是默认值。除非游标文本一模一样,否则不会共享游标。  SIMILAR:这个最智能,如果游标只有条件中的数据值部分不同,并且库缓存中原有游 标的执行计划对于新执行的 SQL 语句也是最优的,将不再为 SQL 语句创建新的游标,而 是让它共享库缓存中原有的游标。  FORCE :不比较执行计划是否最优,只要游标中除了条件中的数据值部分不同外,其他 部分都相同,就会共享游标。 此参数可以在会话级修改,也就是可以使用 Alter session 修改它的值,这将只影响某 一个会话,而不会影响其他会话。 非常简单的一个实验就可以看到这个参数的效果: 步 1:在某个会话中修改此参数的值: SQL> alter session set CURSOR_SHARING=SIMILAR; 会话已更改。 步 2:在同一会话中发布 SQL 语句,因为我是在会话级修改的 Cursor_sharing 参数 SQL> select * from ui2 where id=1; ID NAME ---------- ------------------------------ 1 1 步 3:查看刚刚发布的 SQL 语句: SQL> select sql_text, EXECUTIONS from v$sqlarea where sql_text like 'select * from ui2%'; SQL_TEXT EXECUTIONS ---------------------------------------------------------------------- ---------- select * from ui2 where id=:"SYS_B_0" 1 我们可以看到,语句中的条件已经变为了“id=:"SYS_B_0"”, :"SYS_B_0"就是 Oracle 为我们做的转换,它将“id=1”中的数据值 1 换为了绑定变量:"SYS_B_0"。 6.session_cached_cursors 参数 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 80 Oracle 可以将一部分执行次数比较多的游标的信息缓存在会话的私人内存 PGA 中,这 样当被缓存游标再被执行时,很多数据不必再到库缓存中寻找,会话直接可以在自己的 PGA 中取出。这可以大大提高软解析的速度,这样的解析被称为更软的软解析(或快速软解析)。 一般来说,会话在执行游标时,第一步会到自己的 PGA 中搜索游标,如果找到了,这就是更 软的软解析。如果游标没有被缓存到 PGA 中,再到库缓存中查找,如果找到了这就是普通的 软解析。如果库缓存中也没有,就进行硬解析,重新生成游标相关数据和执行计划。 如果会话在执行游标时,发现游标的总的执行次数已经超过了三次,就会将游标信息缓 存在自己的 PGA 中。此参数的作用是设定一个会话共可以缓存多少个游标。 此参数的值如果比较大,将会耗用更多的 PGA 和共享池内存,但是,这对提高软解析速 度是很有帮助的。如果你的数据库软解析耗用了过多的时间,可以尝试加大此参数的值。在 10g 中,此参数默认值是 30。在大型 OLTP 应用中,此参数的值一般都设置为几百甚至上千。 7.Statspack 报告中的 SQL 游标信息 (1). SQL ordered by Gets : 此部分信息是将游标按其 Buffer gets 也就是逻辑读次数排序显示,只显示逻辑超过 10000 的游标 CPU Elapsd Buffer Gets Executions Gets per Exec %Total Time (s) Time (s) Hash Value ----------------- -------------- -------------- ----------- ---------- ----------- ------------- 110,987 1 110,987.0 96.9 5.68 7.56 2032889471 Module: SQL*Plus select id,myid,owner from big_table (省略其余内容„„„„) 上面列的意义是: Buffer Gets:在两快照间声明所完成的逻辑读。 Executions:两快照间声明的执行次数。 Gets per Exec:前两列相除,两快照间每次执行所完成的逻辑读。Buffer Gets / Executions 。 %Total:此声明的逻辑读在总的逻辑读中所占的比例。计算公式:Buffer Gets / 两快照间的 总逻辑读。此处总逻辑读来源于资料视图中的 session logical reads 资料。 CPU Time (s):两快照间声明所耗 CPU 时间,单位:秒。 Elapsd Time(s):两快照间声明的墙上壁钟时间,单位:秒。 Hash Value:声明的 Hash 值。 %Total 是这里面非常重要的列,通过它你可以看到每条语句的逻辑读在总的逻辑读中 的比例,如果有某个游标占了超过 20%的逻辑读,就需要注意它了。它的逻辑读可能太多 了。当然,其他列也很重要,例如 Buffer Gets,它是游标的逻辑读次数。如果某个游标的逻 辑读占了总逻辑读的 50%,但是,Statspack 数据采集时数据库根本就不繁忙,此游标的逻 辑读次数非常小,那么这条游标运行的可能非常正常。也就是说,逻辑读的数量也很重要。 打个比方,如果比尔。盖茨说准备捐 80%钱建希望小学,这一定是非常引人注目的。如果 我说我也准备捐出我 90%的钱建希望小学,这不会引起什么人的重视,因为我的总量太小 了。 (2). SQL ordered by Reads 这一部分是按每条游标的物理读排序的结果,只显示物理读超过 1000 的游标: CPU Elapsd Physical Reads Executions Reads per Exec %Total Time (s) Time (s) Hash Value --------------- --------------- -------------- --------- -------- ------------ ------------- ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 81 2 2 1.0 9.1 0.00 0.01 1428100621 select /*+ index(idl_ub2$ i_idl_ub21) +*/ piece#,length,piece fr om idl_ub2$ where obj#=:1 and part=:2 and version=:3 order by piece# Physical Reads:两快照间声明所完成的物理读。 Executions:两快照间声明的执行次数。 Reads per Exec:前两列相除,两快照间每次执行所完成的物理读。Physical Reads / Executions 。 %Total:此声明的物理读在总的物理读中所占的比例。计算公式:Physical Reads / 两快照间 的总物理读。此处总物理读来源于资料视图中的 physical reads 资料。 CPU Time (s):两快照间声明所耗 CPU 时间,单位:秒。 Elapsd Time(s):两快照间声明的墙上壁钟时间,单位:秒。 Hash Value:声明的 Hash 值。 同样,我们最应该关注%Total 和 Physical Reads。 (3). SQL ordered by Executions 此部分是按游标的执行次数排序,只显示执行次数超过 100 次的游标。 CPU per Elap per Old Executions Rows Processed Rows per Exec Exec (s) Exec (s) Hash Value ---------------- --------------------- ---- ---------------- ----------- ------------- -- ---------- 71 71 1.0 0.00 0.00 1254950678 select file# from file$ where ts#=:1 Executions:两快照间声明的执行次数。 Rows Processed:两快照间声明执行所操作的行数。此行数是结果行数,不是表行数。如果 从一个千万行的表中选一行,此列是 1。 Rows per Exec:上两行相除,每次执行操作多少行。 CPU per Exec (s):两快照间声明所耗 CPU 时间 / Executions ,每次执行所耗 CPU 时间。 Elap per Exec (s):两快照间声明的墙上壁钟时间 / Executions ,每次执行所耗墙上壁钟时间。 Hash Value:声明的 Hash 值。 其他的我就不再说了,Statspack 中还有按游标的 CPU 时间、Elapsed 时间、解析次数 等等排序显示的游标。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 82 4.4 库缓存调优与 Pin 频繁使用的对象 4.4.1 使用视图进行库缓存大小测试 如果你的企业新开启了一项业务,并为此开发了一套新的应用程序,这需要一个新的数 据库为后台。你负责规化此新的数据库。那么,你该如何确定共享池的大小呢?下面的测试 可以有助于你确定共享池大小。注意,这样确定的共享池大小,只是估计的大小。到底多大 的共享池才适合你的数据库,这需要数据库运行一段时间后,通过观察历史数据才能得知。 这就好像你第一次炒菜,你不知道放多少盐,下面的方法只是告诉你大概放多少盐,而具体 放多少数量的盐饭菜的才可口,这需要你实验个几次才知道。第一次你放一匙,如果淡了, 第二次就放一匙半,等等。数据库的调节也是如此。这就是我们以前极力讲述历史数据重要 性的原因。没有历史数据,没有了可供参考的数据,你就不知道这一次放多少盐才合适。好, 下面开始讲述如何测试共享池大小。 首先将你的共享池设置的非常大。如果是在 10g 中,就将 sga_target 设置的非常大, 重新启动你的数据库,然后启动数据库上的所有应用。在应用运行一段时间时间后,通过如 下这个语句,可以计算出大概的库缓存的大小: select mem1+mem2 from (select sum(sharable_mem) mem1 from v$db_object_cache) a,(select value*250 mem2 from v$sysstat where name= 'opened cursors current') b; 这个语句包括两个子查询,第一个子查询是利用 v$db_object_cache 视图,求得当前库 缓存中所有对象所占用的内存总量。另外,Oracle 为每个游标还要额外分配 250 字节的内 存用于存储一些管理性信息。第二个子查询就是计算当前打开的游标数量,并用它乘以 250, 所得结果就是 Oracle 为游标额外分配的内存总数了。将两个子查询所得结果相加,就是你 的应用程序所需的库缓存的大小了。 第 四 节 库缓存调优与 Pin 频繁使用的对象  使用视图进行库缓存大小测试 库缓存大小测试为你设定共享池大小提供了一个 依据,单注意,这仅仅是一个依据。DBA 在事前只能 大概的测算出这样一个依据。真正合适的大小要通过 观察运行期数据才能得出  Pin 频繁使用的(大)对象 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 83 知道了库缓存的大概大小,我一般的方法是用它剩以 2,用所得的结果作为整个共享池 的大小。 如果你的应用程序比较散,很难这样为了观察共享池的内存占用情况而全部都集中的运 行一下,那么,还有一种更为简单、使用更广的评估共享池、Buffer cache 等内存组件大 小的方法。对于 OLTP 系统来说,如果主机上除数据库外不再运行其他软件,我们应该保留 20%或 30%左右的内存给 OS,50%左右的分给 SGA,剩下的给 PGA。在 SGA 中,可以将 40%分 配给 Buffer cache,共享池也要占 40%,剩下的分给 SGA 中其他内存组件。如果是 OLAP 系 统,除给 OS 留 20%、30%左右的内存外,SGA 和 PGA 可以一样大,甚至 PGA 还可以超过 SGA。 在 SGA 中,共享池要尽量的小,可以将大部分的内存都分给 Buffer cache。 无论你怎么分配,我们这样只是在事先估计一下共享池等内存组件的大小。每个内存组 件到底多大合适,等到数据库运行一段时间后,还要参考历史资料再做具体的调整。 4.4.2 Pin 频繁使用的对象和大对象 1.什么对象应该被 PIN 在共享池中 Pin 住对象的意思是将对象常驻在库缓存中,即使空间不足了,也不会老化这些被 PIN 住的对象。那么,什么样的对象应该被 PIN 在库缓存中呢?有三种对象应该被 PIN:  频繁使用的对象  大对象  序列 频繁使用的对象将它们 PIN 在库缓存中,避免它们老化,这是很合理的。不过,频繁使 用的对象你 PIN 或者不去 PIN 它都无所谓。因为如果它的确频繁使用,它不会被老化的。 Oracle 对库缓存中对象的老化,是采用的 Oracle 改进的 LRU 法则,这种法则保证频繁使用 的对象可以不被老化,而只老化那些不频繁使用的对象。但是,为了保险期间,如果你可以 预知那个对象的确会被频繁的使用,那么将它用命令 PIN 在库缓存中比较保险,这样可以防 止它被意外的老化。 占用字节达到 1KB 以上的对象,我们就可以称为大对象。大对象是一定要 PIN 在库缓存 中的。特别是不太常用的大对象。常用的大对象因为会被频繁使用,即使不 PIN 它,也会停 留在库缓存中,不过,我们上说了,保险期间,如果你可以确定它会被频繁的调用,那么还 是 PIN 住它吧。但是,为什么不太常用的大对象也一定要 PIN 呢?这样不占内存吗?大对象 被加载到内存中时,会老化很多小对象,这将非常耗时。而且,大对象的加载、老化非常容 易引出大量的碎片。因此,越是不太常用的大对象,第一次使用时,它被加载到内存中,由 于不太常用,过一会儿它被老化了。当再次调用到它时,它又要被加载到内存中。这样反复 的加载、老化,再加载、再老化,等等,库缓存中不一会儿就充满碎片了。所在,这些不太 常用的大对象一定要 PIN 到内存中。一般来说,只要是大对象,最好都 PIN 住它们。以免它 们的加载、老化产生碎片。 序列为什么也要被 PIN 在库缓存中呢?因为序列有一个 CACHE 功能,这一点我们可以复 习一下以前的内容,我就不讲了。如果序列老化,CACHE 中预先生成的值就会丢失,序列将 会出现间隙。因此,将有 CACHE 并且不想出现间隙的序列 PIN 住也就很重要了。如果你不明 白这其中的原理,只是看一些文章、资料中说要将序列 PIN 在共享池中,这就容易造成共享 池内存的浪费。对于出现间隙也无所谓的,还有没有 CACHE 的序列,PIN 它们是没有必要的。 2.如果 PIN 对象 (1)、 PIN 住 SQL 语句 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 84 第一步查出 SQL 语句在库缓存中的地址和 SQL 语句的 HASH 值: SQL> select substr(sql_text,1,40) sqlt, address, hash_value from v$sql where sql_text = 'select name from ui1 where id=101'; SQLT ADDRESS HASH_VALUE ---------------------------------------- -------- ---------- select name from ui1 where id=101 2345C39C 2874596039 第二步调用 DMBS_SHARED_POOL.KEEP 过程 PIN 住对象: 先来看看文档中此过程的定义: DBMS_SHARED_POOL.KEEP ( name VARCHAR2, flag CHAR DEFAULT 'P'); 它有两个参数,第一个参数是要 PIN 在内存中的对象的名字,也可以对象的地址、HASH 值组成的字符串。对于上面的游标对象,必须使用地址、HASH 值字符串,使用形式为: ‘2345C39C,287459603’。也就是‘Address,hash_value’。 第二个参数,说明被 PIN 的对象的类型。有如下几种类型: 'P' or 'p' :被 PIN 对象是包、过程或函数,这个是缺省值 'T' or 't' :被 PIN 对象是 TYPE。它类似 C 语句中的结构,是开发中常用的组合型数据。 'R' or 'r' :被 PIN 对象是触发器 'Q' or 'q' :被 PIN 对象是序列 如果是游标对象,可以随变指定一个非上面列出的字母,我一般常用 C,它是 Cursor 的第 一个字母。另外,表、索引还有视图不能被 PIN 到内存中。好,下面调用此过程将上面例子 中的游标 PIN 到内存中: SQL> exec dbms_shared_pool.keep('2345C39C,2874596039','c'); PL/SQL 过程已成功完成。 被 PIN 住的对象,我们可以使用 UNKEEP 过程取消 PIN,调用方法非常简单: SQL> exec dbms_shared_pool.unkeep('2345C39C,2874596039','c'); PL/SQL 过程已成功完成。 注意,被 PIN 的游标对象,只有父子对象的句柄还有父对象的 0 号子堆内存将会一直保 留,其他子堆仍会老化。这个可以通过 Alter system flush shared_pool 查看,刷新共享 池后,只有对象句柄和父对象 0 号子堆还有共享池中。对于过程,在刷新共享池后,将保留 句柄和堆 0 的内存,其他内存也会被清掉。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 85 4.5 保留区与 ORA-04031 4.5.1 保留区 没有被 PIN 住的大对象的加载、老化将会使共享池产生碎片,Oracle 想了个方法解决 这个问题,它专门在共享池开辟一块区域,所有大小超过 4400 字节的对象,将在此专门开 辟的区域中分配空间,这块区域被称为保留区。这样,让大对象和小对象分开存储,可以减 少大对象的加载、老化对大量小对象产生的影响,并且可以减少小对象区域内的内存碎片。 而且 Oracle 针对保留区设计了专门的内存管理算法,使得大对象可以更快速的加载。 你可以使用 shared_pool_reserved_size 参数设置此保留区的大小,默认情况下,保留 区将被自动设置为共享池大小的 5%。另外,保留区大小不能设置为超过共享池大小的一半, 否则将会报出错误。通常情况下,保留区的大小保留默认值就行,我们一般不需要调节它。 关于进入保留区对象的大小,Oracle 并不建议我们调节它。但有时 4400 这个值对我们 的系统可能并不合适。假设经常 4000 字节左右(不到 4400)的对象被加载到共享池中,三、 四千字节的对象,已经是大对象了,并且这些大对象很少使用,这样将它们 PIN 在内存中有 点浪费空间,不到 4400 的大小使得它们无法被加载到保留区,这些对象每次执行时的加载 需要老化很多小对象,并且还很容易造成共享池的碎片。这时,我们可以调低 4400 这个限 制值,比如我们可以将保留区的限制降到 3500,这样可以让那些本来不能进入保留区的对 象可以进入保留区。这不旦可以加快这些对象的加载速度,重要的是减少了它们对众多小对 象的影响。我们可以通过 V$DB_ OBJECT_CACHE 观察对象在共享池中所占用的内存大小,如 果发现上述情况,可以调低 4400 这个限制值。这个限制是用一个隐藏参数来设置的: _shared_pool_reserved_min_alloc。凡是参数名前带有下划线的,都被称为隐藏参数,这 些隐藏参数不能通过 Show parameter 显示,我们可以使用我写的脚本 Show_para.sql 显示 第 五 节 保留区与 ORA-04031  保留区是共享池中一块特殊的内存区  大小超过 4400 字节的对象,将在此专门开辟的区 域中分配空间  作用是避免共享池碎片  隐藏参数_shared_pool_reserved_min_alloc 可 以控制进入保留区对象的大小 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 86 隐藏参数和它们的意义,此脚本的编写见本章末尾的附录部分。 有一个视图可以用来帮助调节保留区:V$SHARED_POOL_RESERVED,它有如下的列: FREE_SPACE:保留区中自由空间的总和 AVG_FREE_SIZE:每个自由内存块的平均大小 FREE_COUNT:自由的内存块数量 MAX_FREE_SIZE:最大的自由内存块大小 USED_SPACE:保留区中已使用的空间大小 AVG_USED_SIZE:每个已经使用的内存块的平均大小 USED_COUNT:已使用的内存块数量 MAX_USED_SIZE:已使用的最大的内存块大小 REQUESTS:在保留区中搜索自由内存块的次数 REQUEST_MISSES:保留区中没有可以满足要求的内存块并开始在保留区中 LRU 链中刷新对 象的次数 LAST_MISS_SIZE:最后一次出现 REQUEST_MISSES 时,所请求的空间大小 MAX_MISS_SIZE:最大一次出现 REQUEST_MISSES 时,所请求的空间大小 下面的列即使 SHARED_POOL_RESERVED_SIZE 没有被设置也是有效的: REQUEST_FAILURES:无论保留区还是非保留区,没有可以满足要求的内存的次数(也就是 ORA-4031 错误发生的次数) LAST_FAILURE_SIZE:最后一次 REQUEST_FAILURES 时所请求的内存大小 后三列相关 DBMS_SHARED_POOL.ABORTED_REQUEST_THRESHOLD 过程。 ABORTED_REQUEST_THRESHOLD : 最 小 的 超 过 DBMS_SHARED_POOL.ABORTED_REQUEST_THRESHOLD 设置值的大小 ABORTED_REQUESTS:超过 DBMS_SHARED_POOL.ABORTED_REQUEST_THRESHOLD 的次数 LAST_ABORTED_SIZE:最后一次超过 DBMS_SHARED_POOL.ABORTED_REQUEST_THRESHOLD 时的 大小。 注意,如果 REQUEST_FAILURES 大于 0 且持续增加时,说明用户要求大小为 N 的内存块, 但共享池内已经没有大小超过 N 的内存块。当 REQUEST_FAILURES 增加的比较快时,用户的 操作将会失败并收到著名的 ORA-04031 错误,就是“共享池内存不足”,通常还有一句“不 能申请大于 N 字节的内存块”。 造成这种情况有三种原因,一是内存的确不足了,这个时候你要增加内存。二是共享池 中还有内存,但都是小块,没有大小超过 N 字节的内存块以满足用户的请求,也就是说内存 中有碎片了。关于共享池内存碎片的解决方法,我们马上就说。再来看第三种情况,三是共 享池中还有内存,但因为保留区设置的不合理,用户要在普通区中请求内存(用户请求的内 存大小未超过 4400),而普通区中已经没有自由内存了,但保留区中还有很多空闲。或者情 况相反,用户在保留区中请求内存,保留区内存已经不足,但普通区中仍有大量内存空余。 当 4031 错误出现时,你的情况是否属于第三种―――保留区设置不合理,可以通过本 节所介绍的视图得知。下面我们分别介绍一下普通区空间不足和保留区空间不足这两种情况 的监测和解决: (一) 、普通区内存空间不足: REQUEST_FAILURES 不为 0 且 持 续 增 加 时 , 如 果 LAST_FAILURE_SIZE < _SHARED_POOL_RESERVED_MIN_ALLOC (进入保留区的对象最小大小限制), 例如, _SHARED_POOL_RESERVED_MIN_ALLOC 限制仍是默认值 4400,而 LAST_FAILURE_SIZE 是 3800, 也就是最后一次用户请求内存失败时用户所请求的内存大小为 3800 字节。3800 小于 4400, 这说明用户是在普通区中请求内存失败的。观察一下保留区内存是否充足,如果充足的话, ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 87 可以将_SHARED_POOL_RESERVED_MIN_ALLOC 设置的小于 3800 让用户的请求在保留区内分配, 或才干脆减少保留区大小,这样普通区中空间自然就会多起来。那么,如何确定保留区中内 存是否充足呢?可以通过下面这些列进行观察:  FREE_SPACE:保留区中自由空间的总和  AVG_FREE_SIZE:每个自由内存块的平均大小  FREE_COUNT:自由的内存块数量  MAX_FREE_SIZE:最大的自由内存块大小  USED_SPACE:保留区中已使用的空间大小  AVG_USED_SIZE:每个已经使用的内存块的平均大小  USED_COUNT:已使用的内存块数量  MAX_USED_SIZE:已使用的最大的内存块大小 如果 FREE_SPACE 显示的自由空间还有很多,就说明保留区中仍有大量空间,完全可以 将_SHARED_POOL_RESERVED_MIN_ALLOC 设置的小一些,或减少保留区所占内存。 如果保留区中空间也不是太足了,就需要检查共享池中是否碎片太多,如果不是碎片的 问题,就只有增大共享池大小了。关于碎片我们马上就讨论。 (二) 、保留区空间不足 当 REQUEST_FAILURES 不为 0 且 持 续 增 加 时 , LAST_FAILURE_SIZE > _SHARED_POOL_RESERVED_MIN_ALLOC,就说明用户所请求的内存要在保留区中分配,而保留 区这时的空间已经不足已满足用户的请求了。 这时我们可以加大_SHARED_POOL_RESERVED_MIN_ALLOC 的限制值,减少进入保留区的对 象数。将 SHARED_POOL_RESERVED_SIZE 参数的值设大一些,以增加保留区大小。当然,如果 你的主机上仍有空余内存,也可以增大共享池内存大小。 4.5.2 共享池碎片 共享池内存不足的一个主要原因就是共享池碎片。我们先说解决这个问题的方法,很简 单,就是使用如下的命令清空共享池: alter system flush shared_pool; 这条语句也叫做刷新共享池。这条命令将大部分共享池的空间变为了刚启动数据库时的 状态。也就是说它清空了大部分共享池中的数据,并且会将碎片进行合并。这条语句对系统 的影响是非常大的,因此,不要轻易的使用。一定要在确定了共享池内存问题是因为碎片引 起的时,才可以使用它。那么,如何确定共享池是否碎片过多呢。简单的方法是看 V$SHARED_POOL_RESERVED 视图中的 LAST_FAILURE_SIZE 列,或者看报出 ORA-04031 错误时 的提示,“请求 N 字节的内存失败”。如果这个 N 是一个很小的值,假设只有 80 字节,也就 是说共享池中空闲的内存块都不超过 80 字节。Oracle 会将连续的内存块合并,即使在连续 的内存块合并后,也没有了 80 字节的内存块。这说明共享池中已经有了碎片。或者,也可 能是因为共享池内存不足了。要想进一步的判断,我们可以使用我写的脚本 Show_sp.sql, 它的编写见本章附录,下面是脚本的输出格式: SQL> @?/sql/show_sp KSMCHCOM size COUNT(*) STATUS BYTES ---------------- ----- ---------- -------- ---------- free memory 0-1K 356 free 39116 free memory 1-2K 60 free 53576 free memory 2-3K 7 free 14372 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 88 free memory 3-4K 8 free 25384 free memory 4-5K 40 free 158144 free memory 5-6k 1 free 5180 free memory 8-9k 1 free 8296 free memory > 10K 11 free 3382344 free memory > 10K 16 R-free 3406208 这个脚本依次显示了 0-1K、1-2K、3-4K 等等大小的自由内存块数量。通过这个视图我 们可以了解到自由内存块的大小分布。我上面这个结果是数据库刚启动时的状态,我们可以 看到大小在 10K 之上的大内存块还有很多。随着用户的操作,大的内存块被不断的切割、变 小,大内存块可能会越来越少,也越来越小。如果出现我 4031 时,通过使用上面的脚本, 观察到大小在 2K 之上的内存块已经被耗尽了,而 0-1K,1-2K 的内存块却有很多,这基本上 就可以确定是碎片太多了。这时就可以通过刷新共享池来解决问题。 不过,有时的 4031 错误是非常严重的,严重到无论什么操作都无法执行了,对于这种 4031 错误,只有重启数据库了。 另外说明一点,保留区是不会有碎片的,因为 Oracle 对保留区内存管理算法进行了改 进,所以保留区内不会有碎片。但保留区并不适合存放过小的对象。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 89 4.6 共享池顾问 Oracle 中各种各样的顾问都是在后台收集资料,你可以通过一些视图来了解这些顾问 们所收集的资料。共享池顾问也不例外。它有两个视图,一个是 V$LIBRARY_CACHE_MEMORY, 它显示了 Library cache 中内存的使用情况。它按照命名空间(也就是库缓存中对象类型) 进行显示,每一行都是一种类型的对象在库缓存中的内存使用情况。它有如下几列: LC_NAMESPACE:名称空间。 LC_INUSE_MEMORY_OBJECTS:当前正在使用的对象数量 LC_INUSE_MEMORY_SIZE:正在使用的内存数量,单位 MB 字节。 LC_FREEABLE_MEMORY_OBJECTS:freeable(可以释放)的对象 LC_FREEABLE_MEMORY_SIZE:freeable(可以释放)的内存空间。单位 MB 使用这个视图你也可以了解到空闲内存的多少。 共享池顾问还提供了一个视图:V$Shared_pool_advice SHARED_POOL_SIZE_FOR_ESTIMATE:估计的共享池大小 SHARED_POOL_SIZE_FACTOR:估计的共享池大小与当前大小的比。 ESTD_LC_SIZE:估计的库缓存大小 ESTD_LC_MEMORY_OBJECTS:估计的库缓存中对象的数量 ESTD_LC_TIME_SAVED:在此估计的共享池大小下将会节省多少时间,单位是秒 ESTD_LC_TIME_SAVED_FACTOR:与当前共享池大小下节省时间的比 ESTD_LC_LOAD_TIME:在此估计的共享池大小下将会节省多少解析游标的时间,单位是秒 ESTD_LC_LOAD_TIME_FACTOR:节省的解析游标时间的比 ESTD_LC_MEMORY_OBJECT_HITS:在估计共享池大小下,库缓存的命中次数是多少。 利用上面这些数据,我们很容易生成如下的图,我以命中次数列为准,假设有下面这样 的图: 第 六 节 共享池顾问 注意如下两个视图:  V$LIBRARY_CACHE_MEMORY  V$Shared_pool_advice 根据这两个视图的信息,画出曲线图,这将帮助你 决定共享池大小是否需要调整 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 90 在图中假如说 100MB 是当前大小,从 112MB 后,命中次数就不再增加,也就是说如果你 的主机上仍有空余内存,并且你想让速度更快一点,将共享池从 100MB 加大到 112MB 会对命 中率有一些帮助。如果你当前的共享池大小是 124MB,那么,从图中可以看到到内存为 112MB 后,对命中次数就不再有影响了,这时你完全可以将共享池的大小减少到 112MB。 我们也可以以 ESTD_LC_TIME_SAVED_FACTOR 和 ESTD_LC_LOAD_TIME_FACTOR 列为准生成 这样的折线图,来观察共享池在不同大小可以节省的时间,以便最终决定共享池的大小。 作为一个 DBA,有时候是需要用将数据做成各种各样图表的。图表比数据更有说服力。 当你的主机硬件能力已经达到极限,你如何说服领导性能慢不是作为 DBA 的我技术不好,而 是我们的主机、存储设备要升级了。这时候拿出一幅幅图表,这比抽象的数据更能打动你的 领导。因此,作为 DBA 你至少要掌握一种图表生成工具,例如 Excel。 29600 29800 30000 30200 30400 30600 30800 31000 31200 共享池大小 76 88 100 112 124 136 命中率次数 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 91 4.7 库缓存调优总结 好了,我们要对库缓存乃至共享池的调节做一个总结了: 一、设定合适的大小 二、准备适当大小的保留区 三、Pin 住频繁使用的对象 四、注意共享游标 五、注意使用 V$sql、V$sqlarea 监控最耗 CPU、磁盘和解析次数最多的 SQL 语句 七、使用 V$librarycache 监控 GET、PIN 和 Reload 的比例 八、减少对象的失效,不在繁忙时重定义对象 第 七 节 库缓存调优总结  设定合适的大小  准备适当大小的保留区  Pin 住频繁使用的对象  注意共享游标  注意 V$sql、V$sqlarea 和 V$librarycache 的使用  减少对象的失效,不在繁忙时重定义对象 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 92 4.8 调优字典缓存 本章的内容是共享池,但是到现在为止一大半篇幅都在讲库缓存,有些内容我们甚至还 将库缓存和共享池这两个名词混在了一起。这并不是因为字典缓存不重要,而是 Oracle 并 不希望你去过多的干涉字典缓存的工作,Oracle 自己可以处理字典缓存的各种问题。 Oracle 没有为 DBA 提供类似 V$DB_OBJECT_CACHE(查询库缓存内容的视图)这样,查询 字典缓存具体内容的视图。我们只能从名字“字典缓存”中猜测,它是保存数据字典信息的。 Oracle 为 DBA 提供了一个相关字典缓存的视图:V$rowcache,在此视图中我们可以查询到 字典缓存的命中率,如果命中率过低,DBA 唯一需要做的事就是加大共享池的内存。如下的 命令可以显示出字典缓存的命中率: SQL> select (1-sum(getmisses)/sum(gets))*100 from v$rowcache; (1-SUM(GETMISSES)/SUM(GETS))*100 -------------------------------- 88.6516784 目前的字典缓存命中率为 88.65%。通常情况下,这个比率应该高于 85%。否则,作为 DBA 你唯一能作的事情就是,增大共享池内存。 我们以前曾给大家介绍过递归调用这个概念,比如你想查询表 TAB1,Oracle 必须根据 你提供的表名,查找出表 TAB1 具体在那个数据库文件、那些块中。还有,你当前使用的用 户是否有权限查询 TAB1,等等,这些信息都保存在数据字典表中,查询这些信息的操作就 被称为递归操作。用户表、索引的数据都是以块的方式缓存在 Buffer cache 中的,而这些 数据字典表的信息,以行的方式被缓存在字典缓存中,因此字典缓存也被称为行缓存。 第 八 节 调优字典缓存  字典缓存:Oracle 并不希望你来调节它  v$rowcache 与字典缓存命中率 注意,字典缓存 Oracle 可以自动的调节它,Oracle 并不希望你来调节,因此,有关字典缓存的视图、数 据字典非常少,DBA 只需关注意 V$ROWCACHE 中的命中 率即可。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 93 4.9 大池 大池的配置参数是 large_pool_size。大池的作用是为了分担共享池的负担。大池特别 适合用来存贮那些占用内存比较多的操作。比如 RMAN 这个工具,它是 Oracle 的主要的备份 恢复工具,它就可以被分配在大池中。其他的还有并行查询操作、DBWR 的辅助进程。这些 操作本来都是要在共享池中分配内存的,它们占用的内存比较多,很容易使共享池出现碎片。 当你配置有大池后,它们会自动的被移到大池中,这样,这些操作对共享池的影响将降到最 少。 我们可以通过 v$sgastat 视图查询到大池当前的大小。在 10g 中,我们通常不需要自己 动手设置 large_pool_size 参数,这个参数的默认值将是 0。但这并不意味着大池没配置, Oracle 将自动的配置大池的大小。如果 RMAN 操作报告内存不足,或是其他的操作报告大池 内存不足,你可以手动的通过增加 Large_pool_size 的方式,增加大池的大小。 第 九 节 大池  初始化参数:large_pool_size  用 V$sgastat 观察大池 大池是共享池的辅助,对于一些操作,它可以有效 的缓解共享池的争用。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 94 4.10 共享池相关的闩 4.10.1 什么是闩 闩,Latch,它是一种在并发程序中常用的低级锁,通常用最低层的汇编指令实现,代 码要力求简洁,闩的获取、释放也要尽量的快,尽可能在很少的指今周期内就可完成。而普 通的锁不同,通常锁的代码都很长,速度也一般,但锁的功能更强。闩一般在多 CPU 或多核 系统中作用巨大。使用闩进行并发程序设计的方式又叫做非阻塞并发模式。如果我们想了解 更多的闩的信息,可以在网上以“非阻塞编程”为关键字搜索一下,它的具体实现方式与 DBA 无关,我们就不多讲了。 由于 Oracle 要支持很大并发的各种数据库操作,所以在 Oracle 中,也大量使用了闩这 种编程模式。我们刚才说了,闩是一种低级锁。在计算机系统中,各种类型的锁只有一种目 的,保护计算机内的资源,闩也不例外。Oracle 中的闩有两种目的,一是用来保护重要的 内存结构,二是串行执行某些代码。闩的第一种作用和普通锁是类似的,闩的第二种作用使 得闩更向是一种操作,而不是纯粹的锁。好了,闲话少叙,总之,我们把闩看作一种锁就行 了。下面,我们来看一下有关共享池的闩。 4.10.2 Shared pool 闩 首先有一个 Shared pool 闩,它作用于共享池中分配内存时。如果并发会话比较多,有 多个用户同时要求在共享池中分配内存,这很容易出乱子。就好像我们去食堂买饭,但我们 第 十 节 共享池相关的闩  什么是闩(Latch)  Shared pool 闩  Library cache 闩 闩,Latch,它是一种在并发程序中常用的低级锁, 通常用最低层的汇编指令实现,代码要力求简洁,闩 的获取、释放也要尽量的快,尽可能在很少的指今周 期内就可完成。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 95 不能端着碗自己去打饭,只能在食堂的窗口前排队。食堂的窗口就是这里的 Shared pool 闩,想要分配饭菜,好,先到 Shared pool 这个窗口前排队。如果同时要求分配共享池内存 的会话只有你一个,你就不需要等待。如果有好些人同时要求分配内存,那么,只好有人等 待了。在 9i 中,你只会看到 Latch free 等待事件,9i 中的所有闩的等待事件都是这一个, 事件参数 P2 中记录有闩的编号,在 v$latchname 中可以根据闩编号查找出闩的名字。在 10g 中不用在这样了,共享池闩会被显示为 Latch: shared pool。闩的持有时间都很短,因此 每次闩的等待时间也很短,在等待事件中观察闩,我们应该看总的等待时间。具体的 SQL 语句我就不再说了,我们应该煅炼根据我所讲的内容自己写 SQL 语句来观察数据库的行为或 问题。如果你对视图列的意义不是太清楚,可以查询文档。但是不要试图弄清楚文档中所记 录的视图的所有列的意义。有些列的意义 Oracle 并不太想让你明白。 有些闩有子闩,就像 Shared pool 闩,它就有 7 个子闩。这就好像食堂有 7 个打饭的窗 口一样。有些资料、代码充许我们有限的共享,这就是有些闩有多个的原因。子闩是打饭的 窗口,而此时的父闩已经没有了实际的意义,它只是这些子闩的代表,就像我们前面说过的 父游标和子游标一样。父闩中总和了其所有子闩的数据,仅此而已。我们可以在 V$latch 中看到父闩资料,在 V$latch_children 中看到子闩资料。这两个视图的列是一模一样的。 这两个视图中都有 GETS、MISSES 列,GETS 代表请求并获得闩的次数,MISSES 代表请求失败 的次数。这两数的比例,代表了某一种闩的争用是否激烈,至于多大的比率才合适,这没有 统一的标准,只有你通过历史数据才能知道。 Shared pool 闩的作用是保护在共享池分配内存这个操作。通常情况下,是不会有会话 频繁的在共享中请求内存的。每次硬解析,都需要在共享池的库缓存中分配空间,这将需要 Shared pool 闩。如果有大量的游标没有共享,就很容易出现 Shared pool 闩的争用。因此, 如果在等待事件中发现 Shared pool 闩总的等待时间很高,就说明是游标没有共享。软解析 时是不需要使用此闩的。 4.10.3 Library cache 闩 另外还有一个有关共享池的闩是 Library cache 闩,它主要用在软、硬解析时被获取, 这个闩争用的主因,也是硬解析过多。当然,有时软解析过多也会引起这个闩的争用,减少 解析的次数是解决这类问题的关键。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 96 附录: 1.show_sp.sql 脚本: select ksmchcom, decode(round(ksmchsiz/1000),0,'0-1K', 1,'1-2K', 2,'2-3K',3,'3-4K', 4,'4-5K',5,'5-6k',6,'6-7k',7,'7-8k',8,'8-9k', 9,'9-10k','> 10K') "size", count(*),ksmchcls Status, sum(ksmchsiz) Bytes from x$ksmsp where KSMCHCOM = 'free memory' group by ksmchidx, ksmchcls,'sga heap('||KSMCHIDX||',0)',ksmchcom, ksmchcls, decode(round(ksmchsiz/1000),0,'0-1K',1,'1-2K', 2,'2-3K', 3,'3-4K', 4,'4-5K',5,'5-6k',6,'6-7k',7,'7-8k',8,'8-9k', 9,'9-10k','> 10K') order by "size"; 2.show_para.sql 脚本 col description for a40 col value for a30 select i.ksppinm name, i.ksppdesc description, cv.ksppstvl value, cv.ksppstdf isdefault, decode(bitand(cv.ksppstvf,7),1,'MODIFIED',4,'SYSTEM_MOD','FALSE') ismodified, decode(bitand(cv.ksppstvf,2),2,'TRUE','FALSE') isadjusted from sys.x$ksppi i, sys.x$ksppcv cv where i.inst_id=userenv('Instance') and cv.inst_id=userenv('Instance') and i.indx=cv.indx and i.ksppinm like '%¶%' order by replace(i.ksppinm,'_',''); ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 97 第 5 章 Buffer cache 故障排除与调优 您将学习: 1. Buffer cache 工作原理 2. 块的读 3. 块写 4. 逻辑读的两种类型 5. CR 块 6. 等待事件 第 五 章 Buffer cache 故障排除与调优 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 98 5.1 Buffer cache 工作原理 5.1.1 Buffer cache 作用 Buffer cache 是 Oracle 建立的数据文件的缓冲区。Oracle 中所有 Select、DML、DDL 等命令,凡是需要所有对数据文件进行读、写操作的,基本上都是对 Buffer cache 进行读、 写操作。如果需要读或写的块没有包括在 Buffer cache,Oracle 会先把它读进 Buffer cache 中,再进行读、写。 通常,如果要读的块已经在 Buffer cache 中了,Oracle 将不必再访问磁盘数据文件, 这个读操作被称为逻辑读。也就是没有真正读写磁盘的意思。 如果要读的块不在 Buffer cache,那么 Oracle 必须到磁盘上将其调入 Buffer cache 中,这个操作被称为物理读。在将块物理读进 Buffer cache 后,Oracle 再从 Buffer cache 中逻辑读取需要的数据。 5.1.2 Buffer cache 大小的设置 在 10g 中,非常简单,我们不必设置 Buffer cache 的大小,只需设置一个 sga_target 参数,SGA 目标值,10g 自然会根据你的数据库的情况,自己调整共享池、Buffer cache 池 等等 SGA 中内存组件的大小。这一点我们在共享池部分也说过。db_cache_size 参数是设置 Buffer cache 大小的,和共享池一样,在 10g 中,它也只是个下限值。 Buffer cache 是磁盘数据文件的缓存,数据文件的基本读写单位是 Oracle 数据块,最 常见的块大小是 4KB、8KB、16KB 等等,在我们的实例库,数据库块大小是 8KB。也就是说 Oracle 一次读写将从数据文件中读写 8KB 字节。Buffer cache 既然是数据文件的直接缓存, 第 一 节 Buffer cache 工作原理  Buffer cache 作用  Buffer cache 大小的设置  Buffer cache 工作原理简述  LRU 的应用  脏块与脏 LRU 链的应用 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 99 Buffer cache 的内存块大小当然也是 8KB 了。 Oracle 从 9i 之后开始支持多重块大小,你在创建数据库时设置的块大小被称为标准块 大小,除了标准块大小外,我们还可以使用多个非标准的块大小。就像我们的实例库,标准 块大小是 8KB,我们还可以使用 2KB、4KB、16KB 和 32KB 的非标准块大小。 那么,数据库有必要使用多种块大小吗?绝对有必要。对于全表扫描操作,越大的块效 率越高。因为全表扫描要访问表所有的行,所有块。块越大,相同大小的表所占用的块就越 少。而每读一个块,Oracle 都算作一次物理读,表块越少,访问全表所需的物理读就越少。 因此,全表扫描操作适合大块。但对于索引扫描,索引扫描是一种随机的形式访问表中的行。 随机访问下小块效率更好。因为你每访问一个块,Oracle 都要将块读进 Buffer cahce,这 样做的目的是希望你再次访问块中其他行时,不必再从磁盘读取(从磁盘中读是物理读), 而直接从内存中读取(从内存中读是逻辑读)。随机访问很可能访问完块中一行后,就不再 访问此块了。因此是随机本身就是无规律的访问。因为小块占用的内存少,随机访问下,将 小块读入内存比将大块读入内存,能更节省内存空间,提高内存的使用效率。因此,随机访 问下,小块的效率比大块要好一些。还有,大块的争用可能性更高,而小块争用的可能性低。 因为大块中行比较多,而小块中行少。这就好像一个团队有 100 个人,另一个团队有 10 个 人,当然人越多越容易发生矛盾。总之,大块有大块的好处,小块有小块的好处,如果数据 库只能使用一种块大小,这显然是不太合理的。 我们已经在以前的章节中讲过了如何配置使用非标准块大小,这里就不再重试了。其要 点有如下两点:  只有配置了相应非标准块大小的 Buffer cache 池后,才能使用非标准块大小  非标准块大小是在表空间层指定的。一旦在表空间层确定了表空间所使用的块大小,以 后被创建在此表空间中的所有表、索引等等段对象,都将按表空间块大小为准。 有一定需要补充的是,用来配置非标准块大小 Buffer cache 的 初 始 化 参 数 db_16k_cache_size 、 db_2k_cache_size 、 db_32k_cache_size 、 db_4k_cache_size 、 db_8k_cache_size,它们并不在 Oracle 自动内存管理的范筹内,Oracle 并不会自动的调整 他们的大小。你将这些参数设置为多大,只要 SGA 内还有空闲内存,Oracle 将会立即分配 相应大小的内存空间。 5.1.3 Buffer cache 工作原理简述 1. Buffer cache 的内存组织形式: 上一章 Library cache 中基本的内存组织形式是零散的内存块(也称内存堆),比如说我 们经常提到的 6 号子堆,用于存贮执行计划、解析树等相关信息。Buffer cache 既然是用来 缓存数据块的,那么,它的内存组织形式就是以块为单位。 表中每一个方格,就是 Buffer cache 中一个 块。介绍一种常见的叫法。当块在磁盘上时,叫 “块”,在内存中时,叫“Buffer”。 Buffer cache 中每个 Buffer 的大小,当然和 块大小是一样的。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 100 2.HASH 假设 Buffer cache 有 500M,块大小是 8K。我们很容易就能算出来,Buffer cache 中共 有 500*1024/8= 64000 个 buffer 。Buffer 的目的,是为了提供逻辑读,假如我们需要读一个 块,Oracle 会首先看 Buffer cache 中有没有,如果 Buffer cache 中没有,再从磁盘读进 Buffer cache,从 Buffer cache 中将数据返回给用户。现在,用户发出一条声明:select * from table 。 这条声明需要读 5 号数据文件的 31508 号块。Oracle 首先要在 Buffer cache 中查找有没有这 个 5 号文件的 31508 块。如何以最快的速度,在几万个块中确定这个 31508 块有还是没有, 如果有地址是什么?当然非 HASH 算法莫数了。 Oracle 以每个块的文件号、块号和类型做 HASH 运算,得到 HASH 值。跟据 HASH 值, 到 HASH 表中取出指定块的内存地址。 哈希值 1 2 N 在上图中,BH 的全称是 Buffer header,在 BH 中记有 Buffer 的内存地址,它相当于上 一部分所讲述的 Library cache 中对象的句柄。在 Oracle 中,哈希值的数目是固定的,如果 哈希值数目过少,而块的数量过多,会有大量块共享一个哈希值。这些共享一个哈希值的块, 会用链表串起来,叫做 Cache buffer chain 链。在图 2 中,哈希值为 1 的共有 4 个块,这 4 个块就用一个 Cache buffer chain 链串了起来。Cache buffer chain 的条数和哈希值的数目是一 样的。用 Oracle 的一个隐含参数控制,我们后面的章节会详细讲到。其实从 Oracle 9i 后, Cache buffer chain 的条数一般远远多于内存中可用 Buffer 的数目,因此,多个 Buffer 共享一 个哈希值的情况已不多见了。 在 BH 中,除去和它对应的 Buffer 的地址外,也有一些其他信息。可参见下图: 6-10 Copyright © Oracle Corporation, 2002. All rights reserved. Buffer Header (kcbbh) LRU chain Hash chain Users list pid 4 pid 9 Waiters list Flags Obj # Class Buffer state CR env Mode RBAs (low, high and recovery) Change State CKPTQ and FQ Working set description TSN RDBAAFN FOQ (flag on queue) = 1 : on write list = 2 : on ping list kcbbh Lock element Buffer address pid 7 kcbbh Buffer Cache Hash bucket foq Stats (touch count and time) BH BH BH BH BH BH BH BH BH ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 101 上图中大部分内容,可以从 X$BH 视图中找到。此处,对这些内容只是作一个简单介 绍,在下面的一些章节我们会详细讲述部分重要的内容。 3. LRU 利用 HASH 方法,就可以在 Buffer cache 中快速定位要访问的块了。但如果 Buffer cache 中并没有我们要访问的块,这时,我们要从磁盘中将块读进 Buffer cache。首先,我们要先 在 Buffer cache 找到一个空块,如果没有空块的话,我们要找一个可以重用的块。无论空块 还是可重用的块,我们以后通称它们为 Free 块(自由块)。还按上面的例子来讲,如果 Buffer cache 有 500M,块大小 8K,那么共有 64000 个 Buffer。当系统繁忙时,这六万多个 Buffer 可能都被占用(也就是说,已经没有空块了)有些 Buffer 中正在进行修改,有些已经修改 完了,有些从磁盘块读进 Buffer 已经有一段时间了但很少再次访问,有些 Buffer 经常访问, 等等。此时,有一个块要被读进 Buffer,它该重用谁的空间呢?如何在这各种各样的情况下, 以有限数目的 Buffer,来提高系统的运行效率呢?首先要做的,就是要保证最常使用的块, 能最多的待在内存中。Oracle 如何做到这一点的呢?LRU 算法,最近最少使用算法。就是 优先重用最近最少使用的块。 Oracle 中另外建立了一套 LRU 链表,依照 Buffer 被访问的频率排列。如果 Oracle 需要 Free Buffer, 就从 LRU 中找。 哈希值 1 2 N 在 LRU 链表的每个节点中,不再记录过多 信息,只记录 BH 的地址(并不记录 Buffer 的 实际地址)。LRU 链的算法是 Oracle 改进的 LRU(最近最少使用)算法,链的的两端分别 称为热端、冷端。每个块跟据其访问的频率在链上排列。如下图: A B C D E F G H I J K L M N O P Q R 热冷端的分界,就是冷端头。在冷端头左边全部都是热块。冷端头的右边,全部都是冷块。 这个分界点受隐含参数_db_percent_hot_default 控制,此参数初始是 50%,即冷热各占一半。 当块第一次被读进 Buffer 时,会被插入到冷端头位置处。每次需要自由块时,Oracle 服务器 进程从冷端开始搜索可以被重用的块,因此,冷端的块是最有可能被重用的。在每个块的 Buffer header 中,准备有一个量,记录块被访问的次数,称作:Touch Count(TCH)。它代 表块的访问频率。Oracle 通过它决定块的冷热位置。下面我们介绍一下 Touch Count。 (1). Touch Count :接触点计数 BH BH BH BH BH BH BH BH BH LRU 热端头 冷端头 从此处开始搜索自由块 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 102 此数值代表 Buffer 的热度(访问频率)。此数值在内存中不受任何的保护,多个进程可 以同时修改它,因此这个值并不精确的表示块被访问的次数,它只表示一种趋势,代表一个 Buffer 是否被经常访问。 我们先了解它的最基本的一点,此数值如何增加。在 3 秒之内,无论用户从那个会话访 问一个块,无论访问多少次,此值加 1。 我们可以通过 X$BH 看到此值的变化,在 X$BH 中是 TCH 列 例 1: 访问表中的行,有两种方式,一种全表扫描,一种是通过 ROWID。如: Select * from table where id=1; ----全表扫描访问 Select * from table where rowid=’XXXX‘; ----通过 ROWID 访问 ROWID 有行所在的文件号、块号、行编号构成,通过 ROWID 访问行,是 Oracle 中最 直接、速度最快的方式。通过 DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)包, 可以从 ROWID 中解析出块编号,如下: Select DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID),rowid from t4_1 ; 在一个会话中,通过 ROWID 访问某表中任意一行,然后在另一会话中,通过如下声明, 观察 Touch Count 的变化: Select file#,dbablk,tch from x$bh where obj=(select object_id from dba_objects where object_name=’’ and owner=’’); 分别试一试在 3 秒之内连续访问一个 Buffer 多次 (例 1 完) Oracle 虽然是根据 TCH 值决定块应该在冷端还是热端,但当块的 TCH 值发生变化时, 并不会立即改变它所处的位置。服务器进程需要读块进 Buffer,它会从 LRU 链的尾端开始 搜索自由块,如果发现搜索到的块的 TCH 值小于 2,就重用这个块。如果 TCH 大于 2,就 把此块移到热端头部,并把它的 TCH 设为 0。2 这个数字是受隐含参数_db_aging_hot_criteria 控制。 下面我们来练习一个例子,加深一下印象。 例 2: 步 1:以 ROWID 方式,访问表 T4_1 的三个块,其中一个块只访问一次,另一块访问两次, 还有一块访问 10 次。 select * from t4_1 where rowid in ('AAAB0/AAFAAAHsUAAA'); --执行一次 select * from t4_1 where rowid in ('AAAB0/AAFAAAHsVAAA'); --执行两次,注意 3 秒间隔 select * from t4_1 where rowid in ('AAAB0/AAFAAAHsWAAA'); --执行 10 次,每次间隔 3 秒 显示每个块的 TCH: Select file#,dbablk,lru_flag,tch from x$bh where obj= (select object_id from dba_objects where object_name=’T4_1’ and owner=’SCOTT’); FILE# DBABLK LRU_FLAG TCH ---------- ---------- ---------- -------------------- ---------- ---------- ---------- 5 31508 1 1 5 31509 1 2 5 31510 1 10 步 2:运行一个要占用许多块的操作(不能是全表扫描的 SELECT,原因后有) scott@MYTWO> insert into big_table select * from t1; 已创建 240000 行。 步 3:重新显示 TCH sys@MYTWO> / ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 103 FILE# DBABLK LRU_FLAG TCH ---------- ---------- ---------- -------------------- ---------- ---------- ---------- 5 31509 9 0 5 31510 9 0 可以看到,无论 TCH 过去是 2 还是 10,一律清零,且 TCH 为 1 的块 31508 被重用了。通 过 LRU_FLAH 列,我们可以看到 31509、31510 已被移到了热端。 另外,在 Oracle 中,对全表扫描作另外的处理。Oracle 经过统计,认为全表扫描的块 很少被再次使用,因此 Oracle 倾向优先重用因全表扫描而占用的 Buffer。因此,全扫描块的 TCH 一直为 0,无论全扫描几次,TCH 不会增加。关于这一点,读者可以自己找个例子证 明。 4.LRUW 在 Oracle 中,Buffer cache 不但缓存用户进程读过的块,当用户对块修改时,比如更新 某表中的某一行。此更改也先在 Buffer cache 的块中完成,等待一定的时机再写往硬盘。Buffer cache 中被修改的块,称之为脏块(Dirty Buffer),在脏块被写到硬盘后,此块变的不脏。脏 块是不可重用的块(非自由块),因为其中有用户修改的信息。只有当块变的不脏后,才可 被重用。 如果在 LRU 链表中含有大量脏块,这势必加长了搜索自由块所需的时间。例如,如果 LRU 链中包含 30000 个块,现有 10000 个脏块分布在 LRU 链中,每次搜索自由块时,也必 需搜索大量的脏块,这些脏块是不可重用的,这必然浪费了一些时间。如果将脏块尽量从 LRU 链中移出,在上述例子中,搜索自由块时,则只需在 20000 个块中搜索即可。 从 LRU 移出的脏块也必须用一个链串起来,以方便需要时寻找。链接脏块的链就是 LRUW 链。 注意,一个块只能被缓存在 LRU 或 LRUW 某一个链上,不可能同时在这两个链上。如 下图所示。 图 5 块什么时候会被移进 LRUW 链呢?这里需要注意的是,当块变脏时,块并不立即移到 LRUW 中。块还会继续呆在 LRU 中。当下次服务器进程搜索自由块时,会将发现的所有脏 块一起移到 LRUW。 哈希值 1 2 N BH BH BH BH BH BH BH BH BH LRU LRUW ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 104 为什么不在块一变脏时,立即将块移到 LRUW?如果要这样做,适必增加了修改块时 必需要完成的工作。而且在链表间转移块,也必需要先获得保护链表的锁,即 latch。这样 一来,适必造成 latch 争用。不如在修改块后,暂不对块所处的链修改,而是等到以后搜索 自由块时,把发现的脏块一起进行移动。这也反应了 Oracle 在有关 Buffer cache 算法设计时 的宗旨,就是将单一的操作,尽可能的积攒,等待时机一起操作。这样也有利于提高单一操 作的完成速度。 LRUW 链共有两大作用,其一就是上面讲过的,存储脏块,减少 LRU 链上块的数目, 提高搜索 LRU 的效率。LRUW 链的另一作用,当然就是将脏块集攒起来,一起写进磁盘。 关于这一点,我们下面章节会详细论述。 另注:链表节点的移动是很快的操作。 5.1.4 LRU 的应用 任何缓存的大小都是有限制的,并且总不如被缓存的数据多。就像 Buffer cache 用来 缓存数据文件,数据文件的大小远远超过 Buffer cache。因此,缓存总有被占满的时候。 当缓存中已经没有空闲内存块时,如果新的数据要求进入缓存,就只有从缓存中原来的数据 中选出一个牺牲者,用新进入缓存的数据覆盖这个牺牲者。这一点我们在共享池中曾提及过, 这个牺牲者的选择,是很重要的。缓存是为了数据可以重用,因此,通常应该挑选缓存中最 没有可能被重用的块当作牺牲者。牺牲者的选择,从 CPU 的 L1、L2 缓存,到共享池、Buffer cache 池,绝大多数的缓存池都是采用著名的 LRU 算法,不过在 Oracle 中,Oracle 采用了 经过改进的LRU算法。具体的算法它没有公布,不过LRU算法总的宗旨就是――“最近最少”, 其意义是将最后被访问的时间距现在最远的内存块作为牺牲者。比如说,现在有三个内存块, 分别是 A、B、C,A 被访问过 10 次,最后一次访问是在 10:20,B 被访问过 15 次,最后一次 访问是 10:18,C 也被访问 10 次,最后一次被访问是在 10:22。当需要选择牺牲者时,B 访 问次数最多,牺牲者肯定不是它。A、C 访问次数一样,但 A 在 10:20 被访问,而 C 在 10:22 被访问,A 最后被访问的更早些,牺牲者就是 A。注意,这就是 LRU 的宗旨,“将最后访问时 间距现在最远的块作为牺牲者”。 为了实现 LRU 的功能,Oracle 在 Buffer cache 中创建了一个 LRU 链表,Oracle 将 Buffer cache 中所有内存块,按照访问次数、访问时间排序串在链表中。链表的两头我们分别叫做 热端与冷端, 如下图 当你第一次访问某个块时,如果这个块不在 Buffer cache 中,Oracle 要选将它读进 Buffer cache。在 Buffer cache 中选择牺牲者时,Oracle 将从冷端头开始选择,在上图的 例子中,内存块 U 将是牺牲者。 如上图,新块将会被读入 U,覆盖 U 原来的内容。这里,我们假设新块是 V。但是块 V ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 105 不会被放在冷端头,因为冷端头的块,会很快被当作牺牲者被覆盖的。这不符合“将最后访 问时间距现在最远的块作为牺牲者”的宗旨。块 V 是最后时间距当前时刻最近的,它不应该 作为下一个牺牲者。Oracle 是如何实现 LRU 的,我们继续看。 Oracle 将 LRU 链从中间分为两半,一半记录热端块、一半记录冷端块。如上图,而刚 刚被访问的块 V,如下图: 如过再有新的块进入 Buffer cache,比如块 X 被读入 Buffer cache,它将覆盖 T,并 且会被移至块 V 的前面,如下图: 大家可以想像一下,如果按照这面的方式继续下去,最右边冷端头处的块,一定是最后 一次访问时间距现在最远的块。那么,访问次数多的块是不会被选做牺牲者的,这一点 Oracle 是如何实现的?这很简单,Oracle 一般以 2 次为准,块被访问 2 次以上了,它就有 机会进入热端。 Oracle 为内存中的每个块都添加了一个记录访问次数的标志位,假设图中每个块的访 问次数如下: 如果现在又有新块要被读入 Buffer cache,Oracle 开始从冷端头寻找牺牲者,冷端头 第一个块 S,它的访问次数是 2,那么,它不能被覆盖,只要访问次数大于等于 2 的块,Oracle 会认为它可能会被经常访问到,Oracle 要把它移到热端,它会选择 R 做为本次的牺牲者: ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 106 块 S 会被从冷端移到热端,并且它的访问次数会被清零。此时,块 R 就是牺牲者了,因 为它的访问次数不到两次。 新块 Y 覆盖了块 R,并被移到了冷端块开始处,它的访问次数是 1。如果块 Y 再被访问 了一次,它的访问次数变为了 2: 虽然 Y 的访问次数达到了两次,但它不会马上被移到热端,它仍然留在原来的位置,随 着不断有新块加入,被插入到它的前面,它会不断的被向后推移。 如上图,又加入了很多的新块,Y 又被推到了冷端头,当再有新块进入 Buffer cache 时,Y 不会是牺牲者,它会被移到热端头 S 的前面,Y 后面的 Z,它的访问次数没有达到 2, 它将会是牺牲者。 好了,这就是 Oracle 中 Buffer cache 管理 LRU 的原理。按照这种方式运作,Oracle 可以把常用的块尽量长的保持在 Buffer cache 中。而且,每有新块进入 Buffer cache,Oracle 都会从冷端头处,从右向左搜索牺牲块。因为越靠近冷端,块的访问次数有可能越少、最后 的访问时间离现在最远。好了,LRU 链还没有讲完,下面,我们再讨论一下脏块与脏 LRU 链 的问题。 5.1.5 脏块与脏 LRU 链的应用 Oracle 中修改块的规则是只对 Buffer cache 中的块进行修改,并不直接修改磁盘中的 块。如果要修改的块不在 Buffer cache 中,Oracle 会先将它读入 Buffer cache,再在 Buffer ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 107 cache 中进行修改。当 Buffer cache 中的块被修改后,Oracle 会把它标记为“脏”块。脏 块含有脏数据,脏数据就是用户修改过的数据。Oracle 会定期的将脏块写到磁盘中。有一 个专门的后台进程就是专门负责写脏块到磁盘的,它就是 DBWn。我们也把 DBWn 写脏块到磁 盘这个过程叫做刷新脏块,刷新过后,脏块就不脏了,又变成了干净块。其实,假设有一个 块 A,如果 Buffer cache 中此块的数据和磁盘上块中数据不一致,那么,这个块就是脏块。 否则,就是干净块。当修改完成后,因为 Oracle 只修改 Buffer cache,因此,块中数据和 磁盘肯定不一致,这时块就是脏块。当块被刷新后,块被写到磁盘,那么,磁盘中块数据和 Buffer cache 中块的数据又是一致的,此时,块就又变成了干净块。 脏块在被写回磁盘前,也就是在它还是脏块时,它是不能被覆盖的,因为,脏块含有用 户修改过的数据,而这些数据还没被写到磁盘,如果此时覆盖了脏块,用户的修改结果将会 丢失。 设当前 LRU 链如上图所示,其中 V、L、O、P、Q 是脏块。当新的块要进入 Buffer cache 时,Oracle 从冷端头开始选择牺牲块,Q、P 和 O 都不能做作牺牲块,因为它们是脏块,N 是这一次的牺牲者,新进入的块将会覆盖 N,然后将 N 插入到 Y 之前。然后呢,下一次有块 进入 Buffer cache 时,Oracle 从冷端头开始搜索,它还要检查一边 Q、P 和 O,发现它们都 不能覆盖,再将 M 定为牺牲者。等等,每一次都要检查一边 O、P、Q,这太浪费时间了,Oracle 不会这么傻,Oracle 又准备了一个脏 LRU 链,专门保存脏块。当块变脏时,块不会马上被 移到脏 LRU 中,只有当 Oracle 从冷端头开始,寻找牺牲者时,才会将发现的脏块移动到脏 LRU 链中。这样做的目的我们刚才已经快要讲到了,就是下次再寻找牺牲者时,可以不用再 检查这些脏块。好,让我们继续看图,接着上图,有新块 Z 要进入 Buffer cache: 如上图,O、P、Q 将被移到脏 LRU 链中。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 108 冷端头变成了 N,N 的访问次数小于 2,它就是本次的牺牲者了。这样当下一次再需要 从冷端头开始寻找牺牲者时,就不用再检查 O、P、Q 这三个脏块了。当脏 LRU 链的长度,也 就是脏 LRU 链中的脏块达到一定数目时,DBWn 会开始刷新脏块。 通过上面所讲述的 LRU 链与脏 LRU 链的原理,我们可以发现 Oracle 把很多工作,都留 到了在 LRU 的冷端搜索牺牲者时。当块的访问次数增加的超过 2 时,块在 LUR 链的位置不变; 当块变脏时,块的 LRU 链位置也不变。只有当从 LRU 的冷端搜索牺牲者时,才会将发现的脏 块移到脏 LRU 链,将访问次数超过 2 的,插入到热端,这就是 Oracle 改进了的 LRU 算法。 Oracle 这样做的目的,是为了让我们平时的查询、修改所需完成的操作尽量的少。对于用 户的查询、修改操作,LRU 算法几乎没有任何的影响,额外所做的工作只是改变了几个标志 位而已,查询时增加访问次数标志位,修改块时还要设置脏块标志位。LRU 算法大部分的工 作,都是在寻找牺牲者时完成的。因此,有时寻找牺牲者这个过程有可能会出现等待,等待 事件就是 free buffer waits。  访问次数大于 2 的块太多,或才脏块太多,反正这些块都是不能覆盖的,Oracle 不得 不移动它们到它们该去的位置。当碰到的这样的块超过 LRU 中总块数的 40%时,也就是 说搜索了一小半 LRU 链,还是没有发现可以覆盖的牺牲者,Oracle 就不在找了,它会 唤醒 DBWn 刷新脏块。在 DBWn 刷新期间的等待,就会被记入到 free buffer waits 事件 中。另外,在资料视图中有一个资料 free buffer inspected,它记录了 Oracle 在所 有的寻找牺牲者的过程中,共计碰到了多少个不可覆盖的块。  在寻找牺牲者过程中发现脏块,Oracle 将其移动到脏 LRU 链,但是脏 LRU 链中脏块数 目达到限制,DBWn 被唤醒开始刷新脏块,Oracle 必须等待刷新脏块完毕,才能再继续 寻找牺牲者,这其间的等待事件,也会被记入 free buffer inspected。 总之,free buffer waits 事件发生的主要原因就是在 LRU 中寻找牺牲者的时间过长。 如果这个等待事件频繁出现,说明 Buffer cache 中脏块太多了,这通常是 DBWn 刷新速度慢 造成的。我们应该将 DBWn 更频繁的被唤醒去刷新脏块,好让它们变干净、可以被选为牺牲 者。我们不应该让脏块从脏 LRU 链中被刷新,因为这时通常会出现 free buffer inspected。 脏 LRU 链并不是为了将脏块集中到一起,让 DBWn 去刷新的,我们上面的图例中已经讲过, 将脏块移动到脏 LRU 链中,是为了减少下一次寻找牺牲者时,所需搜寻的块。Oracle 中另 有一个链表,准门用来记录脏块,好让 DBWn 定期刷新,这个链表是检查点队列。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 109 5.2 块的读 当用户访问某表中某些行时,行所在的块,会先被读入 Buffer cache。这需要 Buffer cache 中有一块空闲空间来容纳这些新读入的块。如果 Buffer cache 中尚有空块,空块将被使用, 并挂在 LRU 链表上。通常,第一次读块时,块会被挂在 LRU 链中部冷端开始处。这一过程 就是物理读。如果用户要访问 3 行,这 3 行在同一个块中,读取这 3 行需要一次物理读。当 块被物理读到 Buffer 中后,需要从 Buffer 中再读三次,分别取出三个用户访问的行,而这 三次对 Buffer 的访问,都是逻辑读。也就是说,每读一行,都需要一个逻辑读。 例 1: 如下声明,我们通过 ROWID 访问三行,这三行在同一块中。理论上,应该产生一个物 理读三个逻辑读: select * from t4_1 where rowid in ('AAAB0/AAFAAAHsUAAA','AAAB0/AAFAAAHsUAAB','AAAB0/AAFAAAHsUAAC') 在开始本次例子前,先要介绍如何观测一个声明所产生的逻辑读与物理读数目。观测的 方法有很多,最简单的就是使用 set autotrace 声明。下面介绍一下此声明。 在 Sql*Plus 中输入 set autotrace ,会看到它的简单的使用说明: scott@MYTWO> set autotrace 用法: SET AUTOT[RACE] {OFF | ON | TRACE[ONLY]} [EXP[LAIN]] [STAT[ISTICS]] On exp :按正常情况显示声明的输出,同时在后面补加上执行 计划。 Set autotrace On stat :按正常情况显示声明的输出,同时在后面补加上统计 资料。 On :按正常情况显示声明的输出,同时在后面补加上执行 计划和统计资料。 第 二 节 块的读  第一次读块时,块会被挂在 LRU 链中部冷端开始 处。这一过程就是物理读  用 ROWID 每读一行,都是一次逻辑读  全表扫描每读 arraysize 行算一次逻辑读  索引扫描和 ROWID 扫描类似 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 110 Trace exp :不显示声明原来的输出,只显示执行计划。 Trace stat :不显示声明原来的输出,只显示统计资料。 Trace :不显示声明原来的输出,显示统计资料和执行计划。 Off :关闭 Set auto ,恢复正常状态 下面,开始测试: 步 1:依次发布如下声明: Set autotrace trace select * from t4_1 where rowid in ('AAAB0/AAFAAAHsUAAA','AAAB0/AAFAAAHsUAAB','AAAB0/AAFAAAHsUAAC') Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE 1 0 CONCATENATION 2 1 TABLE ACCESS (BY USER ROWID) OF 'T4_1' 3 1 TABLE ACCESS (BY USER ROWID) OF 'T4_1' 4 1 TABLE ACCESS (BY USER ROWID) OF 'T4_1' Statistics ---------------------------------------------------------- 249 recursive calls 0 db block gets 58 consistent gets 4 physical reads 0 redo size 1484 bytes sent via SQL*Net to client 503 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 2 sorts (memory) 0 sorts (disk) 3 rows processed 加下划线的就是物理读与逻辑读,共中物理读(physical reads)为 4,逻辑读(consistent gets)为 58,这大大超我们的预料。这主要是为了更快的执行你发布的声明,Oracle 通常会 默默的为你做很多事,Oracle 在后台做的这些工作,通常称为递归调用。这在上一章已经说 过了,统计资料中的第一行就是递归调用的次数,本次声明总共发生了 249 次递归调用。看 倒数第三行,“2 sorts (memory) ”,这说明这些递归调用还产生了 2 次内存排序,而我们 的声明中根本就没有排序。这些递归调用影响了我们的观测结果。 除了递归调用外,Oracle 内部的运转对我们做各种测试都会有一些影响。对一些测试, 可以通过某种方法将影响降到最低。但对某些测试,却无法做到这一点。因此,总有一些测 试,我们只能得到大概的结果,这通常使我们无法深入理解 Oracle 的某些方面细节。甚至 有些测试得到的结果,只是针对某种特殊的情况、环境,如果把这些结果当作了通用的结果, 则会对我们产生误导。 针对当前的例子,有没有方法可以减少递归调用的影响呢?答案是肯定的,在第一部分 课程中,我们已经讲过,递归调用主要的作用是将用户要访问表的数据字典信息读进共享池, 如果再次访问表,无论声明是否可以共享执行计划,一定是可以共享这些数据字典信息的。 现在,我们已经执行过一次声明了,只要把刚才访问过的块从 Buffer cache 中清除,再次执 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 111 行声明时,就不会有递归调用的影响了。 步 2:执行如下操作,清除 Buffer cache 中的相关块。 select /*+index(t1 t1_myid)*/ * from t1 where myid<=180000; 在另一会话中查看 X$BH 视图: sys@MYTWO> @show_buffer 未选定行 好了,T4_1 的在 Buffer cache 中的块已经被清除出去了,但我们只执行了一条 SQL 声明, 只读取了一个表 T1,这不会对字典缓存有太大的影响。也就是说,我们再次执行第一步的 声明,将不会再有递归调用的影响了。 下面让我们再试一下: scott@MYTWO> select * from t4_1 where rowid in ('AAAB0/AAFAAAHsUAAA', 'AAAB0/AAFAAAHsUAAB','AAAB0/AAFAAAHsUAAC'); Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE 1 0 CONCATENATION 2 1 TABLE ACCESS (BY USER ROWID) OF 'T4_1' 3 1 TABLE ACCESS (BY USER ROWID) OF 'T4_1' 4 1 TABLE ACCESS (BY USER ROWID) OF 'T4_1' Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 3 consistent gets 1 physical reads 0 redo size 1484 bytes sent via SQL*Net to client 503 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 3 rows processed 可以看到,递归调用为 0,而 1 次物理读与 3 次逻辑读的次数,与我们的推测相吻合。 (例完) 结论:物理读是按块计算的,而逻辑读则是按读取的行数计算的。 根据上例,每从内存读出一行给用户,就发生一次逻辑读。这不是绝对的,很多时候 Oracle 可以在一次逻辑读中,读更多的行,不过逻辑读仍然是根据读取的行数计算的,只不过不一 定是每行一个逻辑读而已。因此,我们上面的结论仍然正确。 观察上例中执行计划部分,虽然执行计划是后面章节的内容,但此处所涉及的非常简单,并 不难理解。从执行计划中我们可以看到为了完成我们的操作,Oracle 执行了三次表访问: 2 1 TABLE ACCESS (BY USER ROWID) OF 'T4_1' 3 1 TABLE ACCESS (BY USER ROWID) OF 'T4_1' 4 1 TABLE ACCESS (BY USER ROWID) OF 'T4_1' 由上面三行可知,我们是通过 ROWID 进行的表访问,三次表访问,对应三次逻辑读。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 112 在上述三次表访问其间,每次取出一行,因此,我们的 3 个逻辑共取出 3 行。如果 Oracle 在一次表访问其间取出多行,我们的逻辑读将不再等于所访问的行数。看下面的例子: 例 2:发布如下 SQL 声明: select * from t4 where id>=1 and id<=2; Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=2 Bytes=184) 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T4' (Cost=3 Card=2 Bytes=184) 2 1 INDEX (RANGE SCAN) OF 'T4_ID' (NON-UNIQUE) (Cost=2 Card=2) Statistics ---------------------------------------------------------- 5 consistent gets 1386 bytes sent via SQL*Net to client 503 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 2 rows processed 我们只要求访问两行,但就有 5 个逻辑读。这是因为发布的声明使用了索引,读取索引 也需要逻辑读。暂时不管索引相关的逻辑读,记住,此时有 5 个逻辑读。依次发布下列声明, 并在每次执行后观察逻辑读的增长。 select * from t4 where id>=1 and id<=3; select * from t4 where id>=1 and id<=4; select * from t4 where id>=1 and id<=5; : : : : select * from t4 where id>=1 and id<=15; 直到此时,逻辑读一直保持为 5 。 select * from t4 where id>=1 and id<=16; 此声明发布后,逻辑读提升至 6 。 也就是说,访问 1 行、2 行、3 行 „„ 直到 15 行,都在一个逻辑读内完成。这是因为 Oracle 有一参数 arraysize。可以用 Show arraysize 显示会话当前的取值。Ser arraysize N 可以设置此 参数的值。注意,此参数是基于会话的。 显示一下当前会话的 Arraysize 值: scott@MYTWO> show arraysize arraysize 15 可以看到,是 15。也就是说,Oracle 在一次逻辑读中,返回 15 行。这就是为什么一直到 select * from t4 where id>=1 and id<=16; 时,才会又增加一次逻辑读的原因。 测试注意事项,上面的测试中,如果 T4 的 ID 列上并没有索引,这将会发生全表扫描。 这样,逻辑读会更多。在 Arraysize 值为 15 的情况下,如果要访问表中的 40 行,这 40 行分 布在表的第 5、6 个数据块上,这会产生多少逻辑读?搜索表的第一个块时,并没有行被读 出,但毕竟对这个块有过读取操作,这算是一个逻辑读。同样,读 2,3,4 块时,也都会有一 次逻辑读。在 5,6 块时,反回了共 40 行,因为 Arraysize 值为 15,40 行至少会产生 3 个逻 辑读。要返回的行都已经读取完了,是不是可以结束了。不是,因为 Oracle 不知道后面的 块中是否还会有满足条件的行,因此,Oracle 会继续读第 7,8,9,„„,直到最后一个块。 实际上,为了查找数据块中是否有满足条件的记录,这会发生一次逻辑读。还有段头、区头, ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 113 这些非数据块,每当读到这些块时,也会发生逻辑读。 简单点说,读行时,如果行的数目到达 Arraysize ,逻辑读加 1。那么如果读取行的数 目跨越块又会如何呢?也就是,读取 15 行,假如说 1—5 行在块 1,而 6—15 行在块 2 中, 逻辑读是一次还是两次呢!测试如下: 例 3: 步 1:首先观察 T4_1 表行在块中分布的情况。 首先,我们先简单介绍一下 ROWID(有关 ROWID 的详细介绍,到有关数据文件部分 章节时再详细讲述),Oracle 为每一行,分配了一个 ROWID,我们可以通过如下方式显示 ROWID: Select rowid from t4_1; ROWID ------------------ AAAB0/AAFAAAHsUAAA AAAB0/AAFAAAHsUAAB AAAB0/AAFAAAHsUAAC : : : : 这样的一样字符:AAAB0/AAFAAAHsUAAA ,就是行的 ROWID。Oracle 可以在行 ROWID 中提取出行所在的数据文件编号、块编号与行编号。通过 ROWID,Oracle 可以以 最快、最直接的方式找到行的所在并读取它。Orcle 也提供了一个 DBMS_ROWID 包,通过 可以从 ROWID 中看到行的位置,其中 dbms_rowid.rowid_block_number(rowid)可以从 ROWID 中提取块编号,这正是我们现在需要的。 输入如下声明, select rowid,dbms_rowid.rowid_block_number(rowid),rownum,id from t4_1 where rownum<=75; ROWID DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) ROWNUM ID ------------------ ------------------------------------ ---------- ---------- AAAB0/AAFAAAHsUAAA 31508 1 1 AAAB0/AAFAAAHsUAAB 31508 2 2 : : : : : : AAAB0/AAFAAAHsUAAi 31508 35 35 AAAB0/AAFAAAHsUAAj 31508 36 36 AAAB0/AAFAAAHsUAAk 31508 37 37 AAAB0/AAFAAAHsUAAl 31508 38 38 AAAB0/AAFAAAHsVAAA 31509 39 39 AAAB0/AAFAAAHsVAAB 31509 40 40 AAAB0/AAFAAAHsVAAC 31509 41 41 AAAB0/AAFAAAHsVAAD 31509 42 42 : : : : : : AAAB0/AAFAAAHsVAAi 31509 73 73 AAAB0/AAFAAAHsVAAj 31509 74 74 AAAB0/AAFAAAHsVAAk 31509 75 75 已选择 75 行。 从上面可以看到,ID 从 1 到 38 的行,在块 31508 中,ID 从 39 起的行在 31509 块中。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 114 步 2:观察逻辑读: scott@MYTWO> select * from t4_1 where id=1000000; Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE 1 0 TABLE ACCESS (FULL) OF 'T4_1' Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 57 consistent gets 0 physical reads 0 rows processed 通过上面这个无结果的查询,我们可以了解到 Oracle 扫描表中全部的块,查找 ID 为 100000 的行,共产生了 57 个逻辑读。接下来我们读出一行,看一看逻辑读的增加情况。 scott@MYTWO> select * from t4_1 where id=35; Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE 1 0 TABLE ACCESS (FULL) OF 'T4_1' Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 58 consistent gets 0 physical reads 1 rows processed 逻辑读增加到了 58。这证明扫描块 31508 中有没有满足条件的行,这产生一个逻辑读, 从 31508 中读取满足条件的行又会产生一个逻辑读。好,下面加大选取行的范围: scott@MYTWO> select * from t4_1 where id>=35 and id<=38; Statistics ---------------------------------------------------------- 58 consistent gets 4 rows processed 这并不增加逻辑读,通过前面的测试,我们知道,选取少于 15 行,都不会再增加逻辑 读。但是如果跨块呢?继续测试: scott@MYTWO> select * from t4_1 where id>=35 and id<=49; 已选择 15 行。 Statistics ---------------------------------------------------------- 58 consistent gets 15 rows processed 由步 1 观察到的块中行的分布情况可知,从 39 行开始,行就已经存储在 31509 块中了。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 115 但是跨块并不增加逻辑读。最后让我们选取 16 个行看看: scott@MYTWO> select * from t4_1 where id>=35 and id<=50; 已选择 16 行。 Statistics ---------------------------------------------------------- 59 consistent gets 16 rows processed 选取超过 15 行后,逻辑读增加 1。而跨块读并不增加逻辑读。 (例 3 完) 从例 3 中可知,扫描块中是否有满足条件的行,将会产生一个逻辑 IO。从块中读满足 条件的行产会产生额外的逻辑 IO。而只要在 Arraysize 范围内,跨块读并不会额外增加逻辑 IO。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 116 5.3 块的写 5.3.1 写脏块 脏块,标准的叫法是 Dirty Buffer,即被修改过的块。Oracle 中对块的修改也是在 Buffer cache 中完成,然后再写进磁盘。不过为了提高速度一般采用“延迟写”的技术。即脏块并 不立即写进磁盘,而是等脏块的数量攒到一定程度,或触发某种条件时,再成批将脏块写盘。 在脏块没有写盘前,由重做(redo)保护对脏块所做的修改。关于重做的内容,我们在以后 的章节详述。 和脏块有重要关联的就是另一个链:检查点队列(Checkpoint quene)。检查点队列是按 块第一次被修改的顺序排列。这样做的目的是为了实现“增点检查点”,详细的检查点相关 内容,到重做的有关章节再讨论,简单的论述马上就要讲到。为了能够将块按首次修改的顺 序链接进检查点队列,最简单的方法就是当块被修改后,立即将块加进检查点队列。 现在,总结一下前面提到过的脏块有可能所在的位置,LRU、LRUW 和检查点队列。 其中 LRU 与 LRUW 是互斥的,即脏块相么在 LRU 中,要么在 LRUW 中。但 LRU、LRUW 与检查点队列并不互斥,即脏块一般是既在 LRU 或 LRUW 中,又同时在检查点队列中。下 面,我们来介绍一下检查点与检查点队列。 5.3.2 增量检查点 检查点的主要目的是以对数据库的日常操作影响最小的方式刷新脏块。脏块不断的产生, 如何将脏块刷新到磁盘中去呢?在8i之前,Oracle定期的锁住所有的修改操作,刷新Buffer 第 三 节 块的写  写脏块  增量检查点  检查点队列  检查点设置  通过视图了解与脏块有关的信息 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 117 cache 中的所有脏块,这种刷新脏块的方式被称为完全检查点,这极大的影响了效率,从 9i 之后只有当关闭数据库时才会发生完全检查点。 从 8i 开始,Oracle 增加了增量检查点的概念,增量检查点的主要宗旨就是定期的刷新 一部分脏块。将脏块一次刷新完是不合理的,因为脏块不断产生,没有穷尽。像完全检查点 那样停止用户所有的修改操作,将脏块刷新完再继续,这绝对会极大的影响性能。所以增量 检查点的一次刷新部分块是脏块问题的最好解决办法。那么,每次刷新时,都刷新那些块呢? 根据统计研究,根据块变脏的顺序,每次刷新那些最早脏的块,这种方式最为合理。为了实 现这一点,Oracle 在 Buffer cache 中又建立了一个链表,就是检查点队列。每个块在它变 脏时,会被链接到检查点队列的末尾。就好像排队一样,9:00 来的人站在第一位,9:05 来 的人排第二位,以后每来一个人都站在队伍的末尾,这个队伍就是按来到的时间顺序排列的 一个队列。检查点队列就是这样,块在变脏时会被链到末尾。因此检查点队列是按块变脏的 时间顺序,将块排成了一个队列。 5.3.3 检查点队列 如上图,检查点队列中的每一节点,都指向一个脏块。检查点队列每个节点中的信息其 实非常少,就是记录对应块在 Buffer cache 中的地址,脏块对应的重做记录在日志文件中 的位置,另外还有前一个节点、后一个节点的地址。检查点队列还有 LRU、脏 LRU,这些都 是双向链表。双向链表就是在节点中记录前、后两个节点的地址。 检查点队列头部的块是最早变脏的,因此,Oracle 会定期唤醒 DBWn 从检查点队列头开 始,沿着检查点队列的顺序,刷新脏块。在刷新脏块的同时,仍可以不断的有新的脏块被链 接到检查点队列的尾部。这个定期唤醒 DBWn 刷新脏块的操作,Oracle 就称为增量检查点。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 118 如上图,1、2、3 号节点所指向的脏块已经被刷新为干净块。同时,又有两个块变脏, 它们被链接到了检查点队列的末尾,它们是 9 号、10 号节点。 检查点队列的头,又被称为检查点位置,Checkpoint postion,这些名称我们不必从字 面上去理解。总之,检查点位置就是检查点队列头。检查点队列头节点(也就是检查点位置) 的信息,Oracle 会频繁的将它记录到控制文件中,而且会很频繁的记录。一般是每隔三秒, 有一个专门的进程 CKPT,会将检查点位置记录进控制文件。 如上图,当前的检查点位置是检查点队列的 1 号节点。又一个三秒到了,CKPT 进程启 动,将新的检查点位置记入控制文件: ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 119 新的检查点位置是 4 号节点,它对应当前变脏时间最早的脏块。1、2、3 号节点已经从 检查点队列中摘除了。因为它们对应的脏块已经不脏了。一般来说,控制文件中的检查点位 置之后的块都是脏块。但是有时也例外,因检查点位置每三秒才会更新一次,就像上图,1、 2、3 号节点对应的脏块已经被刷新过了,但是由于三秒间隔没到,检查点位置还是指向 1 号节点。只有当三秒到后,检查点位置才会被更新到 4 号节点上。 关于检查点队列、检查点位置我们先说到这里,在全面的介绍什么是增量检查点之前, 我们先说一下检查点队列的一个重要作用。 让我们先来总结一下用户修改块时,Oracle 内部都发生了什么: 1.如果块不在 Buffer cache,将块读入 Buffer cache 2.先生成重做记录,并记入日志缓存,在用户提交时写到日志文件中 3.在 Buffer cache 中修改块 4.在 Buffer cache 中设置块的脏标志位,标志块变成脏块,同时在检查点队列末尾增加一 个新节点,记录这个新脏块的信息,信息包括:脏块在 Buffer cache 中的位置,在步骤 2 时生成的与此脏块对应的重做记录位置。 5.用户提交后,将相应的重做记录从重做缓存写入日志文件。 我现在将日志补充到上面的图中: ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 120 就像上图,检查点队列的每个节点,都保存有脏块的地址和脏块对应的重做记录的编号。 脏块在 Buffer cache 中的位置是随机的,用户不一定修改那个块。但重做记录是顺序生成 的,就和检查点队列的排列顺序一样。因为,它们都是当块被修改而变脏时产生的。块 A 先被修改,块 A 的重做记录就排在前面,块 B 后被修改,块 B 对应的重做记录会被排在块 A 对应的重做记录的后面。和它们在检查点中的顺序是一样。每当数据库因以外而当机,比如 异常死机、断电等等,Buffer cache 中有许多脏块没来的及写到磁盘上。以图为例,比如 说现在断电了,现在磁盘上还有 7 个脏块,它们里面有用户修改过的数据,Oracle 已经将 反馈信息“你的修改已完成”发送给用户,用户也以为他们的修改完成了,将会一直保存到 数据库中。但是,突然的断电,令这几个脏块中的数据丢失了,它们没来得及写到磁盘上。 Oracle 如何解决这个问题呢?很简单,当数据库重新启动时,Oracle 只需从控制文件 中读出检查点位置,检查点位置中记录有重做记录编号,根据此编号,Oracle 可以很快的 定位到日志文件中的重做记录 n,它读出重做记录 n 中的重做数据,将用户的修改操作重现 到数据库。接着,Oracle 读取重做记录 n+1 中的重做数据,重现用户修改,这个过程将沿 着日志流的顺序,一直进行下去,直到最后一条重做记录,在上图的例子中,最后一条重做 记录是第 n+6 条。这个过程完成后,用户所有的修改又都被重现了,一点都不会丢失。只要 你的日志文件是完整,日志流是完整的,就一点信息都不会丢失。 有人可能会有一个问题,重做记录在生成后,也是先被送进重做缓存,再由重做缓存写 往日志文件。这样的机制下,一定会有某些重做记录在没来的及写到日志文件中时,数据库 突然当机,而造成这些重做记录丢失。这样,这些重做记录所对应的脏块,将得不到恢复。 用户还是会丢失一些数据。 这种情况的确会发生,但丢失的都是没用的信息。为什么这么说的。Oracle 会在用户 每次发出提交命令时,将事务所修改脏块对应的重做记录写进日志文件,只有当这个操作完 成时,用户才会收到“提交完成”,这样的信息,对于一个完整的事务,当用户看到提交完 成后,也就意味着所对应的重做记录一定被写到了日志文件中,即使发生异常死机,它也是 绝对可以恢复。而当用户没有提交,或没来得及提交,数据库就崩溃了,那么事务就是不完 整的,这个事务必须被回滚,它根本用不着恢复。对于这样不完整的事务,它对应的重做记 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 121 录有可能丢失,但这无所谓了,因为不完整的事务根本不需要恢复。也就是说,只要用户的 事务提交了,用户的修改一定不会丢失。不过这还有一个前提,就是日志文件千万不能损坏, DBA 所要做的就是要保证日志文件不能损坏。DBA 可以使用 RAID1 这样的磁盘镜像技术,或 者多元备份日志文件,等等,这个我们在前面章节中已经讲过了的。 我们上面所讲到的这种恢复,是自动进行的,并且不需要 DBA 参与,它被称之为实例恢 复。 检查点队列与增量检查点的作用我们已经说的差不多了,它们的主要目的就是让 DBWn 沿检查点队列的顺序刷新脏块。还有,就是实例恢复。 下面我们来讨论一下增量检查点的设置。 5.3.4 检查点设置 这里所说的检查点设置,主要指增量检查点发生频率的设置。注意增量检查点只是一个 名词,不必按字面的意义去理解它。增量检查点发生时,Oracle 会唤醒 DBWn 沿着检查点队 列写脏块,这就叫发生了一次增量检查点。那么到底多长时间发生一次增量检查点呢?这个 增量检查点的频率是非常重要的,它基本上控制着 DBWn 多长时间去刷新一次脏块。DBWn 活 动的太频繁,会影响数据库的整体性能,如果 DBWn 活动太不频繁,又会使脏块积压太多, 这同样也会影响性能。而且,如果出现异常崩溃,需要实例恢复,脏块越多,实例恢复越慢。。 在 9i 之前 DBA 主要靠间隔时间等方式来设置增量检查点的频率,比如可以让 Oracle 每 10 分钟发生一次增量检查点。如果这个数字设置不合适,对数据库性能的影响是很大的。而且 有可能造成实例恢复时间过长。在 9i 之后,特别是到了 10g 中,检查点已经相当的智能化 了,很少会成为 I/O 问题的原凶。9i 中设置 fast_start_mttr_target 参数为你所期望的实 例恢复时间,系统将自动控制增量检查点的频率。比如,你希望实例恢复可以在 5 分钟内完 成,你可以将此参数设置为 300,也就是 300 秒。 如果此参数设置的值超出了硬件实际的限制,比如你将它设置为 60,你期望无论在任 何情况下,数据库都可以在 1 分钟内完成实例恢复,但根据数据库的脏块生成速度、存储设 备的写性能,1 分钟内根本无法完成实例恢复。这时候 Oracle 会自动设置合适的 fast_start_mttr_target 参数值,我们可以在参数文件中看到修正后的参数值,也可以在 V$instance_recovery 视图中的 Target_mttr 列中看到实际的值。例如: 我们不能将这个值设置的太小,因为实例恢复必竞只是偶然现象。如果为了让实例恢复 尽快完成,而设置 fast_start_mttr_target 为很小的值,那么 DBWn 将活动的很频繁,这会 造成性能问题的。 为了避免用户设置不合理的增量检查点频率,在 10G 中 , 如 果 将 fast_start_mttr_target 设置为 0,Oracle 将根据产生脏块的速度、存储硬件的性能自动 调节检查点的频率,尽量使检查点频率不成为 I/O 问题的原凶。 检查点的主要任务就是催促 DBWn 刷新脏块,如果 DBWn 刷新脏块时的等待事件太多,就 说明脏块太多、存储设备的写速度太慢,或者就是增量检查点的频率太高了,或太低了。DBWn 写脏块的等待事件是 Db file parallel write。如果你的增量检查点频率很低,你发现了 此事件,在排除了存储设备写性能的问题后,你应该将增量检查点频率设置的高一些。反之, 如果你的增量检查点频率本身很高,出现了 Db file parallel write 事件,这说明检查点 频率太高了,你应该将它调低一点。 除它之外,还有一个和 DBWn、增量检查点有关的等待事件,它是 Write complete waits 事件,当前台进程要修改 DBWn 正要成批写的块中的若干个块时,就会有此等待事件,这个 事件是前台进程在等待 DBWn 写完成。这个等待事件太多,说明了存储设备写性能有问题, 或者增量检查点太频繁了。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 122 我们可以在 V$instance_recovery 中看到有关检查点的很多信息,有这样几个列对我们 调节检查点最为重要,首先是 Estimated_mttr 列,它的值如果太大,说明检查点不够频繁, 同时也说明脏块产生的太多。 同时在 V$sysstat 资料视图中,还有两个资料 background checkpoints started、 background checkpoints completed,前面的一个是后台进程检查点开始次数,后一个是后 台进程检查点完成次数。后台进程检查点的意义,其实就是增量检查点。只有增量检查点才 是由后台进程触发的。如果你用 Alter system checkpoing 命令让系统完成完全检查点,这 叫做前台检查点与增量检查点无关,是不会被记入这两个资料了。如果这两个值经常相差一 些,比如检查点的开始次数比完成次数大的不止 1,这说明有太多次检查点开始,但没有及 时完成。这说明检查点太频繁或检查点完成的太慢。 5.3.5 通过视图了解与脏块有关的信息 我们看一看资料视图中,了解一下 Oracle 为我们提供了哪些观察写脏块的信息。 sys@MYTWO> select * from v$sysstat where lower(name) like '%physical writes%'; STATISTIC# NAME CLASS VALUE ---------- ---------------------------------------------------------------- ---------- ---------- 46 physical writes 8 856 47 physical writes non checkpoint 8 286 98 physical writes direct 8 24 100 physical writes direct (lob) 8 0 通过如上查询,Oracle 用来描述物理写的资料有三类,物理写(physical writes)、非检 查点物理写(physical writes non checkpoint)和直接物理写(physical writes direct 、physical writes direct (lob))。 查阅 Oracle 文档,有关的解释如下: · physical writes: 写到磁盘上的块的总的数量。此数值等"physical writes direct" 加上所有 的从 Buffer cache 中的写。 · physical writes non checkpoint:不是为了检查点的推进而被写的 Buffer 的次数。 作为一 个单位使用,为了确定利用设置 FAST_START_IO_TARGET 参数来限制恢复的 IO 数的 I/O 负担。本质上,此资料估算出了没有检查点发生情况下写的数目。从 physical writes 减去此 值,可以得到非检查点的额外的 I/O。 注意,此资料的单位是次数,而上一项资料的单位是块数。 · physical writes direct:绕过 Buffer cache 直接写到磁盘上的数量。 还有一项资料也是和我们马上要进行的讨论是相关的: · DBWR checkpoint buffers written :为检查点而写的 Buffer 数。 此章暂不讨论直接物理写,通过前两个资料,可知 Oracle 把物理写主要分做检查点相 关的写和检查点无关的写。我们上面已经提到,脏块可以在三个地方:LRU、LRUW 和检 查点队列,从检查点队列写出的块就是检查点相关的,从 LRU、LRUW 写出的块归为非检 查点相关的。在 Oracle 内部,把从 LRU 与 LRUW 写出的块,归为一类。 下面我们再讨论一下 Oracle 会在什么时机、完成什么样形式的写(检查点相关和检查点无 关)。脏块被写进磁盘后,又会如何。 1. 检查点相关写: 检查点队列的目的是为了在实例崩溃后,减少所需恢复的块数,从而减少恢复所需时间。 检查点队列的头,也被称为检查点位置(Checkpoint position),在此位置点前的块,无 论通过哪种写(检查占相关写或检查点无关写),反正都已被 DBWn 写进磁盘。而检查点位 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 123 置后面的块,有可能是还没来的及写进磁盘的。因此,发生实例崩溃需要恢复时。检查点位 置前的块是不必恢复的,因为已经被 DBWn 写进磁盘了。只需从检查点位置处向后进行恢 复即可。 完全的描述检查点的运作,要涉及 LGWR、重做和控制文件等方面的内容,我们到后 面章节再详细讲述。下面用一个简单的图例,说明检查点的运作: (a). 假设此时从 A 到 N 都是脏块 A B C D E F G H I J K L M N (b). 又产生了一些新的脏块,O――>T。不过 DBWn 也刷新了一部分脏块。但检查点位置 还没有更新。此时发生实例崩溃,仍需从 A 处开始恢复。检查点位置不可能随 DBWn 的写 进度,分毫不差的前进,这样的话会加重 DBWn 的负担,影响写的效率。实时上,检查点 位置通常 3 秒更新一次。 A B C D E F G H I J K L M N O P Q R (c).新检查点位置被更新。又有一些脏块加入。 H I J K L M N O P Q R S T 图 1 我们不止一次强调过,块在变脏的那一刻,会被立即送入检查点队列,如何验证此点呢? 看下面的例子: 利用 alter session set events 'immediate trace name buffers level 1'; 我们可以将 Buffer cache 中内存块的实际内容转存到磁盘上去,我们就利用它来观察 Oracle 完成修改时 Buffer 的状态。 下面我们分别测试一下按 ROWID 更新个别块,按条件更新少量块,和更新大量块时, 块是否立即在检查点队列中。 例 1: 步 1: 按 ROWID 更新个别块 先在一会话中发布: alter session set events 'immediate trace name buffers level 1'; 再在另一会话中发布更新声明: scott@MYTWO> update t4_1 set owner=lower(owner) where rowid in ('AAAB0/AAFAAAHsUAAA','AAAB0/AAFAAAHsVAAA'); 已更新 2 行。 (提交不提交均可) 打开转储文件,可以用两种方式找到相关 T4_1 的 Buffer。一是先查阅 X$BH 视图,得 到 BA(Buffer Address:Buffer 地址)列值,根据 BA 列值在转储文件中查找。或者查找 检查点位置 检查点位置 检查点位置 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 124 DBA_OBJECTS 视图,得到 T4_1 的 OBJECT_ID,根据此 ID 号在转储文件中查找。具体步 骤不再列出,以下是查找结果: BH (0x7B7D3814) file#: 5 rdba: 0x01407b14 (5/31508) class 1 ba: 0x7B40B000 set: 3 dbwrid: 0 obj: 7487 objn: 7487 hash: [7aa6f5d4,7aa6f5d4] lru: [7bfddfcc,7c3f7994] ckptq: [7aaa82d0,7aaa82d0] fileq: [7aaa83ac,7aaa83ac] st: XCURRENT md: NULL rsop: 0x00000000 tch: 1 flags: buffer_dirty gotten_in_current_mode redo_since_read LRBA: [0x9c.98d8.0] HSCN: [0x0000.005db4b0] HSUB: [1] RRBA: [0x0.0.0] BH (0x7BFDDF84) file#: 5 rdba: 0x01407b15 (5/31509) class 1 ba: 0x7BCEF000 set: 3 dbwrid: 0 obj: 7487 objn: 7487 hash: [7aa6f6f4,7aa6f6f4] lru: [7bbde958,7b7d385c] ckptq: [7aaac7bc,7b7f7b6c] fileq: [7aaac898,7aaac898] st: XCURRENT md: NULL rsop: 0x00000000 tch: 1 flags: buffer_dirty gotten_in_current_mode redo_since_read LRBA: [0x9c.98d8.0] HSCN: [0x0000.005db4b0] HSUB: [1] RRBA: [0x0.0.0] 上面的块是 31508 块,下面的是 31509 块。它们的状态中都有 Buffer dirty 字样,证明 它们都是脏块,它们的 ckptq(q,queue 队列这个单词的第一个字母)都不为空,这说明它 们已经在检查点队列中了。不在检查点队列中的块,应该形式为:ckptq: [NULL] 。 步 2:更新少量块与更新大量块:步骤基本同上,只要是脏块,ckptq 都不为空。 实事上,我曾把转储文件全部找查一遍,只要状态显示为脏(flags: buffer_dirty), Ckptq 必不为空。这基本上证实了我的想法,即,块在它变脏的时刻,进入检查点队列。而且,检 查点既然是按块首次变脏的顺序排列的,那么,从算法上讲,选择在它变脏时将它送入检查 点,也是最合理的时机。(例 1 完)。 每到一定的时机,DBWn 会沿着检查点队列的顺序,将脏块写进磁盘,变成可以被重 用的自由块。DBWn 会在什么时候写检查点队列中的块呢?其他参数我们已经在前面讲过 了,在这一章中我们只实验一个相关参数,LOG_CHECKPOINT_TIMEOUT,检查点超时参 数。如果将此参数设为 10 秒,会有一个 CKPT 进程,每 10 秒通知 DBWr 去写检查点队列 中的块,这被叫做,发生了一次增量检查点。 注:此参数在 9i 以后并不推荐使用,此处使用此参数只为可以更清楚看到增量检查点 的发生。例 2:增量检查点: 步 1:设置增量检查点发生时间: sys@MYTWO> alter system set log_checkpoint_timeout=10; 系统已更改。 步 2:如下命令打开时间提示符,然后开始更新: scott@MYTWO> set time on 21:29:45 scott@MYTWO> update t4_1 set owner=lower(owner) where rowid in('AAAB0/AAFAAAHsUAAA'); 已更新 3 行。 21:37:23 scott@MYTWO> 然后马上查看 31508 块的状态,应该是“脏”,通常在 10 秒以内,此块会变的不脏。这 说明 DBWn 已经将它写进磁盘。如果加大检查点超时参数的值,块由脏变干净的时间也会 相应加长。 2. 检查点无关写: ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 125 当需要从文件读块到 Buffer cache 中前,如果还有空块,就直接拿来用,如果没有空块 了,服务器进程会搜索 LRU 链查找自由块。 1). 在搜索 LRU 链时,服务器进程会将找到的脏块移到 LRUW。如果脏 LRUW 的长度超 出限制,服务器进程就会通知 DBWn 写 LRUW 中的脏块到磁盘。 2). 还有,如果服务器进程在搜索一定数量的 buffer 后,仍没有发现自由块,这说明 LRU 上的脏块已经非常多了,它也会去通知 DBWn 写脏块。只不过此情况下写的脏块有可能是 从 LRU 链上写出的。 我们曾在前的章节提到过,检查点是按块第一次变脏的顺序排列的。因此,Oracle 将脏 块送进检查点队列的最佳时机就是块变脏时。我们也做过相关的测试,基本上证明了此点。 也就是说,脏块也都在检查点队列中的。正常情况下,绝大部分脏块也都是由检查点队列中 写出的。以上两种检查点无关写,都是在情况比较紧急的情况下的写。特别是在第二种情况 下,没有可用的自由块了,这时再去触发检查点,由检查点通知 DBWn 写,写完再由相关 进程重新开始搜索。这太慢了,现在急需自由块,直接由 DBWn 从 LRU 上写出,然后马上 重用。这样速度会快一些。 无论从 LRU 还是从 LRUW 上写出的块,写完后要从检查点队列中摘除。因为一个块不 需要被两次以上的写。如果它还继续留在检查点队列中,势必会在增量检查点时再次被写, 这对系统资源是一种不必要的浪费。 还有一个检查点无关写,就是 DBWn 的超时,有很多资料在 Buffer cache 章节中曾明确 提到 DBWn 有一个三秒超时。到时,DBWn 搜索脏块,并从 LRU 移动到 LRUW,等到 LRUW 中脏块超出限制,DBWn 就会写脏块。因此,如果一段时间没有更新,DBWn 将写出所有 的脏块。这一点目前“存疑”,因为它和测试结果大相庭径。下面看测试: 例 2_4: 1). sys@MYTWO> alter system switch logfile; sys@MYTWO> alter system checkpoint ; scott@MYTWO> alter system set log_checkpoint_timeout=36000; --将检查点改为 10 个小时 的间隔 sys@MYTWO> @re_dbwr --初始化 SHOW_DBWR_TEMP 表 表已截掉。 已创建 19 行。 在一个会话中更新 T4_1 中的 16 个块: scott@MYTWO> update t4_1 set owner=lower(owner) where rowid in ( 'AAAB0/AAFAAAHsUAAA','AAAB0/AAFAAAHsVAAA','AAAB0/AAFAAAHsWAAA', 'AAAB0/AAFAAAHsXAAA','AAAB0/AAFAAAHsYAAA','AAAB0/AAFAAAHsZAAA', 'AAAB0/AAFAAAHsaAAA','AAAB0/AAFAAAHsbAAA','AAAB0/AAFAAAHscAAA', 'AAAB0/AAFAAAHsdAAA','AAAB0/AAFAAAHseAAA','AAAB0/AAFAAAHsfAAA', 'AAAB0/AAFAAAHsgAAA','AAAB0/AAFAAAHsiAAA','AAAB0/AAFAAAHsjAAA', 'AAAB0/AAFAAAHskAAA'); 已更新 16 行。 在另一会话查询有关 T4_1 的脏块: sys@MYTWO> @show_buffer BA PRV_HASH NXT_HASH PRV_REPL NXT_REPL S D LRU_FLAG TCH -------- -------- -------- -------- -------- - - ---------- ---------- --------- -------- -------- -------- -------- - - ---------- ---------- 7B597000 7AA6F4B4 7AA6F4B4 7BFD5154 7B7E5ED8 N N 8 70 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 126 7B839000 7AA6F5D4 7AA6F5D4 7BBD4E64 7BBD5AE0 N Y 0 1 7B83A000 7AA6F6F4 7AA6F6F4 7BBD5A24 7BBD5B9C N Y 0 1 : : : : : : 7B849000 7AA707D4 7AA707D4 7BBD6528 7BBD66A0 N Y 0 1 已选择 17 行。 每隔一段时间重复显示一下脏块的情况。看是否 DBWn 的三秒超时将脏块变干净了。 在此期间,不要有任何其他的动作。 大概半个小时后,脏块依旧脏着。会不会是 16 个脏块太少。凑不成一批,触发不了真 正的物理写。 2). 再更新 16 个块: scott@MYTWO> update t4_1 set owner=lower(owner) where rowid in ( 'AAAB0/AAFAAAHslAAA','AAAB0/AAFAAAHsmAAA', 'AAAB0/AAFAAAHsnAAA','AAAB0/AAFAAAHsoAAA','AAAB0/AAFAAAHspAAA', 'AAAB0/AAFAAAHsqAAA','AAAB0/AAFAAAHsrAAA','AAAB0/AAFAAAHssAAA', 'AAAB0/AAFAAAHstAAA','AAAB0/AAFAAAHsuAAA','AAAB0/AAFAAAHsvAAA', 'AAAB0/AAFAAAHswAAA','AAAB0/AAFAAAHs1AAA','AAAB0/AAFAAAHsyAAA', 'AAAB0/AAFAAAHszAAA','AAAB0/AAFAAAHs0AAA'); 已更新 16 行。 又等了一个多小时。脏块还是脏块。 3). 加大脏块产生的力度: update t4_1 set owner=lower(owner); T4_1 表共 54 个数据块。 一小时后,脏块不变。 4). update t4 set owner=lower(owner); sys@MYTWO> select count(*) from v$bh where dirty='Y'; COUNT(*) ---------- 260 脏块数已经有 260 个,等了大约三个小时,脏块不变。 5). 再更新 2000 行: scott@MYTWO> update t2 set owner=lower(owner) where myid>0 and myid<=2000; 已更新 2000 行。 sys@MYTWO> select count(*) from v$bh where dirty='Y'; COUNT(*) ---------- 361 脏块已有 361 个。20 分钟后,脏块不变。 6). 脏块到达 804 时,Buffer cache 共 16M,4096 个块。此时已将近 Buffer 总量的 20%。 10 分钟后脏块不变。 7). 脏块到达 981 时,超过 20%,脏块仍然没有任何改变。 8). 省略若干步,脏块到 1639 时,等待 20 分钟,仍没有变化。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 127 9). 最终当脏块到达 1643 个时,终于开始有变化了: 写脏块前的资料: sys@MYTWO> @show_dbwr STATISTIC# NAME VALUE A.VALUE-B.VALUE ---------- -------------------------------------------------------------------------- -------- --------------- 46 physical writes 9233 1566 47 physical writes non checkpoint 8061 1229 49 DBWR checkpoint buffers written 1622 0 53 DBWR make free requests 14 2 54 DBWR free buffers found 2916 0 55 DBWR lru scans 14 2 56 DBWR summed scan depth 3868 0 57 DBWR buffers scanned 3868 0 58 DBWR checkpoints 5 0 60 DBWR fusion writes 0 0 75 free buffer requested 108324 4221 76 dirty buffers inspected 3668 1158 78 hot buffers moved to head of LRU 8636 1 79 free buffer inspected 3669 1159 90 CR blocks created 129 0 91 current blocks converted for CR 0 0 92 switch current to new buffer 2732 389 152 background checkpoints started 5 0 153 background checkpoints completed 5 0 已选择19行。 从其中的差别可以看到,DBWR checkpoint buffers written 值前后不变,脏块是检查点无 关写。但这个检查点无关写并不是由三秒超时触发的。而是脏块的数量达到限制而引发的写 脏块。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 128 5.4 逻辑读的两种类型 在 Oracle 中共有两大类命令需要读块,DML 和 SELECT。DML 类命令将块读进 Buffer 是为了修改,此类命令产生的读,我们称为当前读。而 Select 命令所产生的读取操作,我们 称为一致读。一致读和当前读,我们也统称它们为逻辑读。 记得前面我们讲逻辑读时提到过的特性,逻辑读的次数,是按行计算。一致读是这样, 当前读也是这样。如下例子可以证明此点: scott@MYTWO> update t2 set owner=lower(owner) where myid>0 and myid<=100; 已更新 100 行。 Execution Plan ---------------------------------------------------------- 0 UPDATE STATEMENT Optimizer=CHOOSE (Cost=6 Card=100 Bytes=800) 1 0 UPDATE OF 'T2' 2 1 INDEX (RANGE SCAN) OF 'T2_MYID' (UNIQUE) (Cost=3 Card=100 Bytes=800) Statistics ---------------------------------------------------------- 410 recursive calls 105 db block gets 93 consistent gets 12 physical reads 24036 redo size 784 bytes sent via SQL*Net to client 832 bytes received via SQL*Net from client 第 四 节 逻辑读的两种类型  当前读 - 为修改而读块,就是当前读  一致读 - 因查询而读块,是一致读  当前读、一致读和起来是逻辑读 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 129 4 SQL*Net roundtrips to/from client 8 sorts (memory) 0 sorts (disk) 100 rows processed 上例显示中,共更新一 100 行,产生了一 105 次当前读。不必去管多余的 5 次是什么, 为了尽快的完成操作,Oracle 总会为我们作一些额外的工作。 一致读可以通过设置组(Arraysize,一次读所能读取的行数)大小,改变固定行数情况 下,一致读的次数。也就是说,一次一致读,我们可以读取多个行。在更新和删除时,当前 读的数量,不会小于你所修改的行数。而插入则又不一同,它所产生的当前读,按你插入的 行所占的块数计算,一般会略多于你实际插入的块数。如下例: scott@MYTWO> insert into aa_1 select myid from t2 where myid<=100; 已创建 101 行。 Execution Plan ---------------------------------------------------------- 0 INSERT STATEMENT Optimizer=CHOOSE (Cost=3 Card=100 Bytes=400) 1 0 INDEX (RANGE SCAN) OF 'T2_MYID' (UNIQUE) (Cost=3 Card=100 Bytes=400) Statistics ---------------------------------------------------------- 0 recursive calls 4 db block gets 3 consistent gets 0 physical reads 1468 redo size 788 bytes sent via SQL*Net to client 825 bytes received via SQL*Net from client 4 SQL*Net roundtrips to/from client 1 sorts (memory) 0 sorts (disk) 101 rows processed 我插入了 101 行,这 101 行只占一块,实际的当前读数为 4。 在 DML 命令运行中,为了找到所要操作的行的位置,会对块进行扫描,每扫描一个块, 都增加一次一致读。这一点从上面两个例子中可以看到。如果按 ROWID 直接定位块,则不 会产生额外的一致读: scott@MYTWO> update t4_1 set owner=lower(owner) where rowid in('AAAB0/AAFAAAHslAAA'); 已更新 1 行。 Execution Plan ---------------------------------------------------------- 0 UPDATE STATEMENT Optimizer=CHOOSE 1 0 UPDATE OF 'T4_1' 2 1 TABLE ACCESS (BY USER ROWID) OF 'T4_1' Statistics ---------------------------------------------------------- ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 130 0 recursive calls 2 db block gets 0 consistent gets 0 physical reads 424 redo size 788 bytes sent via SQL*Net to client 847 bytes received via SQL*Net from client 4 SQL*Net roundtrips to/from client 1 sorts (memory) 0 sorts (disk) 1 rows processed 如上例,按 ROWID 更新了一行,只产生了 2 个当前读,没有物理读,也没有一致读。 但是,如果有物理读时,一致读也会相应在增加: scott@MYTWO> update t4_1 set owner=lower(owner) where rowid in('AAAB0/AAFAAAHs4AAl'); 已更新 1 行。 Statistics ---------------------------------------------------------- 2 db block gets 1 consistent gets 1 physical reads 1 rows processed 这是为什么呢?下面我们要从另一个角度对逻辑读下个定义,逻辑读,就是要求得到 cache buffers chains latch 的次数。cache buffers chains latch 是保护 cache buffers chains 的一种 闩锁。关于 Cache buffers chains,我们在本部分内容最后一章――等待事件中将详细讲述它 的作用。简单点说,如果你想对 Hash Bucket 上的 Buffer 进行操作,无论是访问 Buffer,还 是要将块读进 Buffer cache,第一步就是先获得此闩锁。上例中,我们有一次物理读,块要 被加进 Buffer cache,获得 cache buffers chains latch 是必须的,每获得一次 cache buffers chains latch,就是一次逻辑读,而且此逻辑读不能作为 Db Block gets,只能作为一致读。因此,我 们就看到如上例中所示,一次物理读,一次一致读,两次当前读。实际上上例中的更新操作 并没有产生一致读,1 次一致读,代表为了将块链接进 Bucket,而对 cache buffers chains latch 加了一次闩锁。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 131 5.5 CR 块 5.5.1 CR 块的定义 CR,[C]onsist [R]ead,即一致读的简称。Oracle 中,对表的所有 DML 操作,不会阻塞 SELECT。当 SELECT 的行在另外会话被修改过,但还没有提交时,Oracle 会到回滚段中取 出块的前映像信息,构造一个和块没被修改前一模一样的块,供 Select 声明读取。这个一模 一样的块,就是 CR 块。每次访问同样的块,都会重新构造一个 CR 块,并不会从原来已有 的 CR 块中读行,因此 CR 块的 TCH 不会增加,也因此 CR 块可以很快被重用,但同一块的 CR 块的数量有一个上限,这个上限一般是 5。 CR 块也是 Buffer,大量的 CR 块会占用过多的 Buffer cache,对性能造成恶劣影响。只 有对 CR 块的产生、重用、老化有一个比较详细的了解,才能在操作中得心应手,提高性能。 另外,CR 块是 Oracle 实现“写不阻塞读”这一重要创新的基石,因此,避免 CR 块的 产生是不可能的。 5.5.2 单一会话中的 CR 块 X$BH 视图中的 STATE 列,表示块的状态,意义如下: STATE=0 : Free 块 STATE=1 : XCUR 块,当前模式的块,和上一章中的当前读不一样,此处的 XCUR 代表 非 CR 块的逻辑读。只能为当前实例独占。 STATE=2 : SCUR 块,当前模式块,为所有实例共享(用于 RAC) 第 五 节 CR 块  CR 块的定义  单一会话中的 CR 块  多会话中的 CR 块:SELECT 篇  多会话中的 CR 块:UPDATE 篇  多会话中的 CR 块:Insert、Delete 篇  补充说明 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 132 STATE=3 : CR 块 STATE=4 : READ,正在读的块。 STATE=5 : MREC,正在介质恢复中的块 STATE=6 : IREC,正在实例恢复中的块 STATE=7 : WRITE,正在写的块 STATE=8 : PI,[P]ost [I]mage 块 通过 STATE 列,我写了一个显示表块状态的脚本:Show_cr.sql。 1. Insert :不产生 CR 块 例 4: 步 1:在会话 1 中: sys@MYTWO> @show_cr 输入 name 的值: mytest6_3 未选定行 步 2:在会话 2 中: scott@MYTWO> insert into mytest6_3 values(4,4,'D'); 已创建 1 行。 步 3:在会话 1 中再次查看: sys@MYTWO> @show_cr 输入 name 的值: mytest6_3 OBJECT_NAME STATE DBARFIL DBABLK BA --------------- ----- ---------- ---------- -------- MYTEST6_3 xcur 7 530 7B93E000 MYTEST6_3 xcur 7 529 7B93F000 529 块是块头。530 是我们要插入的块,为了要在 530 块中插入,先要把此块读进 Buffer。 Oracle 中,任何块的修改都是在 Buffer 中完成,如果 Buffer 中没有要操作的块,先从磁盘 读入再进行操作。Oracle 不会对磁盘上的块直接操作。 如果表有索引的话,插入操作对索引也会产生 CR 块。我们的测试表就有索引。索引名 MT6_3_ID1,是原名 ID1 列上的索引。 sys@MYTWO> @show_cr 输入 name 的值: mt6_3_id1 OBJECT_NAME BA STATE DBARFIL DBABLK TCH --------------- -------- ----- ---------- ---------- ---------- MT6_3_ID1 7B93D000 xcur 5 120964 3 例 4 已经证明,插入无论对表还是索引,均不产生 CR 块。 2. 更新 更新分两种情况, 1). 全表扫描的更新: 步 1:确定 mytest6_3 的相关块(包含索引)都不在 Buffer cache 中。然后在会话 2: scott@MYTWO> update mytest6_3 set id1=100 where id2=1; 已更新 1 行。 步 2:检查 CR 块 sys@MYTWO> @show_cr 输入 name 的值: mt6_3_id1 OBJECT_NAME BA STATE DBARFIL DBABLK TCH ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 133 LRU_FLAG --------------- -------- ----- ---------- ---------- ---------- ---------- MT6_3_ID1 7B645000 xcur 5 120964 1 0 sys@MYTWO> @show_cr 输入 name 的值: mytest6_3 OBJECT_NAME BA STATE DBARFIL DBABLK TCH LRU_FLAG --------------- -------- ----- ---------- ---------- ---------- ---------- MYTEST6_3 7C35F000 xcur 7 529 1 0 MYTEST6_3 7B9F6000 cr 7 530 0 0 MYTEST6_3 7B637000 xcur 7 530 1 0 结论,因为只对表中一个块:530 ,进行了操作,所以有一个 CR 块,就是 530。因为 没有访问索引,所以并没有索引相关的 CR 块。 步 3:在会话 B 做同样的更新,在会话 A 查看,可以发现 CR 块又增加一个。 OBJECT_NAME BA STATE DBARFIL DBABLK TCH LRU_FLAG --------------- -------- ----- ---------- ---------- ---------- ---------- MYTEST6_3 7C35F000 xcur 7 529 2 0 MYTEST6_3 7B9F6000 cr 7 530 0 0 MYTEST6_3 7B637000 xcur 7 530 2 0 MYTEST6_3 7C30F000 cr 7 530 0 0 步 4:继续在会话 B 发布同样的更新,在会话 A 中查看。可以发现,CR 块的个数到 5 之后, 就不再增加了。同一个块的 CR 块的数量,Oracle 是有限制的。超过此限制,就会重用 CR 块。隐藏参数_db_block_max_cr_dba 限制 CR 块的最大数目,此参数默认为 6 。此数字 包含一个非 CR 块,因此任一块的 CR 块数量最多为 5。 2). 索引区间扫描的更新 步 1:确保 Mytest6_3 表相关块不在 Buffer cache 中,然后在会话 2 中: scott@MYTWO> update mytest6_3 set id2=100 where id1=100; 已更新 1 行。 步 2: sys@MYTWO> @show_cr 输入 name 的值: mytest6_3 OBJECT_NAME BA STATE DBARFIL DBABLK TCH LRU_FLAG --------------- -------- ----- ---------- ---------- ---------- ---------- MYTEST6_3 7B491000 xcur 7 530 1 0 sys@MYTWO> @show_cr 输入 name 的值: MT6_3_ID1 OBJECT_NAME BA STATE DBARFIL DBABLK TCH LRU_FLAG --------------- -------- ----- ---------- ---------- ---------- ---------- MT6_3_ID1 7BBA6000 cr 5 120964 1 0 MT6_3_ID1 7B9D7000 xcur 5 120964 0 0 可以看到,通过索引扫描对表行修改,即使没有修改索引列,索引上也会出现 CR 块。 但表上没有 CR 块。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 134 注意,如果通过索引扫描更新 0 行,照样会出现索引 CR 块。全表扫描也一样,更新 0 行, 会出现表的 CR 块。 3. 删除 经实验,同 UPDATE 总结如下表: INSERT UPDATE DELETE FULL SCAN 表 N Y Y 索引 N N N INDEX SCAN 表 N N N 索引 N Y Y 表 5_1 5.5.3 多会话中的 CR 块:SELECT 篇 1. 更新非索引列后全表扫描与索引扫描 步 1:在会话 B 中更新一行 scott@MYTWO> update mytest6_3 set id2=100 where id1=1; 已更新 1 行。 Execution Plan ---------------------------------------------------------- 0 UPDATE STATEMENT Optimizer=CHOOSE 1 0 UPDATE OF 'MYTEST6_3' 2 1 INDEX (RANGE SCAN) OF 'MT6_3_ID1' (NON-UNIQUE) 如上命令,按索引扫描更新一行。这会在索引上产生一个 CR 块。 步 2:在会话 C 中全表扫描,然后查看 CR 块 scott@MYTWO> select * from mytest6_3; 已选择 6 行。 Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE 1 0 TABLE ACCESS (FULL) OF 'MYTEST6_3' sys@MYTWO> @show_cr 输入 name 的值: mytest6_3 OBJECT_NAME BA STATE DBARFIL DBABLK TCH LRU_FLAG --------------- -------- ----- ---------- ---------- ---------- ---------- MYTEST6_3 7BA66000 xcur 7 529 1 0 MYTEST6_3 7C0D3000 cr 7 530 0 0 MYTEST6_3 7B89E000 xcur 7 530 1 0 sys@MYTWO> @show_cr 输入 name 的值: mt6_3_id1 OBJECT_NAME BA STATE DBARFIL DBABLK TCH LRU_FLAG ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 135 --------------- -------- ----- ---------- ---------- ---------- ---------- MT6_3_ID1 7C3C2000 cr 5 120964 1 0 MT6_3_ID1 7BE7A000 xcur 5 120964 0 0 经过全表扫描后,索引上的 CR 块并不增加,表上新增了一个 CR 块。 步 3:在会话 C 上执行索引扫描 scott@MYTWO> select * from MYTEST6_3 where id1>=0; 已选择 6 行。 Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'MYTEST6_3' 2 1 INDEX (RANGE SCAN) OF 'MT6_3_ID1' (NON-UNIQUE) 步 4:查看表的 CR 块 sys@MYTWO> @show_cr 输入 name 的值: mytest6_3 OBJECT_NAME BA STATE DBARFIL DBABLK TCH LRU_FLAG --------------- -------- ----- ---------- ---------- ---------- ---------- MYTEST6_3 7BA66000 xcur 7 529 2 0 MYTEST6_3 7B4D4000 cr 7 530 0 0 MYTEST6_3 7C0D3000 cr 7 530 0 0 MYTEST6_3 7B89E000 xcur 7 530 1 0 步 5:查看索引的 CR 块 sys@MYTWO> @show_cr 输入 name 的值: mt6_3_id1 OBJECT_NAME BA STATE DBARFIL DBABLK TCH LRU_FLAG --------------- -------- ----- ---------- ---------- ---------- ---------- MT6_3_ID1 7C3C2000 cr 5 120964 1 0 MT6_3_ID1 7BE7A000 xcur 5 120964 0 0 CR 块并无增加。因为在步 1 中更新的是 ID2 列,此列上并无索引,因此索引不产生 CR 块。 下面测试一下更新索引列后的结果: 2. 更新索引列后全表扫描与索引扫描 步 1:在会话 B 中更新有索引的列:ID1 scott@MYTWO> update mytest6_3 set id1=100 where id1=1; 已更新 1 行。 Execution Plan ---------------------------------------------------------- 0 UPDATE STATEMENT Optimizer=CHOOSE 1 0 UPDATE OF 'MYTEST6_3' 2 1 INDEX (RANGE SCAN) OF 'MT6_3_ID1' (NON-UNIQUE) 步 2:在其他会话全表扫描一定会在表产生 CR 块。我们实验一下在索引上的 CR 块上的产 生。我们接着上面的例子,索引上已经有了一个 CR 块。更新索引列时,更新命令会再产生 一个 CR 块,此时已经有 2 个 CR 块了。我们在会话 C 中执行索引扫描: scott@MYTWO> select * from MYTEST6_3 where id1>=0; 已选择 6 行。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 136 Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'MYTEST6_3' 2 1 INDEX (RANGE SCAN) OF 'MT6_3_ID1' (NON-UNIQUE) 步 3:查看索引的 CR 块: sys@MYTWO> @show_cr 输入 name 的值: MT6_3_ID1 OBJECT_NAME BA STATE DBARFIL DBABLK TCH LRU_FLAG --------------- -------- ----- ---------- ---------- ---------- ---------- MT6_3_ID1 7B833000 cr 5 120964 1 0 MT6_3_ID1 7C3C2000 cr 5 120964 1 6 MT6_3_ID1 7B6C9000 cr 5 120964 1 0 MT6_3_ID1 7BE7A000 xcur 5 120964 5 0 变成了 3 个,新增了一个。如果更新索引列,然后在另外会话执行索引扫描,会产生 CR 块。但全表扫描并不会产生 CR 块。因为虽然索引发生了改变,但如果不去访问它,自 然不会有 CR 块产生。我们在步 4 中证明如下: 步 4:在会话 C 中全表扫描 scott@MYTWO> select * from MYTEST6_3; 已选择 6 行。 Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE 1 0 TABLE ACCESS (FULL) OF 'MYTEST6_3' 步 5:查看索引的 CR 块: sys@MYTWO> @show_cr 输入 name 的值: MT6_3_ID1 OBJECT_NAME BA STATE DBARFIL DBABLK TCH LRU_FLAG --------------- -------- ----- ---------- ---------- ---------- ---------- MT6_3_ID1 7B833000 cr 5 120964 1 0 MT6_3_ID1 7C3C2000 cr 5 120964 1 6 MT6_3_ID1 7B6C9000 cr 5 120964 1 0 MT6_3_ID1 7BE7A000 xcur 5 120964 5 0 没有变化。因此没有访问索引。 5.5.4 多会话中的 CR 块:UPDATE 篇 具体的测试,我不在一一列出,由操作者自己动手实践。我只将结果列出如下: 当在会话 A 更新后,在会话 B 进行如下操作:(更新命令在会话 A 中所产生的 CR 块 数量本章前面前面几节已经讲过,此处不在论述) SELECT UPDATE DELETE ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 137 更新索 引列 更新非索 引列 更新索 引列 更新非索 引列 更新索 引列 更新非索 引列 FULL SCAN 表 Y Y Y Y Y Y 索 引 N N N N N N INDEX SCAN 表 Y Y N N N N 索 引 Y N Y N Y N 注:无论索引还是表,无论在会话 A 的更新有没有涉及索引列,在会话 B 的插入,均不新 增 CR 块 5.5.5 多会话中的 CR 块:Insert、Delete 篇 Insert、Delete,是对所有列的操作,相当于上表中“更新索引列”中的结果。 SELECT UPDATE DELETE 更新索 引列 更新非索 引列 更新索 引列 更新非索 引列 更新索 引列 更新非索 引列 FULL SCAN 表 Y Insert 、 Delete 是 对 所 有 列 操作,无法 仅 对 非 索 引列操作 Y 无 意 义 Y 无 意 义 索 引 N N N INDEX SCAN 表 Y N N 索 引 Y Y Y 5.5.6 补充说明 1. 先看下例: 步 1:在会话 A 更新行甲: update mytest6_3 set id1=-1 where rowid='AAAB1+AAHAAAAISAAA'; 步 2:在会话 B 查看行乙: select id1,id2 from mytest6_3 where rowid='AAAB1+AAHAAAAISAAC'; ID1 ID2 ---------- ---------- 3 3 步 3:查看是否有 CR 块: sys@MYTWO> @show_cr 输入 name 的值: MYTEST6_3 OBJECT_NAME STATE DBARFIL DBABLK BA --------------- ----- ---------- ---------- -------- MYTEST6_3 xcur 7 530 7BCF0000 MYTEST6_3 xcur 7 529 7BCF3000 MYTEST6_3 cr 7 530 7BCED000 有 CR 块产生。只要行甲与行乙同在一块中,在会话 A 修改行甲,在其他会话访问行 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 138 乙,就会产生 CR 块。这说明了什么。这说明 Oracle 在为用户提供数据时,根本就不看用户 所要求的行是否正被其他会话修改,只要用户所要求行所在块上有修改,Oracle 就会为此块 构造一个它以前的版本,这就是 CR 块了。 CR 块是脏块,这根据标志列(X$BH.FLAG)可以看出,但它永远不会被写进磁盘, 因为不需要。CR 块可以很快被重用,只要在 LRU 列冷端遇到 CR 块,就可以重用。另外, 同一块的 CR 块数量超过 5 个,也会重用以前的 CR 块。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 139 5.6 等待事件 5.6.1 两个查看等待事件的脚本 脚本 1: col event for a35 col sid for 999 select * from v$session_event where sid>=8 and event not like 'SQL*Net%' order by sid; 脚本 2: col event for a35 col sid for 999 col seq# for 999999 col p1text for a10 col p2text for a10 col p3text for a10 col p1 for 999999 col p2 for 999999 col p3 for 999999 select * from v$session_wait where event not like 'SQL%' and event<>'null event' and sid>=8 order by sid; 第 六 节 等待事件  两个查看等待事件的脚本  cache buffers chains 与 cache buffers chains latch  Cache Buffer LRU chain latches  buffer busy waits 等待  Free Buffer waits 等待事件 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 140 5.6.2 cache buffers chains 与 cache buffers chains latch 1. cache buffers chains:用来将某一 Hash Bukect 中的 Buffer 链接起来的链。如下图: 图 6_1 上图中,每个 Hash 值后的 Hash Bukect 都有一个 Cache buffers chains。每个块按照它的 文件号、块号、行号与块类型计算出一个哈希值,然后块被链接到相应哈希值后的 Cache buffers chains。 Hash Bucket 的数量(或说 Cache buffers chains 的数量)事先是固定好的。由 Oracle 隐 藏参数:_db_block_hash_buckets 确定。在我的机器上默认值是 7841。这个数字,随着你的 Buffer cache 容量的为同而不同。我的测试环境共有 3916 ( 实 际 大 小 是 4096 , db_cache_size/db_block_size)个可用 Buffer,Bucket 的数量 7841 要大于 Buffer 的数量。即 使是这样,由于哈希算法的原因,仍会有一些 Buffer 被串到某一个 Bucket 下。在 X$BH 视 图中,NXT_HASH 和PRV_HASH列分别代表Cache buffers chains链上一BH(Buffer header)、 下一 BH 的地址。当此两值相等时,Cache buffers chains 上只有一个 BH。 sys@MYTWO> select count(*) from x$bh where nxt_hash=prv_hash; COUNT(*) ---------- 3648 在我的测试环境中,目前共有 3648 个 BH,独占一个 Cache buffers chains。也可以如下 获得 Cache buffers chains latch 的个数: select count(*) from v$latch_children where name='cache buffers chains' 每个 Cache buffers chains 上的 BH 越少越好,链上的 BH 少,链上的竞争就少。为了保 护链的访问,Oracle 为链专门设置了一类闩:Cache buffers chains latch(CBC latch)。在 9i 之前,每一个链对应一个闩。但在 9i 后,由于改变了算法,每个链上的 BH 比较少,因此, 一般用一个闩保护多个链。也就是 CBC latch 的数量并不等于 CBC 的条数。 CBC latch的数量由隐藏参数_db_block_hash_latches控制。在我的机器上是1024。用 7841 除以 1024,得 7.305,也就是说,每个 CBC latch 要保护 7 或 8 个 CBC。 CBC latch 的数量默认情况如下计算,计算过程了解即可,不必深究: • 少于 2052 个时,_db_block_hash_latches = power(2,trunc(log(2, 内存块数量 - 4) - 1)) • 多于 131075 个时,_db_block_hash_latches = power(2,trunc(log(2, db_block_buffers - 4) - 6)) 哈希值 1 2 N BH BH BH BH BH BH BH BH BH 这就是 cache buffers chans。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 141 • 位于 2052 与 131075 buffers 之间,_db_block_hash_latches = 1024 访问Buffer cache中Buffer的过程,第一步计算HASH值,根据HASH值获得CBC latch, 然后在 CBC 上扫描目的 BH,找到目的 BH 后,就释放 CBC latch,开始去 PIN 目的 BH。 当数据库在 hash chain 搜索需要的数据块时,必须先获得 cache buffers chains latch。然 后在扫描 hash chain 的过程中会一直持有该 latch,直到找到所要的数据块才会释放该 latch。 当有进程一直在扫描某条 hash chain,而其他进程也要扫描相同的 hash chain 时,其他进程 就必须等待类型为 cache buffers chains 的 latch。此时,就发生了 latch free 等待事件。也就 是说,此 Latch 是不能共享读的。关于更多的 Latch 的资料,我们到 Latch、锁章节再讲。 在 Oracle 9i 后,每个 CBC 上的 BH 一般不会太多,因此,此 CBC Latch 持有的时间是 很短的。但是,如果在某一时刻,有很多进程频繁访问一个块时,仍会出现此等待事件。 2. 简介有关闩的视图:V$LATCH_CHILDREN 详细的介绍到专门有关等待事件和闩的章节再讲。我们先说它之中的三列: addr,latch#,child#,name。 · Addr:闩的地址。 · latch#:闩的编号。CBC latch 是 97。 · child#:子闩的编号。CBC Latch 共有 1024 个,每一个 CBC latch 是一个子闩。子 闩编号从 1 到 1024。 · Name:闩的名字。 第一列 ADDR 需要注意,在 X$BH 中,有一个 hladdr 列,是 Bucket Header 所在 CBC 的地址。如果你发现某一个 CBC latch 上有等待,通过用 V$LATCH_CHILDREN.ADDR 列 和 X$BH.hladdr 列关联,可以得知造成 CBC latch 等待的块的文件号和编号。 3. 观察 Cache buffer chain latch: 我们可以同时执行多遍如下语句:select * from t4; 。在另一个视图一遍一遍的查看 v$session_wait 视图。不过一般我们看不到 CBC latch 等待事件,因为 Latch 的持有都是很短 的。我们可以在每次执行 T4 表扫描时,执行 10046 跟踪,就能在跟踪文件中看到 CBC latch 事件了,如下: alter session set events '10046 trace name context forever,level 12'; select * from t4; alter session set events '10046 trace name context off'; (另注:为了避免物理读对我们测试可能造成的影响,可以先用 alter table t4 cache; 将 T4 的所有块送进 Buffer cache 中。) 此等待事件的 P1 参数是 Latch 的地址,P2 参数 Latch 的编号,P3 参数是试图得到 Latch 且睡眠的次数。在此例子中,我们根据 P1、P2 能够找到引起 CBC latch 等待事件的块,就 行了。进一步的解决方案,要结合其他方面的知识。 补充一点关于 CBC latch 的调优,Oracle 公司建议将此闩的数量设为比 Buffer cache 中 Buffer 数量大 2 倍的最小质数。在系统最繁忙时查看 X$BH 有多少行,就能知道可用的 Buffer 的数量。注意用 db_cache_size/db_block_size(Buffer Cache 大小除以每个 Buffer 大小)得到 的并不是“可用的”Buffer 的数量。不过我们通常不必调节此数字,在 Oracle 9i 后,Oracle 公司已经默认将此闩调整为此值。比如在我的机器上,CBC latch 数量是 7841,可用的 Buffer 数量是 3916,3916×2=7832,我没有算过,不过我想 7841 应该是大于 7832 的最小的质数 吧。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 142 5.6.3 Cache Buffer LRU chain latches 当访问 LRU 链表时,会需要此闩。当服务器进程读块进 Buffer 时,要将使用过的 Buffer 挂到 LRU 上,这时会先搜索 LRU 链表查找自由块,搜索时会一直持有此 Latch。说直接点, 就是物理读。多过的物理读是造成此闩的根本原因。从原理上讲,CBC latch 通常由过多的 逻辑读引起的。但逻辑读过多,有时也会相应的出现物理读多的现象。也就是说,有时逻辑 读多,也可能造成此闩的等待。 5.6.4 buffer busy waits 等待 1. Buffer busy waits 的等待情况之一: 当在 Cache buffer chain 中搜索到要查找的 Buffer 后,要在其上以共享或独占方式 PIN 住该 Buffer。在试图 PIN Buffer 其间,如果发生了不相容的 PIN,就会有 Buffer busy waits 等待。如下例: 例 5: 步 1:确定 AA 中哪些记录在一个块中 select dbms_rowid.rowid_block_number(rowid),zw,id from aa; DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) ZW ID ------------------------------------ ---------- ---------------------------------------------- ---------- ---------- 89391 1 1 89391 2 2 89391 3 3 89391 4 4 89391 5 5 如上,ID 从 1 到 5 的行都在一个块中。 步 2:分别在两个会话中执行如下两段程序块: declare begin for i in 1..100000 loop update aa set zw=1 where ID=1; end loop; end; / declare cursor aa1 is select zw from aa where id=2; mx number(5); begin for i in 1..100000 loop open aa1; fetch aa1 into mx; -- dbms_output.put_line('查询结果:'||mx); close aa1; end loop; ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 143 end; / 步 3:不停的查看等待事件: select * from v$session_wait where sid>=8 and event not like 'SQL*%'; 可以看到在块 89391 上发生了 buffer busy waits 争用。同时,也出现的 Latch 编号为 97 的 Latch free 等待事件。这是因为对同一块的大量操作,必然会出现此闩的争用。(例 5 完) 造成此种 Buffer busy waits 等待事件的原因,主要是不相容的 Buffer pin。如果两块 SQL*Plus 程序块都是读取,则不会出现 Buffer busy waits 争用。除此一种 Buffer busy waits 外,还有一种纯粹因为读而造成的 Buffer busy waits 等待。 2. Buffer busy waits 等待情件之二: 通常情况下,读和读之间是不会发生 Buffer pin 冲突的。但这只限于逻辑读之间。而物 理读是又不一样了。如果两个进程同时请求读一个 Buffer cache 中并不存在的块,换句话说, 就是两个进程同时以共享方式 Pin 一个缓存中并不存在的 Buffer(Pin 一个并不存在的内存 块,将会引起此块的装载)。虽然此时两个进程都是请求的共享 PIN,Oracle 只会让其中一 个进程获得共享 PIN,并装载块进入 Buffer cache。装载块完毕后,另一进程才能获得共享 PIN 开始读 Buffer。 我们可以用如下例子观察此事件: 步 1:将表 T4 状态改为 Cache,并选择它一次,让 T4 的全部内容驻留在 Buffer cache 中。 alter table t4 cache select * from t4; 步 2:打开多个会话,同时执行如下语句:select * from t4; 步 3:查看 V$session_event,会发现找不到 buffer busy waits 等待事件。这是因为我们将 T4 驻留在了 Buffer cache 中,所有的 T4 的读都是逻辑读,因此再多的进程同时读 T4,也不会 有 buffer busy waits 等待。将 T4 的状态改为不缓存:alter table t4 nocache 。 然后可以重启 一下 Oracle 或者通过运行大操作将已经在 Buffer cache 中的 T4 的 Buffer 挤出去。再运行步 2 的测试,可以看到有许多的 Buffer busy waits 等待。 3. 针对 Buffer busy wait 的视图:V$WAITSTAT 此视图非常简单,显示结果如下: scott@MYTWO> select * from v$waitstat; CLASS COUNT TIME ------------------ ---------- ---------- data block 2 0 segment header 2 1 -----> 段头上发生了一次 Buffer busy wait 等待 : : : : undo header 0 0 undo block 0 0 已选择 18 行。 省略了一些我们还没有讲述相关知识的行。从显示结果可以看到,在段头有一次 Buffer busy wait 等待。 此视图只能给你提供一个大概的 buffer busy waits 的分布。如果要想具体的诊断该等待 事件,只能当发生该等待时,到 v$session_wait 里去找原因,从而才能找到解决的办法。在 V$SESSION_WAIT 中,P1 是引起 Buffer busy wait 的块所在的文件编号,P2 是块编号,P3 有时候代表着原因。p3 为 130 意味着同时有很多 session 在访问同一个 data block,而且该 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 144 data block 没有在内存里,而必须从磁盘上获取。如果 p3 为 220 意味着有多个 session 同时 修改在一个 block(该 block 已经被读入内存了)里的不同的行。这种情况通常出现在高 DML 并发性的环境里。但通常情况下 P3 的值参考意义不大。因为根据 P1 的文件号、P2 的块编 号,我们已经可以知道发生争用的块是属于谁的,类型是什么,不必根据 P3 来看。据体根 据 P1、P2 的值找出争用块所属对象,我们到讲完“数据文件与表空间”部分后再详述。 目前,我们只需了解基本原理与概念,掌握住大的方向。而具体的操作,到所有原理理 解后,是很简单的。 5.6.5 Free Buffer waits 等待事件 此事件也发生在物理读期间,在块被读进 Buffer 前,先需要在 LRU 链中,为其找到空 间。因搜索可用空间而造成的等待,就是 Free buffer waits 等待。当前台进程在 LRU 链上搜 索可用空间时,如果搜索到了一定限制的块数(由隐藏参数_db_block_max_scan_pct 控制, 9i 一般是 40%),仍然没有找到可用空间,就会触发 DBWr 写脏块。已经在 LRU 链上找了 40%,仍然没有自由块,这说明脏块已经很多了。而且越靠近 LRU 的另一端(热端),找到 自由块的可能越小,不如停止搜索让 DBWr 刷新脏块。在 DBWr 刷新脏块时,前台进程就 需要等待,这就是 Free buffer waits 等待事件。 oracle 跟踪每次对于可用的内存数据块的请求次数(记录在 v$sysstat 里的 free buffer requested),也跟踪每次请求可用的内存数据块失败的次数(记录在 v$system_event 里的 free buffer waits 的 total_waits)。而 v$sysstat 里的 free buffer inspected 则说明 oracle 为了找到可用 的内存数据块所跳过的数据块的个数,如果 buffer cache 很空,有很多空的数据块的话,则 该值为 0。如果 free buffer inspected 相对 free buffer requested 来说很高,则说明 oracle 进程 需要扫描更多的 LRU 链表上的数据块才可以找到可用的数据块。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 145 附录: 1.show_buffer.sql 脚本: select ba,prv_hash,nxt_hash,prv_repl,nxt_repl,decode(bitand(flag,1), 0, 'N', 'Y') dirty, decode(bitand(flag,16384), 0, 'N', 'Y') stale, LRU_FLAG,tch from x$bh where obj=T4_1 表的 OID; T4_1 表的 OID(对象 ID)可以 V$dba_objects 视图查到,语句如下: select data_object_id from dba_objects where object_name='T4_1'; 2.re_dbwr.sql 和 show_dbwr.sql 脚本: 在使用这两个脚本前先要建立 SHOW_DBWR_TEMP 表: create table SHOW_DBWR_TEMP as select statistic#,name,value from v$sysstat where 0=1; re_dbwr.sql 脚本如下: truncate table show_dbwr_temp; insert into show_dbwr_temp select statistic#,name,value from v$sysstat where (statistic# in (62,67,69,80,93,94,96,97,108,109,110,164,165) or (statistic#>=77 and statistic#<=79)) ; commit; 注意,在9i中把插入语句换为: select statistic#,name,value from v$sysstat where (statistic# in (46,47,49,60,75,76,78,79,90,91,92,152,153) or (statistic#>=53 and statistic#<=58)) ; show_dbwr.sql 脚本如下: select a.statistic#,a.name,a.value,a.value-b.value from v$sysstat a, show_dbwr_temp b where a.statistic#=b.statistic# and (a.statistic# in (62,67,69,80,93,94,96,97,108,109,110,164,165) or (a.statistic#>=77 and a.statistic#<=79)); 注意,在9i中把选择语句换为: select statistic#,name,value from v$sysstat where (statistic# in (46,47,49,60,75,76,78,79,90,91,92,152,153) or (statistic#>=53 and statistic#<=58)) ; 3.show_cr.sql 脚本如下: col object_name for a30 select b.object_name, decode(a.state,0,'free',1,'xcur',2,'scur',3,'cr', 4,'read',5,'mrec',6,'irec',7,'write',8,'pi', 9,'memory',10,'mwrite',11,'donated') STATE, dbarfil,dbablk,ba from x$bh a, dba_objects b where b.data_object_id=a.obj and b.object_name='&objectname'; ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 146 第 6 章 Redo Buffer 与 Java 池 您将学习: 1. 重做缓存 2. Java 池 第 六 章 Redo Buffer 与 Java 池 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 147 6.1 Redo Buffer 6.1.1 重做缓存的作用 重做缓存又被称为重做池,redo log buffer,或 redo buffer、log buffer。进程把每一条 DML 或 DDL 语句的重做条目从用户的内存空间(PGA)中拷贝到重做缓存中。重做条目包 含 DML 和 DDL 的重构或重做信息。它被用于恢复,它在重做缓存中连续式的占用空间。 重做缓存是循环使用的缓存,服务器进程可以拷贝新的条目覆盖哪些已经被写进磁盘中 的条目。LGWR 通常应该足够快的写,以保证新的条目可以覆盖老的条目。LGWR 进程将 重做缓存写到磁盘上活动联机重做日志文件(或一个活动日志组的所有成员)。LGWR 进程 拷贝自最后一次 LGWR 写盘以来,新被送进重做缓存的所有条目到磁盘。 6.1.2 重做缓存的作用 重做缓存的大小使用 LOG_BUFFER 设置,它通常不必太大。只要有几 M、十几 M 足以。 你可以通过显示此参数的值了解当前重做缓存的大小,如: SQL> show parameter log_buffer NAME TYPE VALUE ------------------------------------ ----------- ---------- log_buffer integer 7024640 使用 Show sga 语句,也可以显示出当前的重做缓存大小: SQL> show sga 第 一 节 Redo Buffer  重做缓存的作用  重做缓存的设置  重做缓存相关的资料  调优重做日志缓存 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 148 Total System Global Area 419430400 bytes Fixed Size 1249368 bytes Variable Size 130027432 bytes Database Buffers 281018368 bytes Redo Buffers 7135232 bytes “show sga”中显示的值与 log_buffer 初始化参数有一定的出入,这是因为 Oracle 会根 据 log_buffer 初始化参数的值,自动的在此参数附近进行调节。 如果事务长或者多,那么比较大的 Log_buffer 将减少日志文件的 I/O。重做缓存越小, 达到三分之一的次数越多,而每次重做缓存满三分之一时,都将触发 LGWR 写重做缓存到 日志文件。 频繁的 COMMIT 声明,将清空缓存,因而,如果提交频繁,应该使用更小的缓存。 在有着快速处理器、慢速磁盘的机器中,在 LGWR 拷贝一块日志缓存到磁盘的时间内, 处理器可能填满了其余的缓存。因此,更大一些的日志缓存,使新的条目和正在被写进磁盘 的条目冲突的机会更少。然而,如果生成重做的总数超过被写出的速率,那么无论多么大的 缓存,它终将被填满。在这些情况下,要么保证有足够的日志缓存空间,要么加快拷贝日志 缓存到磁盘的速度。 服务器进程可能在日志缓存中要求空间时,但却没有找到可用空间。服务器进程必须等 待 LGWR 刷新日志缓存到磁盘。 调优重做日志缓存意味着要确保服务器进程在重做缓存中要求的空间是充足的。太大的 日志缓存减少少了分配给其他区域的内存量。有一点非常重要,就是 DBA 可以使用一些方 法,减少必须产生的重做日志总量。这些方法将在后面的章节中介绍。 6.1.3 重做缓存的作用 1.LGWR 相关的日志资料 下面 3 个资料是与 LGWR 直接相关的。仅由 LGWR 进程更新。 (1).redo writes: LGWR 从日志缓存中刷新重做记录到重做日志文件中的次数。 (2).redo blocks written: 由 LGWR 写进重做日志文件的重做块的总量。 (3).redo write time: 以厘秒为单位,记录 LGWR 刷新 log buffer 到重做日志文件所用的总的时间。它是每个 log file parallel write 等待时间的累计。 SQL> select (select value from v$sysstat where name='redo blocks written')/ (select value from v$sysstat where name='redo writes') value from dual; VALUE ---------- 7.77459954 根据 1 和 2 两个资料,我们可以如上计算出平均每次 LGWR 被触发写的块数。 2.日志切换相关资料: (1).redo log space requests: 当前连机重做日志文件已满,切换日志时的等待次数。它是除用户手动切换日志外,所有 日志切换的次数。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 149 (2).redo log space time: 日志切换时的等待时间。 3.重做数量资料 (1).redo size: 所生成的总的重做数量,单位是字节。重做在 PGA 中生成后,先要计算出重做记录的大 小,然后根据此大小在 log buffer 中分配空间。在重做记录还没有被写日志缓存前,重做记录的 大小就会被记入到此资料中。 (2).redo entries: 被拷贝进日志缓存的重做条目数,此处的条目数指的就是重做记录数,而不是重做矢量的 数量; 以上两个资料都是在重做记录被拷贝到日志缓存前,计算的。两个相除可以得到每条重 做记录的平均字节数。命令如下: SQL> select (select value from v$sysstat where name='redo size')/ (select value from v$sysstat where name='redo entries') value from dual; VALUE ---------- 327。450713 4.redo buffer allocation retries 资料 在日志缓存中尝试分配空间不足的次数。retry 是再次尝试的意思。此资料表示,当第一 次分配空间而没有成功时,必须再次尝试在日志缓存中分配空间,这可能是由于 LGWR 速度 慢,或者是发生了日志切换等。和此资料相关的等待时间是 log buffer space。在这个等待事件 中可以查到等待时间。可以通过如下命令,计算出每条重做记录平均的等待次数。 SQL> select (select value from v$sysstat where name='redo buffer allocation retries')/ (select value from v$sysstat where name='redo writes') value from dual; VALUE ---------- 。011216798 此计算结果越接近 0 越好。 5.redo wastage 用户提交时,必须要刷新日志缓存到磁盘文件,即使重做块并没有被填满。此资料就是统 计重做块中没有被填满的字节数。比较准确的叫法应该是,"未使用缓存空间(unused buffer space)"。 6.redo writer latching time 主要指 LGWR 在刷新日志缓存时,等待 redo copy 闩的时间。单位是厘秒。 7.redo synch writes,redo synch time: 此资料专指因提交而必须刷新日志缓存到磁盘的次数和时间。redo synch writes 每提交 一次此资料加 1,而 redo synch time 是写重做记录到磁盘所消耗的时间。 6.1.4 重做缓存的作用 重做日志缓存的调优最重要的就是有关重做缓存的闩,下面我们详细介绍一下。 重做相关的闩主要有 redo copy、redo allocation、redo writeing 这三个。它们的意分别是, redo copy 闩,服务器进程争用此闩,可以通过减少重做的产生,减少提交次数,缓解此闩,但有 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 150 时提交次数和重做记录数是无法减少的。在你不能对应用以及现有硬件做任何修改的情况下, 可增大_log_simultaneous_copies,但要确保 log file sync 事件不会增加太多。redo allocation 闩, 同样,在你不能对应用以及现有硬件做任何修改的情况下,可以尝试调整日志缓存的大小或调 整_io_redo_size 参数的值,看是否能对此闩的争用有所缓解。redo writing 闩,当 redo allocation latch 没有太严重的争用时,此闩一般也不会有争用,此闩的争用意味着服务器进程填满可用 的空间的速度,远大于 LGWR 准备可用空间的速度。此时,如果提交的比率很高,增加日志缓 存的大小,如果提交比率并不高,可以减少_io_redo_size。 下面,我们详细介绍一下这三个闩。 1.相关闩的详细介绍 重做是先产生在各个会话的 PGA 中,再由各个会话的服务器进程,将重做记录拷贝到 SGA 中的 log buffer 中,再由 LGWR 进程刷新到 redo log 文件中。整个这个过程,涉及到 3 个 比较重要的闩。分别是 redo copy latch,redo allocation,和 redo writing。下面先介绍下这 3 个闩, 在重做产生的流程中,各自的作用。明白了这一点,对这 3 个闩的调优会有很大的帮助。 (1).redo copy latch redo copy latch 的数量可以有多个,可以通过_log_simultaneous_copies 参数来设定,缺省值 是两倍 CPU 的个数。当前台进程将自己 PGA 中的重做记录拷贝到日志缓存中时,此闩用于 保护日志缓存中的信息。LGWR 工作时并不需要再获得此闩。 注意。此闩主要的用途是从 PGA 拷贝重做记录到日志缓存,LGWR 的工作是将日志缓存 写到磁盘。因此,LGWR 不需要此闩,但是,oracle 也不会允许某前台进程一边对重做记录 A 进 行修改,LGWR 一边将重做记录 A 写进磁盘。因此 LGWR 虽然不需要获得 redo copy latch, 但是在他开始工作时,必须等待正持有此闩的前台进程,将要刷新的重做记录拷贝完毕,redo copy latch 是保护日志缓存的。LGWR 不持有此闩的目的就是在 LGWR 工作时,任一前台进 程仍可以向日志缓存中拷贝自己的重做日志。所有正在进行的工作都可以继续,而不必等待 LGWR 完成。 如果此闩的数量设置的多一些,这将有助于降低前台进程对此闩的争用。但这又势必增 加 LGWR 等待此闩释放的时间。资料视图(v$sysstat)中的 redo writer latching time 资料记录 的就是 LGWR 等待此闩释放的总的时间。 (2).redo allocation LGWR 和前台进程都需持有。当前台进程在日志缓存中分配空间时,必须先持有此闩, 当 LGWR 刷新缓存时,它持有此闩,以阻止前台进程在日志缓存中分配空间。但 LGWR 持有 此闩的时间是很短暂的。当它一旦确定要刷新的缓存范围后在开始真正的写磁盘操作前,它 就会释放此闩,以便让其他的前台进程可以继续向日志缓存中拷贝重做条目。 从以上两个闩可以看到,oracle 把向 log buffer 中写缓存这样一个操作。分做两个步骤。1 是先在 log buffer 中分配一块空间,2 是向这块空间中实际的写入重做信息。分成两步之后, 多个服务器进程,在 log buffer 中申请空间的操作,会互相阻塞。一旦空间分配成功,多个进程 可以同时向自己所属的那块空间写入信息。分配空间时,必须持有 redo allocation 闩,这个闩数 量只有一个。这就意味着多个进程分配空间的操作将会互相阻塞。向 log buffer 中写信息时, 必须持有 redo copy 闩,redo copy 闩的数量可以有多个。也就是说,在空间分配好后,向各自的 空间中的写操作可以同时进行。但是,计算机的总线宽度和 CPU 资源是有一定限制的。如果 同时向 log buffer 中写信息的进程太多。反而会使速度降低。因此 redo copy 闩虽然可以有 多个,但数量上很受限制。一般都是 CPU 的两倍。目的就是为了限制同时向 log buffer 中写 信息的进程数。如果 oracle 不把向 log buffer 中写信息的过程分为两步,那么在某一个进程, 在 log buffer 中分配空间并向空间写信息的时候其他所有进程都必须等待。分做两步后,在 log buffer 中分配空间这个步骤必须是串行的,而向各自已分配的空间写信息则可以并行。 oracle ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 151 会尽量让可以并行的操作并行来处理。以减少阻塞和等待。 (3).redo writing 当日志缓存中没有空间时,前台进程必须通知 LGWR 刷新日志缓存,只有第一个得到 此闩的进程可以通知 LGWR,它用来阻止其他进程通知 LGWR,前台进程取得此闩,通知 LGWR 刷新缓存,然后马上就释放此闩,它不会一直占有此闩,通常闩的持有都是很快的。 LGWR 接到前台进程的通知,同样,当它写日志缓存到文件前,它也得到此闩,这样,其他 需要刷新日志缓存的进程,就无法再次呼叫它,它可以专心完成刷新缓存的工作,当一个新的 前台进程得到此闩后,第一件事情就是检测 LGWR 是否已经准备好了可用的空间。如果是这 样,进程释放闩并不再通知 LGWR,这是很有可能的,因为,如果同时有十个进程争用此闩,第一 个获得此闩的进程需要 400 字节的缓存,它呼叫 LGWR 刷新日志缓存,LGWR 不可能只为此 前台进程只腾出 400 字节就完了,它每次都要把所有满的日志块和必须要同步的重做信息写 进磁盘,也就是说 LGWR 每刷新一次,很可能会腾出许多空间,因此,后得到此闩的进程就不必 忙着呼叫 LGWR,有可能他需要的缓存空间已经有了。 总结一下此闩的作用,当写日志缓存到文件时,只有 LGWR 能完成这个工作。既然只 能有 LGWR 自己可以写日志文件,那么日志文件不必再有闩来保护。但是同时可能会有多个 服务器进程(又称前台进程)呼叫 LGWR 写缓存,这就需要按次序进行,此时需要此闩来保护。 还有,在 LGWR 正在写缓存时,也不希望别的进程再来打扰,因此,当 LGWR 开始写缓存时,它 先获得此闩,这样,在他完成写操作前,其他想要呼叫它的进程只有等待。综上所述,此闩是在呼 叫 LGWR 写缓存时必须先获得的闩。 2.LGWR 的工作流程: 综上所述,我们先来了解一下 LGWR 的工作流程,在 LGWR 被触发时,它必须先获得 redo writing 闩,确保其他进程不会再通知它写缓存,然后 LGWR 获得 redo allocation 闩,以阻止前台 进程分配更多的重做空间,然后 LGWR 确定要写的 buffer 是哪些,这包括所有满的,或者必须 被同步的 buffer。此时,还要分配一个新的 SCN,这主要用于 RAC 环境,确保不会在同一 SCN 刷新缓存两次,这些完成后,redo allocation 被释放,根据情况,有时 LGWR 还要计算 Target RBA。 在这完成后,redo writing 闩也被释放,可以看到,redo writing 与 redo allocation 都是持有很短的 时间。在真正的写磁盘操作开始前就被释放了。因为其他进程中,重做的生成仍然要继续。 LGWR 在刷新缓存前,必须确保前台进程完成对要刷新缓存的修改。这就是它必须等待 redo copy 闩的释放,此时 LGWR 将暂停工作,等待一个唤醒通知。即相关的 redo copy 闩被释 放时,LGWR 会被唤醒继续工作,如果持有 redo copy 闩的前台进程,正在修改的缓存并不是 LGWR 要刷新的缓存,LGWR 并不需要等待此 redo copy 闩的完成。 在这些操作都完成后,LGWR 开始真正的写磁盘,在磁盘写时,它并不持有上述三个闩中 的任一个。 下面用一个图来说明一下: ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 152 上图中,都是假设每给进程的重做记录正好占满一个重做块,其实,重做记录有大有小,不 可能都那么凑巧。就象上面所讲的例子,在 11 号重做块中提交的进程。并没有将 11 号块占 满。假如说只使用了 200 个字节,LGWR 在刷新重做时,也必须将整个 11 号重做块刷新到重 做日志文件中。这可能会造成一些空间上的浪费,在资料视图 v$sysstat 中有一个 redo wastage 资料,是专门记录重做日志文件中,浪费空间的数量。 3.重做的产生流程 1) 先在 PGA 中生成重做记录,并计算出重做记录大小。 2) 由服务器进程申请 redo copy latch 如果成功的话, 3) 再去申请 redo allocation,成功分配空间后。 4) 释放 redo allocation。 5) 开始把 PGA 中的重做记录写往 log buffer。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 153 6) 记录写完后,释放 redo copy latch。 在第 1 步,计算出重做记录大小后,还将更新资料视图中的重做信息。redo size。我们可 以通过它来了解生成了多少重做。 如果在第 2 步时,分配不成功的话,会等待。 如果在第 3 步时,redo allocation 闩无法获得,那么会等待,第 2 步获得的闩不释放。像这 样进程已持有某个闩,但在等待另外一个闩的次数,可以在 v$latch 中的 watis_holding_latch 列 查看。如果获得了 reod allocation 闩,但是 log buffer 中没有空间,会去通知 LGWR 写日志。并 且把第 1,第 2 步所获得的闩都释放。当 LGWR 刷新过缓存。log buffer 中已经有了可用空间 后,进程将从头开始重新申请第 2 步的 redo copy latch。此时会增加资料视图中'redo buffer allocation retries'的值。 如果在上面步骤 3 时,log buffer 中空间不足,服务器进程把 redo allocation 和 redo copy latch 释放。然后服务器进程将申请 redo writing 闩,获得后,会再次检查一次 log buffer 中空间 是否足够。如果空间仍然不足,LGWR 就必须要去刷新缓存了,在 LGWR 刷新缓存前。会再 检查一次当前连机重做日志文件中是否有足够空间。如果有的话,服务器进程将释放 redo writing 闩。并通知 LGWR 开始刷新缓存。此时服务器进程将等待。相关等待信息会被记录 进 log buffer space 等待事件。 4.简述 LGWR 的触发 LGWR 的触发已经有很多资料讨论过了,我就不详细再讲了。下面我简单的列举下 LGWR 的触发条件: 1) lgo buffer 不足 服务器进程通知 LGWR 刷新缓存。 2) 事务提交时。 3) log buffer 满 1/3 时。 4) 每 3 秒一次。 5) 重做信息满 1M 时。 6) 日志切换时。 7) 在 DBWn 写之前。 因为 oracle 有一个规定:Write-Ahead Loggin(在 write 前 loggin) 它有两点要求: (1)在数据 缓存块被改变前,重做必须先被写进日志缓存。(2)在 DBWN 写缓存块到数据文件前,LGWR 必须写日志缓存到日志文件。因此,如果 DBWN 沿检查点队列写缓存块到磁盘时,此缓存块 对应的重做记录还没有被写进日志文件,DBWN 会暂停写此块。并通知 LGWR 刷新日志缓存。 额外说一点,此时 DBWN 并不会停止,他可以转去写其他检查点队列中的块(Buffer cache 中 的检查点队列可以有多条)。等等,总之 DBWN 在这种情况下不会等待。 注意 LGWR 的写是连续的,它必须按重做记录生成时的顺序。也就是按照 RBA 的大下, 顺序写。 小结:了解了重做产生的流程以及 redo copy。redo allocation 和 redo writing 这 3 个闩在 重做生成的流程中扮演了什么样的角色,当这 3 个闩出现争用时,会更容易的判断问题出现在 什么地方以及下一步该采取什么样的行动。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 154 6.2 Java 池 DBA 使用下列参数限制所有 Java 会话使用内存的总量:  java_soft_sessionspace_limit:当用户的运行期 Java 代码段所占内存超过这个大小时,将 在数据库跟踪文件中写一个警告。使用此参数在会话中为 Java 所能使用内存设置一个 软限制,作为一个对你的警告。  java_max_sessionspace_size:当用户的运行期 Java 代码段所占内存试图超过这个值时, 会话将会因为内存超出而被 Kill 掉。如果用户端调用的 Java 过程没有在它的内存使用 中自己限制内存大小,这个设置可以将作为会话空间总量上的一个硬限制。当此参数的 值被超出,将会报出下列错误信息: ORA_29554:unhandled Java out of memory condition 对于每一个加载的类,Java 引擎使用 8KB。当调用或解析类到数据库时,加载器也临 时使用共享池内存。当调用或解析特别大的 JAR 文件时,可以使用 50MB 的共享池内存。 在共享服务器中,如果 Large_pool_size 被配置,UGA 被分配的大池中,那么,这一部分 Java 占用的内存也将在大池中分配。 Java 池用 Java_pool_size 初始化参数设置,它是 SGA 中一个内存组件,它被用于所有 特定会话的 Java 代码和 EJE 中的数据。在实例启动期间,Java 池被分配,其大小将等于 Java_pool_size 的值。 第 二 节 Java 池 Java 池的配置,注意如下三个参数  java_soft_sessionspace_limit  java_max_sessionspace_size  Java_pool_size ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 155 第 7 章 SGA 管理 您将学习: SGA 的自动共享内存管理(即 ASMM)与非自动共享内存管理 的实施与管理。 第 七 章 SGA 和管理 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 156 7.1 SGA 管理 7.1.1 SGA_TARGET 与 SGA_MAX_SIZE Automatic Shared Memory Management (ASMM),也就是自动共享内存管理,是 10g 中 新增的特性,主要用来管理 SGA 中的内存组件,它针对如下内存组件发挥作用:Buffer cache 池、共享池、大池、Java 池和流池。相应的,下列参数我们可以不必再进行设置: DB_CACHE_SIZE, SHARED_POOL_SIZE, LARGE_POOL_SIZE, JAVA_POOL_SIZE, 和 STREAMS_POOL_SIZE (在 10.2 版本之上),Oracle 可以根据工作负载情况,自动的为这些 内存组件安排合适的内存大小。有一个专门的后台进程 MMAN,负责完成这项任务。它将 根据这些内存组件的大小和 Oracle 的负载情况,自动调整内存组件的大小,保证将内存分 配到最需要它们的地方。 为了启用 ASMM,必须将两个初始化参数的值设置为合适的值:  SGA_TARGET 设置为非 0 值。 此参数设置可用于 ASMM 内存的上限。比为,如果设置为 4G,MMAN 进程将在此范 围 内 调 节 DB_CACHE_SIZE, SHARED_POOL_SIZE, LARGE_POOL_SIZE, JAVA_POOL_SIZE, 和 STREAMS_POOL_SIZE 这些参数。保证这些内存组件的总大小不 会超过 4G,并且以最合理、最有效的方式使用这 4G 内存。假设你的数据库并不繁忙、负 载很低,4G 内存根本用不完,那么 MMAN 进程有可能不会为 ASMM 中的内存组件分配 4G 内存。根据你的数据库负载,假设现在 MMAN 只为 ASMM 分配总共 1.5G 的内存,等到你 的数据库开始繁忙了,1.5G 内存不够用了,只要主机上还有可用内存,MMAN 将会自动的 扩展 ASMM 的总内存大小,但是,ASMM 的总数不会超过 SGA_TARGET 参数规定的值。  STATISTICS_LEVEL=TYPICAL (或 ALL) 第 一 节 SGA 管理  SGA_TARGET 与 SGA_MAX_SIZE  ASMM 内存组件和非 ASMM 内存组件的改变  SGA_TARGET 是 ASMM 可以使用的最大内存  SGA_MAX_SIZE 是 SGA 的最大内存限制 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 157 STATISTICS_LEVEL 至少设置为 TYPICAL,此参数我们在前面讲过,它决定了资料收 集的级别,共有三级,也就有是三个取值:BASIC、TYPICAL 和 ALL。如果设为 BASIC, 绝大部分的资料将不再收集。设为 ALL 的话将收集所有资料。默认值为 TYPICAL,收集大 多数资料。MMAN 进程必须根据工作负载情况,调节各内存组件的大小。而工作负载情况 的数据,就来自于“资料”。因此,如果将 STATISTICS_LEVEL 设置为 BASIC,因为可用 的资料不足,ASMM 将不能正常工作。 以 下 相 关 SGA 内存组件的初始参数仍然需要手动设置: LOG_BUFFER, DB_KEEP_CACHE_SIZE, DB_RECYCLE_CACHE_SIZE, DB_nK_CACHE_SIZE。这些参数 不属于 ASMM 的管理范围。在 10gR1 中,STREAMS_POOL_SIZE 参数也不属于 ASMM, 必须手动调节。SGA 总的上限用 SGA_MAX_SIZE 初始化参数设置,Oracle 一开始也不会 分配 SGA_MAX_SIZE 那么大的内存,在需要的时候,可以手动或自动的调节。 SGA_MAX_SIZE 减去 SGA_TARGET 的值,就是手动 SGA 内存组件可以使用的总内存数 量。 下面来看一个例子: 步 1:查看与 ASMM 相关的初始化参数设置: SELECT name, value FROM v$parameter WHERE name in ('db_cache_size', 'large_pool_size', 'java_pool_size', 'shared_pool_size', 'streams_pool_size', 'sga_target', 'sga_max_size', 'statistics_level') NAME VALUE ------------------------------ ------------------ sga_max_size 314572800 sga_target 314572800 statistics_level TYPICAL shared_pool_size 0 large_pool_size 0 java_pool_size 0 streams_pool_size 0 db_cache_size 0 这几个参数还可以用 Show parameter 命令显示,我在这里用 V$parameter 视图是想让大 家复习一下此视图。Oracle 所有的参数值被记录在此视图中。Show parameter 其实也是从此 视图中查询的结果。 步 2:查询一个不属于 ASMM 范围的内存组件的设置: SQL> show parameter db_recycle_cache_size NAME VALUE ------------------------------------ ----------------- db_recycle_cache_size 0 这个是 Buffer cache 的循环池,它不属于 ASMM,它的大小 Oracle 不会自动为我们调 节的,DBA 必须自己手动的调节的。 步 3:观察结果 结合上面两步,观察下面这些内存组件的真正大小: SELECT name, bytes FROM v$sgainfo; NAME BYTES ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 158 -------------------------------- ---------------------------- Fixed SGA Size 1261516 Redo Buffers 7127040 Buffer Cache Size 188743680 * Shared Pool Size 109051904 * Large Pool Size 4194304 * Java Pool Size 4194304 * Streams Pool Size 0 Granule Size 4194304 Maximum SGA Size 314572800 ** Startup overhead in Shared Pool 37748736 Free SGA Memory Available 0 通过上面的观察,尽管 db_cache_size, shared_pool_size, large_pool_size 和 java_pool_size 这些初始化参数都没有被设置,但是相应的内存组件仍然被分配了内存,这 就是 ASMM。MMAN 进程自动的根据负载为这些内存组件分配了合适的内存。 streams_pool_size 虽然也是 ASMM 的一部分,但是因为并没有使用流,因此 MMAN 也就没 有为它分配内存。 SGA 可以使用的最大内存数量用 SGA_MAX_SIZE 参数进行设置,它的值是 300MB, 因此这里 Maximum SGA Size 是 314572800,正好是 sga_max_size 的值 300 * 1024 *1024。 步 4:改变 DB_RECYLE_CACHE_SIZE 参数的值后再观察结果 SQL> alter system set db_recycle_cache_size=12MB; 系统已更改 SQL> show parameter db_recycle_cache_size NAME VALUE ------------------------------------ ---------- db_recycle_cache_size 12M 在修改 db_recycle_cache_size 后,查看它的值。db_recycle_cache_size 参数是手动调节 的,不属于 ASMM 的管理范围。在前面的步骤中,它的值一直为 0。现在将它的值设为了 12MB,下面,再次查询 v$sga_dynamic_components 视图,观察各内存组件的变化: SQL> SELECT component, current_size FROM v$sga_dynamic_components; COMPONENT CURRENT_SIZE ---------------------------------- ------------------------ shared pool 109051904 large pool 4194304 java pool 4194304 streams pool 0 DEFAULT buffer cache 176160768 * KEEP buffer cache 0 RECYCLE buffer cache 12582912 * DEFAULT 2K buffer cache 0 DEFAULT 4K buffer cache 0 DEFAULT 8K buffer cache 0 DEFAULT 16K buffer cache 0 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 159 DEFAULT 32K buffer cache 0 ASM Buffer Cache 0 可以看到,Buffer cache 的大小从前面步骤中的 188743680,减少到 176160768,两值相 减,(188743680-176160768)/1024/1024,结果正好是 12MB。你手动的设置了非 ASMM 的参 数 db_recycle_cache_size.,由于总 SGA 大小是 ASMM 内存组件大小和非 ASMM 内存组件 大小之和,因此,你手动的增加了非 ASMM 的大小,那么 ASMM 的大小将自动被减少。 7.1.2 ASMM 内存组件和非 ASMM 内存组件的改变 在使用 ASMM 时,如果手动的改变了内存组件的大小,将有以下四种情况:  如果 ASMM 内存组件被增加的超过了它最小的值,相应的 SGA 内存组件被立即的增 加大小到你所设置的值,MMAN 将减少一个或多个其他的 ASMM 内存组件的大小,以 腾出所需要的内存。在这里,非 ASMM 的内存组件不受影响。  如果一个 ASMM 内存组件被减少的低于了它的最小值,你的这个改变操作暂时不会产 生任何效果,MMAN 进程将根据负载情况在需要时真正的去减少相应内存组件的内存 数量。如果负载一直很高,你的这个减少内存数量操作可能一直不被执行。减少 ASMM 内存组件的内存,相当于 DBA 告诉 Oracle:“我要减少这个内存组件的内存”,而 Oracle 仅仅回答:“哦,我知道了,我会在需要时为您真正的减少内存。”,Oracle 并不会马上 就去真正的减少内存。  如果非 ASMM 的内存组件被增加,改变将马上生效。内存将从 ASMM 的内存组件中 来,MMAN 将减少一个或多个其他的 ASMM 内存组件的大小,以腾出所需要的内存, 就像上面的例子中一样。你增加了 db_recycle_cache_size 的大小,这是个非 ASMM 的 内存组件,MMAN 减少了 Buffer cache 池的大小,为 db_recycle_cache_size 腾出了地方, Buffer cache 是 ASMM 的内存组件。  如果非 ASMM 的内存组件被减少大小,改变也将马上生效。内存将被添加到 ASMM 的 可用内存中,在需要时分配给 ASMM 的内存组件。 注意:下面的查询将帮助你监控 ASMM 内存组件和非 ASMM 内存组件的大小: set line 190 col component for a25 col status format a10 head "Status" col initial_size for 999,999,999,999 head "Initial" col parameter for a25 heading "Parameter" col final_size for 999,999,999,999 head "Final" col changed head "Changed At" col low format 999,999,999,999 head "Lowest" col high format 999,999,999,999 head "Highest" col lowMB format 999,999 head "MBytes" col highMB format 999,999 head "MBytes" SELECT component, parameter, initial_size, final_size, status, to_char(end_time ,'mm/dd/yyyy hh24:mi:ss') changed FROM v$sga_resize_ops ORDER BY component; SELECT component, min(final_size) low, (min(final_size/1024/1024)) lowMB, max(final_size) high, (max(final_size/1024/1024)) highMB FROM v$sga_resize_ops GROUP BY component ORDER BY component; ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 160 第一个查询的结果如下: COMPONENT Parameter Initial -------------------------------- ------------------------- ---------------- - ASM Buffer Cache db_cache_size 0 DEFAULT 16K buffer cache db_16k_cache_size 0 DEFAULT 2K buffer cache db_2k_cache_size 0 DEFAULT 32K buffer cache db_32k_cache_size 0 DEFAULT 4K buffer cache db_4k_cache_size 0 DEFAULT 8K buffer cache db_8k_cache_size 0 („„„„ „„„„) 我只列出了一部分列和一部分行。 第二个查询的结果如下: COMPONENT Lowest MBytes Highest MBytes --------------------------------------- ---------------- -------- ---------------- -------- ASM Buffer Cache 0 0 0 0 DEFAULT 16K buffer cache 0 0 0 0 DEFAULT 2K buffer cache 0 0 0 0 DEFAULT 32K buffer cache 0 0 0 0 DEFAULT 4K buffer cache 0 0 0 0 DEFAULT 8K buffer cache 0 0 0 0 DEFAULT buffer cache 276,824,064 264 281,018,368 268 KEEP buffer cache 0 0 0 0 RECYCLE buffer cache 0 0 0 0 java pool 4,194,304 4 4,194,304 4 large pool 8,388,608 8 8,388,608 8 COMPONENT Lowest MBytes Highest MBytes --------------------------------------- ---------------- -------- ---------------- -------- shared pool 117,440,512 112 121,634,816 116 streams pool 0 0 0 0 已选择 13 行。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 161 第 8 章 PGA 与排序 您将学习 1. UGA(The User Global Area):用户全局区 2. CGA (The Call Global Area):调用全局区(会话区) 3. PGA(Program Global Area):程序全局区 4. PGA 的资料 5. PGA 的管理 6. 工作区与工作方式 7. 超过 PGA 目标值的情况 8. PGA 内存的回收 9. 相关视图 10. 手动 PGA 管理 11. 再论游标 12. 堆栈区管理 第 八 章 PGA 与排序 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 162 8.1 UGA(The User Global Area):用户全局区 用户全局区,The User Global Area,它包含和一个用户会话相关的信息。具体有: · 打开游标的永久区和运行区(即私有 SQL 区) · 包的状态信息,特别是包的变量; · Java 会话的信息; · 激活的角色; · 激活的跟踪事件(ALTER SESSION SET EVENT „); · 起作用的 NLS 参数(SELECT * FROM NLS_SESSION_PARAMETERS;); · 所有打开的 db link; · 会话对于信任的 Oracle 的托管访问标记(mandatory access control (MAC) 我们可以把这些信息被分为两类,一类是用户会话数据,另一类是游标。其中,又以游 标最为重要,特别是游标中的运行区,Oracle 用来完成排序、HASH 操作的工作区就在它其 中,它也是占用 UGA 空间最多的部分。 回忆一下游标相关的信息都有哪些?共享 SQL 区在共享池中,与它对应的私有 SQL 区 就是 UGA 中最重要的组成部分,游标的定义就是私有 SQL 区的句柄(此定义来自 Oracle 官方文档),包括绑定变量、结果集指针、还有排序区(又称工作区)等。 第 一 节 UGA(The User Global Area): 用户全局区 UGA:全称 User Global Area  UGA 的内容  UGA 的作用 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 163 8.2 CGA (The Call Global Area):调用全局区(会话区) CGA,即 The Call Global Area,调用全局区,顾名思意,就是在某段代吗被调用期间需 要使用的区域。它是一块动态的内存区域,随着调用(call)的开始而创建,在调用过程中 一直存在,直到调用结束时被释放。UGA 中的信息是到会话关闭时才释放。 它存放的是在调用过程中所需要的数据。或者说,CGA 的存在是瞬间的。它只存在于 一个调用过程中。比如用户调用了一个过程,就需要为此过程的运行,在 CGA 中分配一块 空间,保存它的局部变量和其他一些本地信息。过程运行结束,局部变量等这些本地信息就 要被释放掉。而 PL/SQL 中的全局变量,不会随某一过程的终止而被释放,只有当会话终止 时他们才被释放,因此这些全局变量,被放置在 UGA 中。其实 PL/SQL 中没有独立的全局 变量,全局变量就是包变量。 CGA 还包含一些低层次调用的中间结果,如解析(parse)调用、执行(executive)调 用、抓取(fetch)调用以及递归 SQL 调用和 PL/SQL 调用。就像抓取,服务器进程从 Buffer cache 的块中把行抓取出来,在最终交给用户前,就暂时放置在 CGA 中(我称这一块空间 为:组读取行缓存)。当然,调用并不是只通过 CGA 中的数据结构来工作。实际上,调用 所需要的大部分的重要数据结构都来自于 UGA。比如 SQL AREA,和 SORT AREA 基本 都是放在 UGA 中的,因为这些数据在各个调用之间必须一直存在并可用。而在 CGA 中只 存放了在调用过程中临时需要的数据,比如直接 I/O 缓存(Direct I/O Buffer)、组读取行缓 存、关于递归调用的信息、用于表达式评估(产生查询计划时用)和存放局部变量的堆 空间和其他一些临时数据。因此,没有 CGA 中的数据结构,调用是无法完成的。 总的来说,UGA 是针对会话的,存储会话相关数据,直到会话断开联接,UGA 所占空 间被释放。而 CGA 是针对某个运行中的进程的,存放进程相关信息。 第 二 节 CGA (The Call Global Area): 调用全局区(会话区) 注意事项:  CGA 的存在是瞬间的  包含一些低层次调用的中间结果  针对某个运行中的进程 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 164 8.3 PGA(Program Global Area):程序全局区 在介绍 PGA 前,先需要说明一点,Oracle 有两种共作方式:专用服务器、共享服务器。 在专用服务器方式中,UGA 和 CGA 都在 PGA 中分配空间,这时,PGA 就是由 UGA 和 CGA 构成的。在共享服务器方式下,UGA 会在大池中分配空间(如果没有配置大池就在共享池 中分配),此情况下,PGA 其实就是 CGA。注意,CGA 不象 UGA 可以位于 SGA 中(以共 享服务器模式连接),CGA 一定是位于 PGA 中的。如果当前进程正在运行,则每个 PGA 中 只有一个 CGA。如果当前进程没有运行,则该进程的 PGA 中就没有 CGA。 我们再强调一点,虽然相关 SQL 的信息都在 UGA 中,但相关 PL/SQL 过程、函数的信 息并不在 UGA 中。关于存储过程,它的代码被放在共享池中 ,只有存储过程中的变量, 才存放在 PGA 中。这部分用来存放用户进程信息的空间叫做堆栈空间(堆栈空间是 CGA 中的重要部分)。可以在操作系统教材中找到堆栈的作用。此处的堆栈空间和操作系统中堆 栈的作用基本是一样。参考下图: 如果在专用服务器下: SGA PGA 共享池、Buffer cache 等内存结构 堆栈空间 用户会话数据 游标状态 CGA UGA 共享服务器下的 PGA SGA PGA 用户会话数 据 游标状态 共享池中的其他内存结构 堆栈空间 UGA CGA 第 三 节 PGA(Program Global Area): 程序全局区 PGA,和 SGA 对应,是 Oracle 两大内存区之一  PGA 与专用服务器、共享服务器  PGA 各部分信息的位置 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 165 8.4 PGA 的资料 通过资料视图查看 PGA: sys@MYTWO> @show_pga 输入 user 的值: 12 SUBSTR(NAME,1,30) SID VALUE ------------------------------------------------------------ ---- ---------- session uga memory 12 142424 session uga memory max 12 207888 session pga memory 12 414364 session pga memory max 12 544900 通过 v$process 查看 PGA: sys@MYTWO> @pga 输入 sid 的值: 12 PGA Used PGA Alloc PGA Max ---------- ---------- ---------- 238080 435392 435392 第 四 节 PGA 的资料  session uga memory  session uga memory max  session pga memory  session pga memory max  PGA Used  PGA Alloc  PGA Max ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 166 8.5 PGA 的管理 PGA 有两种管理模式,手动管理与自动管理,自动管理是以后的大式所趋,我们先讲 自动管理,在本部分的末尾,再介绍手动管理。自动管理有个重要参数: · pga_aggregate_target : PGA 大小 · workarea_size_policy : PGA 的管理方式,如果为 AUTO 就是自动管理。如果 MANUAL 则为手动管理。 参数 pga_aggregate_target 是所有会话所能使用的 PGA 的总和。每一个会话使用的 PGA 一般按如下方式计算: 1). 在串行方式下:每会话 PGA 不超过 MIN(pga_aggregate_target*0.05,_pga_max_size*0.5) 即,通常是 Pga_aggregate_target 的 5%,但上限是_Pga_max_size 的 50% 。 实际上,每次有排序等操作时,Oracle 决不会现场再去按公式计算,工作区大小应该是 一个已经算好的值,需要时直接取来用。这个算好的值就是 _smm_max_size 。一般来说, _smm_max_size 的值会在你设定 pga_aggregate_target 值时自动计算。计算公式就是上述公 式。每次你改变 pga_aggregate_target 值时 _smm_max_size 会自动随之改变。但你改变 _pga_max_size 时,_smm_max_size 不会自动改变。 另外,你还可以手动自己设定 _smm_max_size 的值。如果你将 _smm_max_size 定为 了 100M,那怕 pga_aggregate_target 只有 150M,工作区大小也会是 _smm_max_size ,而 不是 pga_aggregate_target 的 5% 。 这 样 一 来 , 你 的 PGA 所 占 内 存 将 远 远 超 过 pga_aggregate_target 的值。不过这没什么,马上我们就要讲到,PGA 的自动管理方式下, 你所设置的 pga_aggregate_target,它本来就是一个建议或目标值,并不是强制的限制。 下面我们用一个练习来验证一下: 练习 1:_SMM_MAX_SIZE 值的变化 步 1:设置 PGA 目标大小为 100M 第 五 节 PGA 的管理 PGA 的管理  手动模式  自动模式 设置 PGA 的参数  pga_aggregate_target  workarea_size_policy ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 167 sys@MYTWO> alter system set pga_aggregate_target=100M; 系统已更改。 步 2:验证结果: sys@MYTWO> @show_para 输入 par 的值: smm_max NAME VALUE ISDEFAULT ISMOD ISADJ ---------------------------------------- ---------------------------- --------- ---------- ----- _smm_max_size 5120 TRUE FALSE FALSE sys@MYTWO> select &aaa from dual; 输入 aaa 的值: 100*1024*0.05 100*1024*0.05 ------------- 5120 100M 的 5%就是_smm_max_size 的大小 5120K。这里需要注意_smm_max_size 的大小 是按 K 来计算的。 (练习 1 完) 练习 2:正常情况下每会话 PGA 的大小 步 1:设置 PGA 的大小: sys@MYTWO> alter system set pga_aggregate_target=20M; 系统已更改。 sys@MYTWO> @show_para 输入 par 的值: smm_max NAME VALUE ISDEFAULT ISMOD ISADJ --------------------------------------- ---------------------------- --------- ---------- ----- _smm_max_size 1024 TRUE FALSE FALSE 此处会话可用 PGA 大小为 1024K,就是 20M 的 5%,不再验证。 步 2:查看会话 10 PGA 所占内存: sys@MYTWO> @show_pga 输入 user 的值: 10 SUBSTR(NAME,1,30) SID VALUE ------------------------------------------------------------ ---------- ---------- session uga memory 10 76960 session uga memory max 10 76960 session pga memory 10 220460 session pga memory max 10 220460 步 3:在会话 10 中执行一个排序操作: scott@MYTWO> set timing on scott@MYTWO> set autot trace scott@MYTWO> select * from t1 order by owner; 已选择 240000 行。 已用时间: 00: 00: 33.00 Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=13361 Card=240000 Bytes=19920000) ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 168 1 0 SORT (ORDER BY) (Cost=13361 Card=240000 Bytes=19920000) 2 1 TABLE ACCESS (FULL) OF 'T1' (Cost=1174 Card=240000 Bytes=19920000) Statistics ---------------------------------------------------------- 0 recursive calls 49 db block gets 6292 consistent gets 17128 physical reads 0 redo size 16857075 bytes sent via SQL*Net to client 176492 bytes received via SQL*Net from client 16001 SQL*Net roundtrips to/from client 0 sorts (memory) 1 sorts (disk) 240000 rows processed 可以看到由于 PGA 小,有磁盘排序的发生。 步 4:查看会话 10 所占 PGA: sys@MYTWO> @show_pga 输入 user 的值: 10 SUBSTR(NAME,1,30) SID VALUE ------------------------------------------------------------ ---------- ---------- session uga memory 10 76960 session uga memory max 10 1189848 session pga memory 10 361188 session pga memory max 10 1671372 可以看到,PGA 最多只占到 1671372 字节,合计 1632K。比_smm_max_size 的 1024 K 多一些。如果 PGA 越大,此处的误差越小。但一般不会完全相同。在服务器并不繁忙的情 况下,一般 Oracle 总会多占一点。 通过上面的例子,我们可以看到,我们将 PGA 大小(pga_aggregate_target)设为 20M, 但会话实际所使用的 PGA 也就 1M 多一点。(练习 2 完) 2). 在并行方式下:Pga_aggregate_target*30%除以并行度。 在并行方式下,PGA 的大小将不再按_smm_max_size 的值计算。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 169 8.6 工作区与工作方式 排序、HASH 联连接等是最耗 PGA 空间的操作。Oracle 用来完成排序、HASH 的空间 叫工作区。 但如果排序、HASH 所需空间过于巨大,工作区的空间终是有限的,就需要使用磁盘上 的临时表空间保存中间结果。这将对性能造成极大的影响。一般来说,工作区操作有如下工 作方式: · Optimal :所有操作都在内存中完成 · Onepass :大部分操作都在内存中完成,但需要少数磁盘空间 · Multipass : 需要多次使用磁盘空间以完成操作 对于某一工作区操作,以 Optimal 方式完成它所需空间称为 Optimal size,相应的还有 Onepass size 和 Multipass size 。对于 Order by 之类的排序操作,对于分配的内存在 optimal size 尺寸和 onepass size 尺寸之间时,排序操作不会随着内存的增加而更快完成,除非能够 为排序操作分配 optimal 尺寸。因此,如果不足以为排序分配 Optimal size 大小的内存,Oracle 趋向于分配 Onepass size 大小的内存。 有关工作区内存分配与排序操作的工作方式,在资料视图中有专门的行,如下: 22:42:20 sys@MYTWO> select * from v$sysstat where name like 'workarea%'; 或如下查看某一会话的工作区使用: SQL> select a.statistic#,sid,name,value from v$sesstat a,v$statname b where a.statistic#=b.statistic# and a.sid=&sid and b.name like 'workarea%'; 显示上面练习 2 中的统计资料: STATISTIC# SID NAME VALUE ---------- ---- ---------------------------------------- ---------- 第 六 节 工作区与工作方式  工作区是排序、HASH 连接、位图操作所使用的内 存区。  工作区的工作方式 - Optimal - Onepass - Multipass ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 170 226 10 workarea memory allocated 0 227 10 workarea executions - optimal 20 228 10 workarea executions - onepass 0 229 10 workarea executions - multipass 2 可以看到有两次 Multipass 排序。它的执行所用时间为 33 秒.。让我们增大内存,让排 序运行在 Onepass 下,比较一下运行时间上的差异。 练习 3:将 PGA 目标值定为 200M,每会话可用 PGA 将为 10M(刚才是 1M)。 步 1:设置 PGA 目标值: sys@MYTWO> alter system set pga_aggregate_target=200M; 系统已更改。 sys@MYTWO> @show_para 输入 par 的值: smm_max NAME VALUE ISDEFAULT ISMOD ISADJ ---------------------------------------- ---------------------------- --------- ---------- ----- _smm_max_size 10240 TRUE FALSE FALSE 步 2:重新登录会话 10,运行查询: scott@MYTWO> set timing on scott@MYTWO> set autot trace scott@MYTWO> select * from t1 order by owner; 已选择 240000 行。 已用时间: 00: 00: 23.09 Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=9431 Card=240000 Bytes=19920000) 1 0 SORT (ORDER BY) (Cost=9431 Card=240000 Bytes=19920000) 2 1 TABLE ACCESS (FULL) OF 'T1' (Cost=1174 Card=240000 Bytes=19920000) Statistics ---------------------------------------------------------- 0 recursive calls 9 db block gets 6292 consistent gets 10924 physical reads 0 redo size 16830733 bytes sent via SQL*Net to client 176492 bytes received via SQL*Net from client 16001 SQL*Net roundtrips to/from client 0 sorts (memory) 1 sorts (disk) 240000 rows processed 仍然有一次磁盘排序。检查资料: 11:45:57 sys@MYTWO> / STATISTIC# SID NAME VALUE ---------- ---- ---------------------------------------- ---------- 226 10 workarea memory allocated 0 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 171 227 10 workarea executions - optimal 20 228 10 workarea executions - onepass 2 229 10 workarea executions - multipass 0 可以看到已经没有了 Multipass,因此运行时间上有所减少,是 23.09,比刚才少了近 10 秒。经测试,我们可以再次把内存加大,到 300M,400M,500M,但都会有 Onepass ,完 成时间也一直在 23 秒左右。用上面的脚本观察内存的占用,一直也都是 2M 多一些的内存, 并没有随 PGA 目标值的加大而增长。也就是既然无法分得 Optimal size 大小的内存,对排 序操作,就分配 Onepass size 大小的内存就行了。下面我们将 PGA 目标值加大到 1G。 练习 4:进一步加大 PGA: 步 1: 加大 PGA 目标值到 1G alter system set pga_aggregate_target=1000M; sys@MYTWO> @show_para 输入 par 的值: smm_max NAME VALUE ISDEFAULT ISMOD ISADJ ---------------------------------------- ---------------------------- --------- ---------- ----- _smm_max_size 51200 TRUE FALSE FALSE 此时,会话所占 PGA 为 50M。 将 PGA 目标值加大到 1G 并不是立即分配 50M 给会话,而是会话最多可以用到 50M。 步 2:重启会话 10 并排序: scott@MYTWO> set timing on scott@MYTWO> set autot trace scott@MYTWO> select * from t1 order by owner; 已选择 240000 行。 已用时间: 00: 00: 20.04 Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=9431 Card=240000 Bytes=19920000) 1 0 SORT (ORDER BY) (Cost=9431 Card=240000 Bytes=19920000) 2 1 TABLE ACCESS (FULL) OF 'T1' (Cost=1174 Card=240000 Bytes=19920000) Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 6292 consistent gets 4144 physical reads 0 redo size 16823207 bytes sent via SQL*Net to client 176492 bytes received via SQL*Net from client 16001 SQL*Net roundtrips to/from client 1 sorts (memory) 0 sorts (disk) 240000 rows processed 已经没有磁盘排序了,查看资料: 12:06:15 sys@MYTWO> / ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 172 STATISTIC# SID NAME VALUE ---------- ---- ---------------------------------------- ---------- 226 10 workarea memory allocated 0 227 10 workarea executions - optimal 26 228 10 workarea executions - onepass 0 229 10 workarea executions - multipass 0 全部排序都是 Optimal。时间只比刚才 Onepass 时的 23.09 少了 3.05。查看内存的占用: 17:14:04 sys@MYTWO> @show_pga 输入 user 的值: 10 原值 2: where a.statistic#=b.statistic# and b.sid=&user and a.name like '%ga memory%' 新值 2: where a.statistic#=b.statistic# and b.sid=12 and a.name like '%ga memory%' SUBSTR(NAME,1, SID VALUE SIZE_MB ------------------------------------------------------------ ---------- ---------- ---------- session uga memory 10 32939888 31.4139252 session uga memory max 10 32939888 31.4139252 session pga memory 10 33326332 31.7824669 session pga memory max 10 33326332 31.7824669 31M 多一些,Oracle 终于可以取得 Optimal size 大小的内存了。(练习 4 完) 补充练习,我们可以试验一下 HASH 联连,即使 PGA 目标值的大小不足以为它分配 Optimal size 大小的内存,它完成的速度也会随 PGA 目标值的大小而提升。 一般情况下,工作区调优的目标将是: · workarea executions – optimal >=90% · workarea executions – multipass = 0 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 173 8.7 超过 PGA 目标值的情况 pga_aggregate_target 只是一个目标值,Oracle 会尽量把全部用户所用的 PGA 总和控制 在 pga_aggregate_target 左右。在两种情况下 PGA 的值将会被超过,一是过于繁忙,二是不 可控部分过大。下面分别讲述: 1. 过于繁忙:我们上面讲到过的跟据 pga_aggregate_target 计算的每会话所占 PGA 只是在 系统不繁忙的情况下。 如果系统比较繁忙,分给各个会话工作区的 PGA 会减少。Oracle 会尽量保证 PGA 的总 和不超过 pga_aggregate_target。但如果系统过于繁忙,会话过多,PGA 总和终将超过 Pga_aggregate_target 的限制。下面我们来测试一下。 练习 5:会话增多时,每个会话的 PGA 开始减少 步 1:设置 PGA 总和为 80M,这样,每会话所占 PGA 为 5M。 alter system set pga_aggregate_target=80M; 步 2:在建立一个脚本,名为 Test_hash.sql 内容改为: select sid from v$mystat where rownum=1; declare l_msg long; l_status number; begin dbms_alert.register('WAITING'); for i in 1..9999999 loop dbms_application_info.set_client_info(i); dbms_alert.waitone('WAITING',l_msg,l_status,0); exit when l_status=0; for x in (select a.owner from t1 a,big_table b where a.id=b.id) loop 第 七 节 超过 PGA 目标值的情况  PGA 目标值:pga_aggregate_target  下列情况下,用户所占用内存可以超过它: - 过于繁忙 - 不可控部分过大 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 174 null; end loop; end loop; end; / 无论在哪个会话中,使用如下脚本将为使上述过程中止: -------------stop.sql----------- begin dbms_alert.signal('WAITING',''); commit; end; / 上述测试需要注意的是,我们不使用 Order by 做为测试用的命令,而是 Hash 联接,因 为 Order by 有时可能会使用 Onepass size 大小的内存。而 Hash 不会,能为它分配多少它就 会使用多少。 步 3:多次执行上面的脚本 test_hash.sql,每次执行后观察它所占的 PGA: 刚开始时: sys@MYTWO> @show_pga 输入 user 的值: 32 SUBSTR(NAME,1,30) SID VALUE SIZE_MB ------------------------------------------------------------ ---------- ---------- ---------- session uga memory 32 5116164 4.87915421 session uga memory max 32 5116164 4.87915421 session pga memory 32 5406852 5.15637589 session pga memory max 32 5406852 5.15637589 步 4:到第 5 个会话后,PGA 分配的开始减慢: sys@MYTWO> @show_pga 输入 user 的值: 22 SUBSTR(NAME,1,30) SID VALUE SIZE_MB ------------------------------------------------------------ ---------- ---------- ---------- session uga memory 22 1255312 1.19715881 session uga memory max 22 1255312 1.19715881 session pga memory 22 1470596 1.40246964 session pga memory max 22 1470596 1.40246964 不过由于内存请求的巨烈,会话 22 所占 PGA 最终会达到 5M。一分钟后再显示: 20:57:01 sys@MYTWO> @show_pga 输入 user 的值: 27 SUBSTR(NAME,1,30) SID VALUE SIZE_MB ------------------------------------------------------------ ---------- ---------- ---------- session uga memory 27 5116164 4.87915421 session uga memory max 27 5116164 4.87915421 session pga memory 27 5406852 5.15637589 session pga memory max 27 5406852 5.15637589 会话 27 的 PGA 也已达到 5M。而总的 PGA 如下: ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 175 sys@MYTWO> select name,value,value/1024/1024 from v$sysstat where name like '%ga memory%'; NAME VALUE VALUE/1024/1024 ---------------------------------------- ---------- --------------- session uga memory 48698392 46.4424057 session uga memory max 209477448 199.773262 session pga memory 76298488 72.7639084 session pga memory max 124986976 119.196869 (练习 5 完)已经超过 80M,达到 119M。上面的练习充分说明了 pga_aggregate_target 只是 一个目标值,并不是一个强制的限制。 2. 不可控部分过大: Oracle 中,只有工作区在实例的控制之下,PGA 中的其他部分,并 不受 Pga_aggregate_target 参数的控制。例如,如果在包中建立一个很大的数组,并在数组 中填入数据,Oracle 无法对这种行为做出控制。下面实验一下: 练习 6:步 1:创建如下包: create or replace package demo_pkg as type array is table of char(2000) index by binary_integer; g_data array; end; / 步 2:在包变量中填入数据: begin for i in ..100000 loop demo_pkg.g_data(1):='x'; end loop; end; / 步 3:查看 PGA 总和: sys@MYTWO> select name,value,value/1024/1024 M from v$sysstat where name like '%ga memory%'; NAME VALUE M ---------------------------------------- ---------- ---------- session uga memory 313722896 299.189468 session uga memory max 598162760 570.452461 session pga memory 643118832 613.325912 session pga memory max 644371008 614.520081 已经达到了 600M,早已超过了我们设定的 Pga_aggregate_target 参数的 80M 的总和。 PGA 中的不可控制部分,应该由程序员、DBA 进行控制,Oracle 并不负责对他们进行限制。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 176 8.8 PGA 内存的回收 PGA 的内存是动态分配的。例如我们在上面的练习中将 Pga_aggregate_target 设置为 80M,每会话 5M。但会话连接后,并不会立即分配 5M 内存,而是到需要时再分配。分配 的内存在使用完毕后,会释放给 Oracle 或 OS。_use_realfree_heap 参数是用来控制内存 释放目的地的。 当_use_realfree_heap 为 True 时,UGA 将不再是 PGA 的子堆,UGA 和 CGA 将使 用操作系统内核函数 mmap 来分配,而使用 munmap 释放时,内存将直接释放给 OS。 而不是释放给 PGA。如上面的练习 6,PGA 与 UGA 峰值时都达到了 600M 左右。当会话 退出后,会话所占内存将被释放,再次查看总的 PGA 占用: sys@MYTWO> select name,value,value/1024/1024 M from v$sysstat where name like '%ga memory%'; NAME VALUE M ---------------------------------------- ---------- ---------- session uga memory 1852432 1.76661682 session uga memory max 598162760 570.452461 session pga memory 18284736 17.4376831 session pga memory max 19471376 18.5693512 UGA 当前仅占 1.7M 多,而 PGA 当前占 17.43M 多。这说明刚才几百 M 的内存都还给 了 OS。当_use_realfree_heap 为 False 时,UGA、CGA 都是 PGA 的子堆,它们所占空 间释放后,仍会交回 PGA,不回给 OS,直到 OS 内存不足时,才会释放出来。但在 Windows 下不是这样,无论_use_realfree_heap 为什么,释放的内存都归 Windows 操作系统。 第 八 节 PGA 内存的回收 PGA 中的内存,在用户使用完后会被释放。根据情况 不同,它可能被释放给:  释放给操作系统  释放给 PGA  隐藏参数_use_realfree_heap 可以控制释放目的 地 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 177 8.9 相关视图 1. PGA 资料 sys@MYTWO> select * from v$pgastat; NAME VALUE UNIT ---------------------------------------------------------------- ---------- --------- 1 aggregate PGA target parameter 83886080 bytes 2 aggregate PGA auto target 61922304 bytes 3 global memory bound 1048576 bytes 4 total PGA inuse 15092736 bytes 5 total PGA allocated 28651520 bytes 6 maximum PGA allocated 28651520 bytes 7 total freeable PGA memory 0 bytes 8 PGA memory freed back to OS 0 bytes 9 total PGA used for auto workareas 0 bytes 10 maximum PGA used for auto workareas 0 bytes 11 total PGA used for manual workareas 0 bytes 12 maximum PGA used for manual workareas 0 bytes 13 over allocation count 0 14 bytes processed 1368064 bytes 15 extra bytes read/written 0 bytes 16 cache hit percentage 100 percent 已选择 16 行。 此视图中显示的是所有会话总的 PGA 信息。 第 九 节 相关视图  PGA 资料  V$SQL_WORKAREA  V$SQL_WORKAREA_HISTOGRAM  V$SQL_WORKAREA_ACTIVE  V$PROCESS ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 178 行 1:和 pga_aggregate_target 一样。PGA 总和的目标值。 行 2:Oracle 能够为运行中的工作区使用的自动模式的 PGA 内存总量。自动模式就是能分 配给工作区的内存。根据工作区负载和 PGA_AGGREGATE_TARGET 参数的值,Oracle 会 不断调整此行大小。如果此行和 aggregate PGA target parameter 相差太大。说明大量 PGA 内存被用作系统的其他成份(如 PL/SQL 或 JAVA 内存),只有很少留给工作区。DBA 必须 保证有足够的 PGA 内存,以自动模式保留给运行中的工作区。 PGA 的一部分被用于无法动态调整的部分,比如 UGA 中的“session 相关的信息”等。 而 PGA 内存的剩下部分则是可以动态调整的,由“aggregate PGA auto target”说明,此部 分即工作区。我们来看第二行的值,就表示可以动态调整的内存数量,该值不能与 pga_aggregate_target 设置的值差太多。如果该值太小,则 oracle 没有足够的内存空间来动态 调整 session 的内存工作区 也就是说,如果此行与第一行相差太大,预示着 PGA 目标值太小。 行 3:global memory bound 表示一个工作区的最大尺寸。并且 oracle 推荐只要该统计值低于 1M 时,就应该增加 pga_aggregate_target 的值。随着系统中工作区的增加,此行的值将逐渐 的减小。 此值也是目标值,很多时候会话实际分配的工作区会超过此值。 行 4:total PGA inuse :指示多少内存以当前模式被占用。能用来查明多少内存由其他 PGA 成份消耗(如 PL/SQL 或 JAVA 等)。当前模式是指 PGA 中非工作区部分,而自动模式是 PGA 中工作区可以占用的部分。 行 5:total PGA allocated :实例分配的当前模式的 PGA 内存。Oracle 尽量保持此值低于 PGA_AGGREGATE_TARGET 。但随着工作量加大,或 PGA_AGGREGATE_TARGET 设 置的比较小,Oracle 可以使用超过此值的内存。 行 13 :over allocation count:由于 PGA_AGGREGATE_TARGET 容量不足而必须被扩展的 次数,此值自实例启动后累积计算。如果在使用 SQL 工作区过程中,oracle 认为 pga_aggregate_target 过小,则它自己会去多分配需要的内存。则多分配的次数就累加在 over allocation count 指标里。该值越小越好,最好为 0。如果这个值大于 0,就要增加 pga 的值 行 14: bytes processed:内存强烈的 SQL 操作所处理的字节数。自实例开始以来累积计算。 行 15: extra bytes read/written:不能以 Optimal 方式执行的操作所占的字节数,自实例启 动以来累积计算。当工作区不能运行在 Optimal 方式下时,额外的 Pass 将被一次或多次执 行。 行 16: cache hit percentage:表示完全在内存里完成的操作的字节数与所有完成的操作(包 括 optimal、onepass、multipass)的字节数的比率。如果所有的操作都是 optimal 类,则该值 为 100%。 此视图从宏观上观察 PGA 的使用情况,在这里行 2、行 3、行 13、行 16 需要注意。其 他行可忽略不看。 2. V$SQL_WORKAREA 来自 X$QKSMMWDS 。是 X$QKSMMWDS 唯一的视图表现。 能够用 ADDRESS,HASH_VLUE 和 V$SQLAREA 中的相同字段联接。或者用 ADDRESS, HASH_VALUE, CHILD_NUMBER 和 V$SQL 联接。这说明 V$SQL_WORKAREA 是和 V$SQL 类似的,没有经过汇总的信息。 名称 是否为空? 类型 ----------------------- -------- -------------- ADDRESS RAW(4) :父游标句柄的地址 HASH_VALUE NUMBER :库缓存中父声明的 HASH 值。 CHILD_NUMBER NUMBER :使用此工作区的子游标编号。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 179 WORKAREA_ADDRESS RAW(4) :工作区句柄地址。这是此视图的主键。 OPERATION_TYPE VARCHAR2(40) :使用工作区的操作类型(SORT, HASH JOIN, GROUP BY, BUFFERING, BITMAP MERGE, or BITMAP CREATE) OPERATION_ID NUMBER :在执行计划中识别操作的唯一号码。可以和 V$SQL_PLAN 关联 POLICY VARCHAR2(20) :工作区的管理方式(手动或自动) ESTIMATED_OPTIMAL_SIZE NUMBER :完全在内存中执行操作所要求的工作区的 估计的大小(单位:KB)。数据来自优化器资料(optimizer statistics)或 以前执行的声明(previous executions) ESTIMATED_ONEPASS_SIZE NUMBER :在 ONEPASS 下执行操作所需工作区的大小。数据来 源和上一列相同。 LAST_MEMORY_USED NUMBER :在游标最后的执行期间工作区所需内存大小 (KB) LAST_EXECUTION VARCHAR2(20) :指出在游标最后的执行期间,工作区运行在 OPTIMAL,ONE PASS,ONE PASS memoryrequirement (or MULTI-PASS)方式下。(指的就是运行在 OPTIMAL,ONE PASS 或是 MULTIPASSES 方式) LAST_DEGREE NUMBER :在游标最后的执行期间的并行度 TOTAL_EXECUTIONS NUMBER :工作区活动的次数 OPTIMAL_EXECUTIONS NUMBER :工作区在运行在 OPTIMAL 的次数 ONEPASS_EXECUTIONS NUMBER :工作区在运行在 ONE PASS 的次数 MULTIPASSES_EXECUTIONS NUMBER :工作区在运行在低于 ONE PASS 要求的次 数 ACTIVE_TIME NUMBER :工作区活动的平均时间(单位:百分之一 秒) MAX_TEMPSEG_SIZE NUMBER :由此工作区实例创建的最大临时段大小(单位:字 节)。如果工作区没有溢出到磁盘,此列为 NULL LAST_TEMPSEG_SIZE NUMBER :由此最后的工作区实例创建的临时段大小(单位: 字节)(意即最后使用此工作区的实例,或工作区最后被使用时)。如 果最后的工作区实例没有溢出到磁盘则此列为空。 此视图专门针对工作区,不使用工作区的声明不会在此视图中出现。如 select * from big_table,将不会出现在此视图中。 另注:所有上述单位:KB,实际单位均是字节。使用时需注意。 在调优时,可以用 ESTIMATED_OPTIMAL_SIZE 列排序,找到几条最耗内存的语句, 看是否可以减少排序。也可以用 MULTIPASSES_EXECUTIONS 列排序,查找哪些语句是运 行在 Multipass 方式下,尝试调优这些语句,对提高整体排序性能有很大帮助。 3. V$SQL_WORKAREA_HISTOGRAM 分组显示自实例启动以来,累积的工作区资料。按照所需的 Optimal 内存大小,从 0 到 1K 开始,1-->2K,2-->4K,4-->8K,6-->16K 等等直到 2T-->4TB,共分成 33 个组。 此视图显示每一个组中有多少次操作能运行在 Optimal 模式下,多少次运行在 One-pass 模 式下,和,多少次运行在 Multi-pass 模式下。 DBA 可以在希望的时间间隔期间的开始和结束分别建立快照,以得到资料。 LOW_OPTIMAL_SIZE NUMBER :一个组的低值 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 180 HIGH_OPTIMAL_SIZE NUMBER :一个组的高值 OPTIMAL_EXECUTIONS NUMBER :自实例开始以来,在此组中以 OPTIMAL 执行的次数 ONEPASS_EXECUTIONS NUMBER :在此组中以 One-pass 模式执行的 次数 MULTIPASSES_EXECUTIONS NUMBER :以 MULTIPASSES 模式执行的次 数 TOTAL_EXECUTIONS NUMBER :总的次数 4. scott@MYTWO> desc v$sql_workarea_active:当前工作区中的所有信息 名称 是否为空? 类型 ----------------- -------- ------------ WORKAREA_ADDRESS RAW(4) : 地 址 。 可 以 和 V$SQL_WORKAREA.WORKAREA_ADDRESS 联接 OPERATION_TYPE VARCHAR2(40):操作类型。 OPERATION_ID NUMBER:在执行计划中识别操作的唯一号码。可以和 V$SQL_PLAN 关联 POLICY VARCHAR2(12):工作区的管理方式 SID NUMBER:使用此工作区的会话 SID QCINST_ID NUMBER:查询协调(查询协调在并行查询中出现)者实 例 ID QCSID NUMBER:查询协调者的会话 ID ACTIVE_TIME NUMBER:此工作区激活的平均时间(厘秒为单位) WORK_AREA_SIZE NUMBER:被当前操作使用的最大工作区大小 EXPECTED_SIZE NUMBER:工作区的预期大小。其实就是所能分配的工作 区的大小 ACTUAL_MEM_USED NUMBER:当前分配个工作区的 PGA 内存大小(KB)。 MAX_MEM_USED NUMBER:这个工作区使用的最大大小(KB)。 NUMBER_PASSES NUMBER:以 Onepass 方式运行的次数 TEMPSEG_SIZE NUMBER:临时段大小 TABLESPACE VARCHAR2(31):临时表空间名称 SEGRFNO# NUMBER:临时段的表空间的文件 ID SEGBLK# NUMBER:给工作区创建临时段的 block 数 此视图中的一部分列可用来和 V$TEMPSEG_USAGE 视图结合在一起显示,具体的操作 在表空间部分讲述。 5. scott@MYTWO> desc v$process; 名称 是否为空? 类型 ----------------- -------- ------------ ADDR RAW(4) :进程状态对象的地址。 PID NUMBER :Oracle 为每个进程设立的统一的编号 SPID VARCHAR2(12) :操作系统为每个进程设立的编号 USERNAME VARCHAR2(15) :用户名 SERIAL# NUMBER :进程序列号。同 PID 的进程结束后再启动,此 列加 1 TERMINAL VARCHAR2(16):操作系统终端标识。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 181 PROGRAM VARCHAR2(64):程序名 TRACEID VARCHAR2(255):跟踪文件标识 BACKGROUND VARCHAR2(1):是否是后台进程。1:是 NULL:是正常 进程 LATCHWAIT VARCHAR2(8):进程正在等待的 Latch 的地址。NULL 为 并不等待任何 Latch LATCHSPIN VARCHAR2(8):进程正在 SPIN 的 Latch 的地址。NULL 为 并不 SPIN 任何 Latch。 PGA_USED_MEM NUMBER:当前进程正在使用的 PGA 内存 PGA_ALLOC_MEM NUMBER:当前进程分配的 PGA 内存。包含服务器进程尚未释放 给 OS 的自由 PGA 内存 PGA_FREEABLE_MEM NUMBER:能够被释放的自由 PGA 内存 PGA_MAX_MEM NUMBER:曾经分配的最大的 PGA 内存 ADDR :有一个状态对象,描述每个进程当前的状态。此列就是此状态对象的地址。这个 状态对象,也可当作进程的句柄。 每有一个会话登录,此视图中会多出一行,此行就是和用户对应的服务器进程。用户的 所用操作,均是服务器进程在服务器端完成的。服务器进程到 PGA 中取出用户的代码(包 括我们下面要提到的“游标代码”)运行。运行期间的一些中间结果服务器进程会保留在各 用户的PGA中,运行结束后服务器进程将运行最终结果如用户所查找的行,存贮在PGA中。 用户到 PGA 中取最终的结果。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 182 8.10 手动 PGA 管理 使用如下命令,显示所有 area 参数: 21:10:03 sys@MYTWO> show parameter area NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ bitmap_merge_area_size integer 1048576 create_bitmap_area_size integer 8388608 hash_area_size integer 131072 sort_area_retained_size integer 0 sort_area_size integer 65536 workarea_size_policy string AUTO 最后一个 workarea_size_policy 用来设置 PGA 的管理方式,如果设为 AUTO 为自动,设 为 MANUAL,则为手动管理方式。在自动管理方式下,我们可以通过 pga_aggregate_target 管理 PGA 中的可控部分(即工作区),手动方式也不例外。在手动方式下,我们仍然只能设 置可控部分――“工作区”。不过在手动方式下,工作区大小通过 5 个参数设定,它们就在 上面显示的参数中列出: bitmap_merge_area_size :位图合并操作将占用的内存大小,如果超出了,则溢出到临时段, 下同。 create_bitmap_area_size :创建位图索时将占用的内存大小。 hash_area_size :HASH 联接将会占用的内存大小。 sort_area_retained_size 和 sort_area_size : 排 序 将 会 占用的内存大小。其中 sort_area_retained_size 部分的内存分配自 UGA,sort_area_retained_size 到 sort_area_size 部分 自分配自 PGA,超过 sort_area_size 溢出到临到段。 第 十 节 手动 PGA 管理  bitmap_merge_area_size  create_bitmap_area_size  hash_area_size  sort_area_retained_size  sort_area_size  workarea_size_policy ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 183 自动管理方式中你不需要设置这 5 个参数,即使用设置了也会被忽略。自动管理方式中, 你只需要设置一个参数,就是 pga_aggregate_target。除了设置参数各不相同外,最重要的就 是管理方式不同。pga_aggregate_target是一个目标值,Oracle会跟所其大小和工作负载情况, 自动为每个会话分配工作区大小。而上述 5 个参数,不再是目标值,它们就是实际要分配的 工作区大小。比如,如果你将sort_area_size设为10M,虽然并不是每个会话一开始就占10M, 但每个会话的排序操作,无论登录的会话有多、系统繁不繁忙,都可以使用 10M 大小的 PGA。 也就是说,如果有 10 个会话登录,总的排序工作区将是 100M,如果 100 个会话将是 1000M, 等等。你很可能希望在早上不繁忙时,让每个用户占 10M,而到了中午繁忙时让每个用户 占 1M,这只有使用自动管理方式才能实现。我们上面的例子也有证明,在自动管理方式下, 随着系统的繁忙程度,每个会话 PGA 的分配会放缓,如果系统进一步繁忙,将会减少每个 会话分得的 PGA。这就是 PGA 的自动管理。 既然有了自动管理,手动管理还有必要吗?有。一是在 9i 共享服务器方式下,仍不支 持 PGA 自动管理,要到 10G 的共享服务器才能支持自动管理。另一种情况是,假设数据库 运行在 PGA 自动管理方式中,在凌晨 2:00 时,你想让一个运行批处理的会话占用更多的 PGA,按正常方式它只能占到 pga_aggregate_target 的 5%。这时,不必更改系统设置,只需 以“ Alter session”方式在会话内部更改 PGA 管理方式,即可让某一会话占用比较多的 PGA, 如下: 步 1:设置 PGA 为手动管理方式: scott@MYTWO> alter session set workarea_size_policy=manual; 会话已更改。 分别设定 sort_area_size 为 80M,sort_area_retained_size 为 40M scott@MYTWO> alter session set sort_area_size=83886080; --注意,此处不能使用 80M 会话已更改。 scott@MYTWO> alter session set sort_area_retained_size=41943040; 会话已更改。 步 2: scott@MYTWO> select * from t1 order by owner; 已选择 240000 行。 已用时间: 00: 00: 17.03 Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=1409 Card=240000 Bytes=19920000) 1 0 SORT (ORDER BY) (Cost=1409 Card=240000 Bytes=19920000) 2 1 TABLE ACCESS (FULL) OF 'T1' (Cost=1174 Card=240000 Bytes=19920000) Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 6292 consistent gets 6160 physical reads 0 redo size 16823207 bytes sent via SQL*Net to client 176492 bytes received via SQL*Net from client 16001 SQL*Net roundtrips to/from client ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 184 1 sorts (memory) 0 sorts (disk) 240000 rows processed 可以看到,此时的 PGA 目标大小为 80M,每会话使用的 PGA 是 5M,我们上几节曾做 过测试,此时会执行在 Onepass 下。但从资料上看,并没有磁盘排序。下面是排序运行期间 会话 PGA 占用情况: 11:25:26 sys@MYTWO> @show_pga 输入 user 的值: 12 SUBSTR(NAME,1,30) SID VALUE SIZE_MB ------------------------------------------------------------ ---------- ---------- ---------- session uga memory 12 32939888 31.4139252 session uga memory max 12 32939888 31.4139252 session pga memory 12 33326332 31.7824669 session pga memory max 12 33326332 31.7824669 11:28:18 sys@MYTWO> @show_work STATISTIC# SID NAME VALUE ---------- ---------- ----------------------------------- ---------- 226 12 workarea memory allocated 30096 <----- 分配了 30M 的 UGA 227 12 workarea executions - optimal 8 228 12 workarea executions - onepass 0 229 12 workarea executions - multipass 0 运行结束后,再次查看资料视图: 11:28:18 sys@MYTWO> @show_work STATISTIC# SID NAME VALUE ---------- ---------- ----------------------------------- ---------- 226 12 workarea memory allocated 0 227 12 workarea executions - optimal 22 228 12 workarea executions - onepass 0 229 12 workarea executions - multipass 0 只有 Optimal,所有操作都是在 Optimal 下完成的。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 185 8.11 再论游标 游标是 UGA 中最重要的一部分内容,也是 PGA 中需要 DBA 参于调节的重要部分,除 游标之外的部分,我们可称之为不可控部分,如包中的全局变量等信息。从概念上讲,工作 区是游标的一部分,但实际上,工作区自成一体,其大小大不受制于游标。有关工作区的内 容,我们上面已经讲过了,下面所要讲,是游标中有关 SQL 声明解析树、执行计划等部分 的相关内容。 我们每打开一个游标,UGA 中都要为其分配一块空间。SQL 是 3G 过程化语言,每一 个 SQL 语句,每一个游标,其实都可看作一段程序,一段运行在服务器端的程序,这就必 然要在服务器上为其开辟一块内存空间。这块用来运行“游标程序”的内存空间,就简称游 标。每一段运行中的“游标程序”都是不一样的,例如 Oracle 也是程序,如果运行两遍 Oracle, 虽然 Oracle 代码是一模一样的,但我们必然要为这两遍运行分配两大块互相独立的 Oracle 相关内存。游标也是一样,无论代码是否相同,每个运行中的游标,都要有一块自己的内存 空间。但游标是过程化语言,由用户给出运行目标,由服务器负责生成运行步骤。为生成更 好的运行步骤,服务器不得不统计各种信息,进行各种对比,才能生成一个良好的运行步骤, 也就是说,生成运行步骤要消耗服务器大量的时间、资源。为了提高速度,Oracle 将生成好 的运行步骤存入一个共享内存区中,以便再次执行相同游标时不必重新计算运行步骤,这块 用于存贮游标运行步骤的共享内存区,就是共享 SQL 区了。有了共享 SQL 中的运行步骤, 就好像有了程序的代码,还要开辟一块内存运行这段代码,运行这段代码的内存就是私有 SQL 区了,Oracle 中游标概念的定义,指的就是这块用于运行“游标代码”的内存。对一 个服务器系统来讲,私有 SQL 区的作用,比之共享 SQL 要大多了。没有共享 SQL 区,最 多是运行效率不高,没有了私有 SQL 区,则是根本就不能运行。 私有 SQL 区可占内存的大小,由参数 Open_cursor 初始化参数决定,此参数默认值是 300 (在 9i 中是 50) 。即每一会话,最多能为其开辟 300 个运行“游标代码”的程序。这一部 第 十一 节 再论游标  游标是什么  游标的位置  静态与动态游标的区别  私有 SQL 区与共享 SQL 区  PGA 的可控与不可控部分 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 186 分虽说可控,但实亦不可控。因为随着会话数量的增加,私有 SQL 区大小,也会相应增加。 游标的代码在共享 SQL 区,而运行游标代码所需内存,则是私有 SQL。这种结构,和 “动态链接库”的概念,极为相似。 私有 SQL 区中的每一个游标,归根到底只是一块内存空间。打开游标即是分配这块空 间,但关闭游标后,有时游标会被缓存在 PGA 中,不被缓存的游标,在关闭时其私有 SQL 内存立即释放。 例 11_1:动态游标的例子 declare msql varchar2(500); mcur number; begin for i in 1..10 loop mcur:=dbms_sql.open_cursor; msql:='select myid from t2 where myid='||to_char(i); dbms_sql.parse(mcur,msql,dbms_sql.native); end loop; end; / 运行上述代码后,查看 PGA 占用情况: 10:57:44 sys@MYTWO> @show_pga 输入 user 的值: 12 SUBSTR(NAME,1,30) SID VALUE SIZE_MB ------------------------------------------------------------ ---------- ---------- ---------- session uga memory 12 142424 .135826111 session uga memory max 12 142424 .135826111 session pga memory 12 294508 .280864716 session pga memory max 12 294508 .280864716 PGA 和 PGA MAX,UGA 和 UGA MAX 相等,证明不闭游标,内存不释放。 如在循环中加入关闭游标的代码再试: 11:08:34 sys@MYTWO> @show_pga 输入 user 的值: 12 SUBSTR(NAME,1,30) SID VALUE SIZE_MB ------------------------------------------------------------ ---------- ---------- ---------- session uga memory 12 76960 .073394775 session uga memory max 12 76960 .073394775 session pga memory 12 228972 .218364716 session pga memory max 12 294508 .280864716 可以看到,游标关闭,内存释放。 例 11_2:静态游标的例子 declare cursor aa1(mm in number) is select id from t2 where myid=mm; cursor aa2(mm in number) is select id from t2 where myid=mm; cursor aa3(mm in number) is select id from t2 where myid=mm; cursor aa4(mm in number) is select id from t2 where myid=mm; ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 187 cursor aa5(mm in number) is select id from t2 where myid=mm; cursor aa6(mm in number) is select id from t2 where myid=mm; cursor aa7(mm in number) is select id from t2 where myid=mm; cursor aa8(mm in number) is select id from t2 where myid=mm; cursor aa9(mm in number) is select id from t2 where myid=mm; cursor aa10(mm in number) is select id from t2 where myid=mm; i number(3):=1; begin open aa1(i); open aa2(i); open aa3(i); open aa4(i); open aa5(i); open aa6(i); open aa7(i); open aa8(i); open aa9(i); open aa10(i); end; / 11:27:17 sys@MYTWO> @show_pga 输入 user 的值: 12 SUBSTR(NAME,1,30) SID VALUE SIZE_MB ------------------------------------------------------------ ---------- ---------- ---------- session uga memory 12 142424 .135826111 session uga memory max 12 142424 .135826111 session pga memory 12 294508 .280864716 session pga memory max 12 294508 .280864716 以上是静态游标不关闭时的PGA占用情况。分别将10个静态游标关闭后再看内存占用: 11:29:50 sys@MYTWO> @show_pga 输入 user 的值: 12 SUBSTR(NAME,1,30) SID VALUE SIZE_MB ------------------------------------------------------------ ---------- ---------- ---------- session uga memory 12 142424 .135826111 session uga memory max 12 142424 .135826111 session pga memory 12 294508 .280864716 session pga memory max 12 294508 .280864716 可以看到,静态游标即使关闭了,也如同没有关闭。因为静态游标会被 Oracle 自动缓 存。Oracle 并没有提供哪个视图查看 PGA 中的游标信息,但由于游标与共享 SQL 区的对应 关系,Oracle 通过共享 SQL 区中的信息,显示游标相关信息。游标(即私有 SQL 区)与共 享 SQL 区是以“锁”的方式相联系的。每一个打开或被缓存的游标,都在共享 SQL 区相应 对象句柄上,持有 NULL 模式的句柄锁。如果 CURSOR_SPACE_FOR_TIME 为 TRUE,那 么在对象相关堆上,还会持有 Shared 模式的 PIN 正是利用这种对应关系,Oracle 提供的查 看游标的视图 V$open_cursor 就是显示所有句柄锁为 1 的共享 SQL 区对象,这些对象,就 是正在打开的游标了。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 188 附录: 1.show_pga.sql 脚本: col name for a30 select name ,value,sid from v$sesstat a , v$statname b where a.statistic#=b.statistic# and b.name like 'session _ga memory%' and a.sid=&user; 2.pga.sql 脚本 select PGA_USED_MEM "PGA Used",PGA_ALLOC_MEM "PGA Allocate", PGA_MAX_MEM "PGA Max" from v$process a ,v$session b where a.addr=b.PADDR and b.sid=&sid; 3.show_para.sql 脚本 col description for a40 col value for a30 select i.ksppinm name, i.ksppdesc description, cv.ksppstvl value, cv.ksppstdf isdefault, decode(bitand(cv.ksppstvf,7),1,'MODIFIED',4,'SYSTEM_MOD','FALSE') ismodified, decode(bitand(cv.ksppstvf,2),2,'TRUE','FALSE') isadjusted from sys.x$ksppi i, sys.x$ksppcv cv where i.inst_id=userenv('Instance') and cv.inst_id=userenv('Instance') and i.indx=cv.indx and i.ksppinm like '%¶%' order by replace(i.ksppinm,'_',''); 4.show_work.sql 脚本 col name for a40 select name ,value,sid from v$sesstat a , v$statname b where a.statistic#=b.statistic# and b.name like 'workarea%' and a.sid=&user; ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 189 8.12 堆栈区管理 堆栈区是 CGA 的一部分,本属不可控部分,此部分所占内存为 1M。通常时候,此值 远超过我们实际需要的堆栈区。当用户非常多时,通过减少每个会话创建在 Oracle 服务器 中的会话堆栈大小,可以节省大量内存。比如,一个 1000 用户的系统,将堆栈从 1M 降为 500K 后,能节省出 1000 * 500K = 500M 的地址空间。但这需要经过大量测试,确保在新的 堆栈大小下,系统仍可正确运转。如果堆栈大小被缩小到 Oracle 服务端所必须的堆栈大小 以下,就会产生堆栈溢出错误,用户进程就失败(通常报 ORA-3113 错误),并且在 alert log 中不会有报错而且也不产生 trace 文件。Oracle 一般不推荐将堆栈该到 500K 以下(尽管不 少系统在 300K 时也能正常运行),但如果内存实在太小,低到 500K 以下也行。降低堆栈大 小的命令如下: orastack executable_name new_stack_size_in_bytes 下面的例子将堆栈改为 500K: orastack oracle.exe 500000 orastack tnslsnr.exe 500000 orastack svrmgrl.exe 500000 orastack sqlplus.exe 500000 在使用 ORASTACK 之前必须保证没有任何 oracle 进程正在运行。 此外,如果有程序在本地连接(没有通过 SQL*NET)了 Oracle,也要先停止(如在本 地运行 sqlplus 连接了实例)。 第 十二 节 堆栈区管理  堆栈区是 CGA 的一部分  它是不可控部分  缺省大小为 1MB  可以减少它避免内存浪费  通常不要低于 500KB 以下 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 190 第 9 章 SQL 调优 您将学习: 1. 访问路径 2. 连接 3. 优化器 4. 执行路径与 Hints(提示) 5. 大纲 6. 诊断工具 第 九 章 SQL 调优 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 191 9.1 访问路径 9.1.1 访问路径 访问路径就是 ORACLE 访问表数据的方法。这些方法很少,包括:  全表扫描:全表扫描可谓是声名狼籍,很多人认为它速度慢,是性能的瓶径。但有时使 用它反而可以提高速度。  各种类型的索引访问:使的最多的访问方式。  通过聚簇或 ROWID 方式直接访问: 下面我们分别介绍一下。 9.1.2 全表扫描 全表扫描又可简称为全扫,或全扫描,这个术语隐含很多意思。Oracle 将读取指定段 中用于某一点或另一点上的每个块。全扫描就是批读取所有的块,准确说是读取段高水标记 之下所有的块。在这里,Oracle 将从段的开始读到结尾。处理每一个块。全扫描是读取 Oracle 的大量数据的行之有效的方法。因为数据库将使用多块读取。由于 Oracle 知道它打算读取 读段中的每一块,因此它将一次性读取多个块,而非一次一个块。多块读的数量由初始化参 数 DB_FILE_MULTIBLOCK_READ_COUNT 确定。 第 一 节 访问路径 访问路径就是 ORACLE 访问表数据的方法,这些 访问路径有:  访问路径  全表扫描  ROWID 访问  索引扫描 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 192 9.1.3 ROWID 访问 每一个表都有一个 ROWID 伪列,此伪列中记录了每一行的物理地址。它包含文件号、块 号和行在该块中的行号。ROWID 对于获取一个特定的行来说可能是最快的方法。但是,使用 ROWID 获取成千上万的行,并不是最好的方法。因为每使用 ROWID 访问一行,都是一次逻辑 读。在不使用 ROWID 访问行时,Oracle 将在一次逻辑读中访问尽量多的行。 9.1.4 索引扫描 1.索引唯一扫描 索引唯一扫描和使用 ROWID 访问行是差不多的。只不过是把每一行的 ROWID 记录到另一 处地方,它被叫做索引,每次访问行时,先访问索引,从索引中取出行的 ROWID,根据 ROWID 再真正的访问行。 2.索引范围扫描 如果索引不是唯一型索引,那么,通过此索引的访问将是索引范围扫描。从根本上说, 索引范围扫描和索引唯一扫描是一样的。 3.索引跳跃扫描 在复合索引中,比如索引包含两个列:A 列、B 列,如果以 B 列为条件进行查询,将使 用索引跳跃扫描。 4.索引全扫描 索引全扫描(Index Full Scans)不读取索引结构上的每个块,这与其名字及我们关于 全扫描的知识相背。可以这样说,它根据部分枝块,找到第一个叶块,然后按叶块双向链表 的顺序,处理所有的叶块。 5.索引快速全扫描 索引快速全扫描是把索引当作表一样进行全扫描操作。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 193 9.2 连接 9.2.1 嵌套循环 在嵌套循环连接中,Oracle 从第一个行源中读取第一行,然后和第二个行源中的数据进 行对比。所有匹配的记录放在结果集中,然后 Oracle 将读取第一个行源中的下一行。按这种 方式直至第一个数据源中的所在行都经过处理。第一个记录源通常称为外部表,或者驱动表, 相应的第二个行源称为内部表。使用嵌套循环连接是一种从连接结果中提取第一批记录的最 快速的方法。 在驱动行源表(就是您正在查找的记录)较小、或者内部行源表已连接的列有惟一的索引 或高度可选的非惟一索引时, 嵌套循环连接效果是比较理想的。嵌套循环连接比其他连接方 法有优势地方是,它可以快速地从结果集中提取第一批记录,而不用等待整个结果集完全确 定下来。这样,在理想情况下,终端用户就可以通过查询屏幕查看第一批记录,而在同时读取 其他记录。不管如何定义连接的条件或者模式,任何两行记录源可以使用嵌套循环连接,所以 嵌套循环连接是非常灵活的。 然而,如果内部行源表(读取的第二张表)已连接的列上不包含索引,或者索引不是高度 可选时, 嵌套循环连接效率是很低的。如果驱动行源表(从驱动表中提取的记录)非常庞大时, 其他的连接方法可能更加有效。 下面的图中说明了 select 语句中联连执行的方法: select /*+ordered*/ename,dept.deptno from dept,emp where dept.deptno=emp.deptno 第 二 节 连接 Oracle 在完成多表连接时,常用的连接方法有如下 三种:  嵌套循环  排序合并连接  HASH 连接 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 194 9.2.2 排序合并连接 当内存能够提供足够的空间时,哈希(HASH)连接是 Oracle 优化器通常的选择。在哈希连 接中,Oracle 访问一张表(通常是较大的表),并在内存中建立一张基于连接键的哈希表。然 后它扫描连接中其他的表(通常是较大的表),并根据哈希表检测是否有匹配的记录。 只有在数据库初始化参数 HASH_JOIN_ENABLED 设为 True, 并且为参数 PGA_AGGREGATE_TARGET 设置了一个足够大的值的时候 ,Oracle 才会使用哈希边 连接 (HASH_AREA_SIZE 是 向 下 兼 容 的 参 数 , 但在 Oracle9i 之 前 的 版 本 中 应 当 使 用 HASH_AREA_SIZE)。这和嵌套循环连接有点类似——Oracle 先建立一张哈希表以利于操作进 行。当使用 ORDERED 提示时,FROM 子句中的第一张表将用于建立哈希表。 当缺少有用的索引时,哈希连接比嵌套循环连接更加有效。哈希连接可能比排序合并连 接更快,因为在这种情况下只有一张源表需要排序。哈希连接也可能比嵌套循环连接更快, 因为处理内存中的哈希表比检索 B_树索引更加迅速。和排序合并连接、群集连接一样,哈 希连接只能用于等价连接。和排序合并连接一样,哈希连接使用内存资源,并且当用于排序 内存不足时,会增加临时表空间的 I/O(这将使这种连接方法速度变得极慢)。最后,只有 基于代价的优化器才可以使用哈希连接。 下面的图中说明了 select 语句中联连执行的方法: select /*+ordered*/ename,dept.deptno from emp,dept where dept.deptno=emp.deptno ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 195 9.2.3 HASH 连接 在排列合并连接中,Oracle 分别将第一个源表、第二个源表按它们各自要连接的列排序, 然后将两个已经排序的源表合并。如果找到匹配的数据,就放到结果集中。 在缺乏数据的选择性或者可用的索引时,或者两个源表都过于庞大(超过记录数的 5%) 时,排序合并连接将比嵌套循环连更加高效。但是,排列合并连接只能用于等价连接(WHERE D.deptno=E.dejptno,而不是 WHERE D.deptno>=E.deptno)。排列合并连接需要临时的内存 块,以用于排序(如果 SORT_AREA_SIZE 设置得太小的话)。这将导致在临时表空间占用更多的 内存和磁盘 I/O。 下面的图中说明了 select 语句中联连执行的方法: select /*+ordered*/ename,dept.deptno from emp,dept where dept.deptno=emp.deptno ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 196 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 197 9.3 优化器 9.2.1 优化器 用来生成执行计划的工具就是优化器。Oracle 中优化器有两种 CBO(基于成本)和 RBO (基于规则)。 RBO 已经很少使用,这里不再介绍。我们主要讲述 CBO 优化器的注意事项。 9.2.2 CBO 生成执行计划的步骤 1. 优化器基于可以的 Access Paths(访问路径)和 Hints(提示),生成一组潜在的执行计 划 2. 优化器基于声明所访问表、索引和分区存放在数据字典中相关的数据分布、存储特性的 资料,评估每一个执行计划。此步骤中,优化器基于 I/O, CPU, 和 memory,计算出执行计 划的 Cost(成本), 3. 优化器比较所有执行计划的成本,选择成本最低的作为最终的执行计划。 9.2.3 CBO 的组成部分 CBO 包括三个主要部分:Query Transformer(查询转换器)、Estimator(评估器)、Plan Generator(计划生成器)。 因此,SQL 声明的解析也分为三个步骤,先由查询转换器对 SQL 声明做转换,简单的 第 三 节 优化器  优化器  CBO 生成执行计划的步骤  CBO 的组成部分  视图合并 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 198 话,查询转换就是在开始计算成本前,将声明转为更合理的形式。然后由评估器从选择性、 集的势和成本三个方面评估表、索引和各种连接,最后由计划生成器生成执行计划。 9.2.4 视图合并 1. 什么是视图合并: 将查询声明中本来对视图的操作,转换为对基本表的操作,就是视图合并。此处所指的 视图,不但包括真正的视图,还包括“内联视图”。看以下例子: 创建视图如下: create view v_t22 as select * from t2 where myid<=10; 发布如下声明: select * from v_t22 vt,t4 where vt.myid=t4.id; --------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| --------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 8 | 2360 | 39 (13)| |* 1 | HASH JOIN | | 8 | 2360 | 39 (13)| | 2 | TABLE ACCESS BY INDEX ROWID| T4 | 10 | 920 | 4 (25)| |* 3 | INDEX RANGE SCAN | T4_ID | 10 | | 3 (34)| |* 4 | TABLE ACCESS FULL | T2 | 32 | 6496 | 35 (12)| --------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("T2"."MYID"="T4"."ID") 3 - access("T4"."ID"<=10) 4 - filter("T2"."MYID"<=10) 看,在执行计划中,已经找不到 V_T22 的影子了。对 V_T22 的操作,如“vt.myid=t4.id”, 被转换成执行计划中第 1 步,参考谓词信息,可知这一步是"T2"."MYID"="T4"."ID"。执行 计划的第 4 步是一个过滤操作:filter("T2"."MYID"<=10)。这个过滤操作,其实就是被展开 的视图。因为是将视图中的条件合并到基表上,因此,这种操作被称为“视图合并”。正是 因为将 V_T22 中的条件合并进了查询中,才可将此条件“传递”到第 3 步:"T4"."ID"<=10。 查询声明中,我们并没有要求限制 T4 表的行,但根据视图中合并过来的条件,优化器可以 做出正确的判断,将视图的条件“传递”到 T4 表中。这大大减少了需要作 HASH 连接的结 果集的行数。如果没有视图合并,优化器是无法进行条件“传递”的,这就是视图合并的好 处。视图就像一个盒子,条件藏在其中。现在将盒子打开,将条件拿出来,这样优化器可以 更好的利用这个“条件”。可以推测,上述声明的查询转换步骤: select * from v_t22 vt,t4 where vt.myid=t4.id; select * from t2 , t4 where t2.myid<=10 and t2.myid=t4.id; 视图 合并 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 199 select * from t2 , t4 where t2.myid<=10 and t4.id<=10 and t2.myid=t4.id; Oracle 文档中有:视图包含下列子句:时,不能进行视图合并: 1). Set operators (UNION, UNION ALL, INTERSECT, MINUS) 2). A CONNECT BY clause 3). A ROWNUM pseudocolumn 4). Aggregate functions (AVG, COUNT, MAX, MIN, SUM) in the select list 但经试验,视图中含有 SUM 等集合函数,并不影响视图合并。其实,当视图中含有 SUM 等集合函数时,并不会进行视图合并,但会进行 Pushes Predicates,效果看起来和视图合并 一样。既然效果一样,我们就认为它也能进行视图合并。下面进行测试: 创建视图: create view v_t2_max as select max(myid) MAX_MYID,id from t2 group by id; 发布查询声明: select vt2.MAX_MYID,t4.id from v_t2_max vt2,t4 where vt2.id=t4.id and t4.owner='SCOTT'; | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| ------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 40 | 68 (18)| | 1 | SORT GROUP BY | | 1 | 40 | 68 (18)| |* 2 | HASH JOIN | | 2500 | 97K| 66 (16)| |* 3 | TABLE ACCESS FULL | T4 | 2500 | 35000 | 28 (11)| | 4 | TABLE ACCESS FULL | T2 | 6368 | 161K| 36 (14)| ------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("T2"."ID"="T4"."ID") 3 - filter("T4"."OWNER"='SCOTT') 执行计划中没有 V_T2_MAX 视图,还是进行了视图合并。不过,如果视图中包括 ROWNUM,视图不会被合并: 创建视图: create view v_t22 as select rownum RM,t2.* from t2 where myid<=10; 发布声明: select * from v_t22 vt,t4 where vt.myid=t4.id; | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| ------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 377 | 113K| 65 (14)| |* 1 | HASH JOIN | | 377 | 113K| 65 (14)| | 2 | VIEW | V_T22 | 377 | 81432 | | | 3 | COUNT | | | | | |* 4 | TABLE ACCESS FULL| T2 | 377 | 76531 | 35 (12)| | 5 | TABLE ACCESS FULL | T4 | 5000 | 449K| 29 (14)| ------------------------------------------------------------------------- Predicate Information (identified by operation id): 1 - access("VT"."MYID"="T4"."ID") 谓词 传递 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 200 4 - filter("T2"."MYID"<=10) 可以看到,第 2 步,有视图 V_T22。它的谓词没有被合并到查询声明中,因此, T2.MYID<=10 这个条件,也没能“传递”给 T4 表。 以上实验,都是以真正的视图为例子,如果将视图换为内联视图,结果一样: sid=12 pid=12> @show_plan 输入 msql 的值: select * from (select * from t2 where myid<=10) vt,t4 where vt.my --------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| --------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 8 | 2360 | 39 (13)| |* 1 | HASH JOIN | | 8 | 2360 | 39 (13)| | 2 | TABLE ACCESS BY INDEX ROWID| T4 | 10 | 920 | 4 (25)| |* 3 | INDEX RANGE SCAN | T4_ID | 10 | | 3 (34)| |* 4 | TABLE ACCESS FULL | T2 | 32 | 6496 | 35 (12)| --------------------------------------------------------------------------------- Predicate Information (identified by operation id): 1 - access("T2"."MYID"="T4"."ID") 3 - access("T4"."ID"<=10) 4 - filter("T2"."MYID"<=10) 如果查询变为:select * from (select rownum r_num,t2.* from t2 where myid<=10) vt,t4 where vt.my,将不会进行视图合并。Oracle 另外准备了一个提示:NO_MERGE,它也可以 用来阻止视图合并: sid=12 pid=12> @show_plan 输入 msql 的值: select * from (select /*+no_merge*/* from t2 where myid<=10) vt,t4 where vt.myid=t4.id ------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| ------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 377 | 109K| 65 (14)| |* 1 | HASH JOIN | | 377 | 109K| 65 (14)| | 2 | VIEW | | 377 | 77285 | | |* 3 | TABLE ACCESS FULL | T2 | 377 | 76531 | 35 (12)| | 4 | TABLE ACCESS FULL | T4 | 5000 | 449K| 29 (14)| ------------------------------------------------------------------------- Predicate Information (identified by operation id): 1 - access("VT"."MYID"="T4"."ID") 3 - filter("T2"."MYID"<=10) 从 T2.MYID<=10 没有转移给 T4.ID,就可以看到出没有进行视图合并。 视图合并的目的是为了将视图里面的谓词拿到外面,放进查询声明中,然后结合查询声 明的其他谓词或信息,看能不能进行一些优化。如果明知将视图里面的条件拿到外面对优化 并无帮助,很多时候,优化器就不再做视图合并了。看如下例子, sid=12 pid=12> @show_plan 输入 msql 的值: select t4.object_id from v_t2_max vt2,t4 where vt2.id=t4.id(本来此处有谓 词:t4.owner=’SCOTT’) ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 201 ------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| ------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 7534 | 139K| 73 (24)| |* 1 | HASH JOIN | | 7534 | 139K| 73 (24)| | 2 | TABLE ACCESS FULL | T4 | 5000 | 30000 | 28 (11)| | 3 | VIEW | V_T2_MAX | 7534 | 97942 | | | 4 | SORT GROUP BY | | 7534 | 97942 | 43 (28)| | 5 | TABLE ACCESS FULL| T2 | 7534 | 97942 | 37 (17)| ------------------------------------------------------------------------- Predicate Information (identified by operation id): 1 - access("VT2"."ID"="T4"."ID") 将 T4.OWNER=’SCOTT’去掉,这样,即使 V_T2_MAX 的条件被合并到外面,也不会 有任何的帮助。本来最后还有一个谓词,将视图中的谓词放到外面,将两个谓词放在一起看 能不能找到更快完成声明的方法,现在什么都没了,不必再做什么视图合并了。对于这种可 合并也可不合并的操作,可以通过提示来改变它的默认行为: sid=12 pid=12> @show_plan 输入 msql 的值: select /*+merge(vt2)*/ t4.object_id from v_t2_max vt2,t4 where vt2.id=t4.id -------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| --------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 5000 | 126K| | 135 (12)| | 1 | SORT GROUP BY | | 5000 | 126K| 324K| 135 (12)| |* 2 | HASH JOIN | | 5000 | 126K| | 66 (16)| | 3 | TABLE ACCESS FULL | T4 | 5000 | 65000 | | 28 (11)| | 4 | TABLE ACCESS FULL | T2 | 6368 | 82784 | | 36 (14)| --------------------------------------------------------------------------------- Predicate Information (identified by operation id): 2 - access("T2"."ID"="T4"."ID") 加了 MERGE 提示,优化器又进行了视图合并。对于这个操作,到底是合并好还是不合 并好,要进行一步的测试才可知道。可以将 T2 和 T4 表设计的非常大,然后,分别观察合 并与不合并时的 LIO、PIO(物理 I/O),解析时间等,就可知道到底谁更合适。 下面再来看段 Oracle 文档:如果视图包含下列操作,只要复杂视图合并(Complex View Merging)可用,就能够进行视图合并: 1). A GROUP BY clause 2). A DISTINCT operator in the select list 当视图有多个基表时,如果有右外连接( right side of an outer join ),视图合并将不会 进行。但是当右外连接的基表只有一个时,可以使用复杂视图合并(Complex View Merging)。 还有,对于如下操作: CREATE VIEW emp_v AS SELECT last_name,employee_id FROM employees; SELECT CURSOR(select * from sys.dual), last_name, employee_id from emp_v; 上面的查询不会视图合并。(意即只要带 CURSOR expression,就不会视图合并) 如果 IN 子查询是无关的,复杂视图查询也能被用来合并 IN 子查询。 说明:什么是复杂视图合并的一个例子(Complex View Merging)。对复杂的视图进行 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 202 合并,就是复杂视图合并。复杂视图的概念,就是带有集合函数、Group by 子句等的视图。 下面再看文档中的一个关于 IN 的例子: 视图创建如下: CREATE VIEW min_salary_view AS SELECT department_id, MIN(salary) min_sal FROM employees GROUP BY department_id; 原查询声明: SELECT employees.last_name, employees.salary FROM employees, departments WHERE (employees.department_id, employees.salary) IN (select department_id, min_sal from min_salary_view) AND employees.department_id = departments.department_id AND departments.location_id = 2400; 视图合并后: SELECT e1.last_name, e1.salary FROM employees e1, departments, employees e2 WHERE e1.department_id = departments.department_id AND departments.location_id = 2400 AND e1.department_id = e2.department_id GROUP BY e1.rowid, departments.rowid, e1.last_name, e1.salary HAVING e1.salary = MIN(e2.salary); ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 203 9.4 执行计划与 Hints(提示) 9.4.1 使用工具查看计划 练习如下例子,了解两种重建索引的不同: sid=10 pid=11> @show_plan 输入 msql 的值: alter index jj2_5_1 rebuild PLAN_TABLE_OUTPUT ---------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost | ---------------------------------------------------------------------- | 0 | ALTER INDEX STATEMENT | | | | | | 1 | INDEX BUILD NON UNIQUE| JJ2_5_1 | | | | | 2 | SORT CREATE INDEX | | | | | | 3 | INDEX FAST FULL SCAN| JJ2_5_1 | | | | ---------------------------------------------------------------------- Note: rule based optimization 已选择 11 行。 sid=10 pid=11> @show_plan 输入 msql 的值: alter index jj2_5_1 rebuild online PLAN_TABLE_OUTPUT ---------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost | 第 四 节 执行计划与 Hints(提示)  执行计划是 Oracle 生成的执行 SQL 语句的计划。  执行计划主要是由访问路径和连接方式组成的。  使用 Hints(提示)可以告诉 Oracle 优化器你想使 用某种访问路径或连接方式,优化器最终是否采用 Hints,还要示情况而定。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 204 ---------------------------------------------------------------------- | 0 | ALTER INDEX STATEMENT | | | | | | 1 | INDEX BUILD NON UNIQUE| JJ2_5_1 | | | | | 2 | SORT CREATE INDEX | | | | | | 3 | TABLE ACCESS FULL | JJ2_5 | | | | ---------------------------------------------------------------------- Note: rule based optimization 已选择 11 行。 可以看到,联机重建索引先执行全表扫描,而非联机重建索引使用的是快速索引扫描。 下面看一个例子: Truncate table a1; insert into a1 select rownum,rownum,rownum from dba_objects where rownum<=100; Truncate table aa; insert into aa select rownum,rownum from dba_objects where rownum<=30; commit; 表 a1 有 100 行,aa 有 30 行。工作中由于程序逻辑的要求,在对 aa 查询时,要对 aa.id 列使用自定义函数 F1: Select „„ f1(id) „„ from aa 自定义函数 F 如下: create or replace function f1(col_name in number default NULL) return number as begin dbms_application_info.set_client_info(userenv('client_info')+1); return userenv('client_info'); end; 在 V$SESSION 视图中有一个 CLIENT_INFO 列,可以保存各个会话的一些私人信息。可以 通过 userenv('client_info') 来存取这个值。取的时候非常简单: Select userenv('client_info') from dual; 当然,也可以直接查询 V$SESSION.CLIENT_INFO 列。 设置这个值时需要使用 dbms_application_info 包,就如上面函数中一样。我们用它来统计 执行次数。 exec dbms_application_info.set_client_info(0) select count(*),max(f1('1')) from aa a,a1 b where a.id(+)=b.x; select userenv('client_info') from dual; 显示结果:100 次。 因为返回了 100 行,每一行都要调用一次 F1,共调用 100 次。其实 F1 只对 AA 表操作, AA 表只有 30 行,F1 只被调用 30 次就够了,下面我们将对 F1 的调用换一下位置: exec dbms_application_info.set_client_info(0) select count(*),max(ca) from (select f1('1') ca,a.* from aa a) a,a1 b where a.id(+)=b.x; select userenv('client_info') from dual; 显示结果:100 次。没有因为我们换了调用 F1 的位置,而减少它的调用次数。这是因为优 化器在对解析查询前,先要做“查询转换”,也就是将查询转换成便于优化的形式,可以看 一下上面两种形式查询的执行计划: -------------------------------------------------------------------- ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 205 | Id | Operation | Name | Rows | Bytes | Cost | -------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | | | 1 | SORT AGGREGATE | | | | | | 2 | NESTED LOOPS OUTER | | | | | | 3 | TABLE ACCESS FULL | A1 | | | | |* 4 | INDEX RANGE SCAN | AA_ID | | | | -------------------------------------------------------------------- Predicate Information (identified by operation id): 4 - access("A"."ID"(+)="B"."X") Note: rule based optimization 可以看到,虽然 SQL 声明不一样,但查询计划一样,优化器认为这是完成操作最佳的方 式,这将第二种形式的查询转换成第一种,再进行解析。下面,只要添上个 ROWNUM,优化 器就不会再进行转换: exec dbms_application_info.set_client_info(0) select count(*),max(ca) from (select f1('1') ca,a.*,rownum r from aa a) a,a1 b where a.id(+)=b.x; select userenv('client_info') from dual; 显示结果:30 次。 ---------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost | ---------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | | | 1 | SORT AGGREGATE | | | | | | 2 | MERGE JOIN OUTER | | | | | | 3 | SORT JOIN | | | | | | 4 | TABLE ACCESS FULL | A1 | | | | |* 5 | SORT JOIN | | | | | | 6 | VIEW | | | | | | 7 | COUNT | | | | | | 8 | TABLE ACCESS FULL| AA | | | | ---------------------------------------------------------------------- Predicate Information (identified by operation id): 5 - access("A"."ID"(+)="B"."X") filter("A"."ID"(+)="B"."X") Note: rule based optimization 第 6 步有一个 VIEW,这代表优化器在此步将(select f1('1') ca,a.*,rownum r from aa a) 这个内嵌视图实体化。 补充一点,这种操作,也可以通过 NO_MERGE 提示来完成: select count(*),max(ca) from (select /*+no_merge*/ f1('1') ca,a.* from aa a) a,a1 b where a.id(+)=b.x; ----------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| ----------------------------------------------------------------------------- ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 206 | 0 | SELECT STATEMENT | | 1 | 39 | 1076 (2)| | 1 | SORT AGGREGATE | | 1 | 39 | | | 2 | NESTED LOOPS OUTER | | 530 | 20670 | 1076 (2)| | 3 | TABLE ACCESS FULL | A1 | 530 | 6890 | 5 (20)| | 4 | VIEW PUSHED PREDICATE | | 1 | 26 | | |* 5 | INDEX RANGE SCAN | AA_ID | 5 | 65 | 2 (50)| ----------------------------------------------------------------------------- Predicate Information (identified by operation id): 5 - access("A"."ID"="B"."X") 第 4 步的 VIEW PUSHED PREDICATE,是将本来在外圈查询中的条件推入里圈,因此,第 5 步 的索引扫描才能只扫描符合"A"."ID"="B"."X"条件的行。 标量子查询的简单例子,化外连接为标量子查询: sid=10 pid=11> select a.id,b.x from aa a,a1 b where a.id(+)=b.x; -- 外连接 Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE 1 0 NESTED LOOPS (OUTER) 2 1 TABLE ACCESS (FULL) OF 'A1' 3 1 INDEX (RANGE SCAN) OF 'AA_ID' (NON-UNIQUE) 151 consistent gets 100 rows processed sid=10 pid=11> select x,(select id from aa where aa.id=a1.x) from a1; -- 标量子 查询 Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE 1 0 TABLE ACCESS (FULL) OF 'A1' Statistics ---------------------------------------------------------- 149 consistent gets 100 rows processed 标量子查询比外连接少了两个 LIO(逻辑 IO),反复多次实验,结果相同。因为测试表 太小了,因此,标量子查询的优势没有完全发挥出来。在大表中,标量子查询 LIO 减少的非 常明显。但是,我们看不到标量子查询的执行计划,当然就更看不到标量子查询是如何与主 查询结合的。 9.4.2 Hints(提示) 使用 Hints,可以人为的更改生成器生成的执行路径,常用的 Hints 有: 1. /*+ALL_ROWS*/ 表明对语句块选择基于成本的优化方法,并获得最佳吞吐量,使资源消耗最小化. 例如: SELECT /*+ALL+_ROWS*/ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='SCOTT'; 2. /*+FIRST_ROWS*/ ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 207 表明对语句块选择基于成本的优化方法,并获得最佳响应时间,使资源消耗最小化. 例如: SELECT /*+FIRST_ROWS*/ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='SCOTT'; 3. /*+CHOOSE*/ 表明如果数据字典中有访问表的统计信息,将使用基于成本的优化方法,并获得最佳的吞吐 量;如果数据字典中没有访问表的统计信息,将基于规则的优化方法; 例如: SELECT /*+CHOOSE*/ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='SCOTT'; 4. /*+RULE*/ 表明对语句块选择基于规则的优化方法. 例如: SELECT /*+ RULE */ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='SCOTT'; 5. /*+FULL(TABLE)*/ 表明对表选择全局扫描的方法. 例如: SELECT /*+FULL(A)*/ EMP_NO,EMP_NAM FROM BSEMPMS A WHERE EMP_NO='SCOTT'; 6. /*+ROWID(TABLE)*/ 提示明确表明对指定表根据 ROWID 进行访问. 例如: SELECT /*+ROWID(BSEMPMS)*/ * FROM BSEMPMS WHERE ROWID>='AAAAAAAAAAAAAA' AND EMP_NO='SCOTT'; 7. /*+CLUSTER(TABLE)*/ 提示明确表明对指定表选择簇扫描的访问方法,它只对簇对象有效. 例如: SELECT /*+CLUSTER */ BSEMPMS.EMP_NO,DPT_NO FROM BSEMPMS,BSDPTMS WHERE DPT_NO='TEC304' AND BSEMPMS.DPT_NO=BSDPTMS.DPT_NO; 8. /*+INDEX(TABLE INDEX_NAME)*/ 表明对表选择索引的扫描方法. 例如: SELECT /*+INDEX(BSEMPMS SEX_INDEX) USE SEX_INDEX BECAUSE THERE ARE FEWMALE BSEMPMS */ FROM BSEMPMS WHERE SEX='M'; 9. /*+INDEX_ASC(TABLE INDEX_NAME)*/ 表明对表选择索引升序的扫描方法. 例如: SELECT /*+INDEX_ASC(BSEMPMS PK_BSEMPMS) */ FROM BSEMPMS WHERE DPT_NO='SCOTT'; 10. /*+INDEX_DESC(TABLE INDEX_NAME)*/ 表明对表选择索引降序的扫描方法. 例如: SELECT /*+INDEX_DESC(BSEMPMS PK_BSEMPMS) */ FROM BSEMPMS WHERE DPT_NO='SCOTT'; 11. /*+INDEX_FFS(TABLE INDEX_NAME)*/ 对指定的表执行快速全索引扫描,而不是全表扫描的办法. 例如: SELECT /*+INDEX_FFS(BSEMPMS IN_EMPNAM)*/ * FROM BSEMPMS WHERE DPT_NO='TEC305'; 12. /*+NOWRITE*/ ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 208 禁止对查询块的查询重写操作. 13. /*+REWRITE*/ 可以将视图作为参数. 14. /*+ORDERED*/ 根据表出现在 FROM 中的顺序,ORDERED 使 ORACLE 依此顺序对其连接. 例如: SELECT /*+ORDERED*/ A.COL1,B.COL2,C.COL3 FROM TABLE1 A,TABLE2 B,TABLE3 C WHERE A.COL1=B.COL1 AND B.COL1=C.COL1; 15. /*+USE_NL(TABLE)*/ 将指定表与嵌套的连接的行源进行连接,并把指定表作为内部表. 例如: SELECT /*+ORDERED USE_NL(BSEMPMS)*/ SDPTMS.DPT_NO,BSEMPMS.EMP_NO,BSEMPMS.EMP_NAM FROM BSEMPMS,BSDPTMS WHERE BSEMPMS.DPT_NO=BSDPTMS.DPT_NO; 16. /*+USE_MERGE(TABLE)*/ 将指定的表与其他行源通过合并排序连接方式连接起来. 例如: SELECT /*+USE_MERGE(BSEMPMS,BSDPTMS)*/ * FROM BSEMPMS,BSDPTMS WHERE BSEMPMS.DPT_NO=BSDPTMS.DPT_NO; 17. /*+USE_HASH(TABLE)*/ 将指定的表与其他行源通过哈希连接方式连接起来. 例如: SELECT /*+USE_HASH(BSEMPMS,BSDPTMS)*/ * FROM BSEMPMS,BSDPTMS WHERE BSEMPMS.DPT_NO=BSDPTMS.DPT_NO; 18. /*+CACHE(TABLE)*/ 当进行全表扫描时,CACHE 提示能够将表的检索块放置在缓冲区缓存中最近最少列表 LRU 的 最近使用端 例如: SELECT /*+FULL(BSEMPMS) CAHE(BSEMPMS) */ EMP_NAM FROM BSEMPMS; 19. /*+NOCACHE(TABLE)*/ 当进行全表扫描时,CACHE 提示能够将表的检索块放置在缓冲区缓存中最近最少列表 LRU 的 最近使用端 例如: SELECT /*+FULL(BSEMPMS) NOCAHE(BSEMPMS) */ EMP_NAM FROM BSEMPMS; 20. /*+APPEND*/ 直接插入到表的最后,可以提高速度. insert /*+append*/ into test1 select * from test4; 21. /*+NOAPPEND*/ 通过在插入语句生存期内停止并行模式来启动常规插入. insert /*+noappend*/ into test1 select * from test4 ; ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 209 9.5 大纲 9.5.1 什么是大纲、大纲的作用、大纲如何才能被使用 大纲,又叫存储大纲。它提供一种手段,可以跨越 Oracle 版本、数据库改变或者其他 可能导致执行计划改变的因素,使用某条语句的执行计划保持不变。具体来讲,当下列影响 SQL 语句执行计划的因素发生改变时,存储大纲可以使 SQL 语句的执行计划保持不变:  新的 Oracle 版本  收集对象资料  初始化参数的改变  数据库的重组  方案的改变  新建索引 大纲如何能使当外部环境变化时,SQL 语句的执行计划不变?方法很简单,在创建某条 语句的大纲时,将 SQL 语句的文本、执行计划存贮起来,这里当然是存到数据字典中。ORACLE 默认会创建一个 OUTLN 用户,在此用户中只有三个表 OL$、OL$HINTS、OL$NODES,这三个表 中就存贮了 SQL 语句的文本、执行计划和语句中所使用的 HINTS。一条语句的这些信息,就 叫做这条语句的大纲。 当用户发出SQL语句后,先在outln.ol$中查找有无此语句。如果有的话,就到OL$HINTS、 OL$NODES 中取出执行计划开始执行。无论外部环境如何变化,大纲的执行计划不会变化, 因此用户发出语句后,语句的执行计划也不会变化。大纲的原理很简单,我们就说到这里, 下面看看如何创建大纲。 第 五 节 大纲  什么是大纲、大纲的作用、大纲如何才能被使用  创建大纲  大纲的使用  维护大纲 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 210 9.5.2 创建大纲 创建大纲有两种方法,方法 1: create or replace outline 大纲名 for category 类别名 on SQL 语句 大纲的类别是为了方便使用、管理,每一个大纲都必须属于一个类别。你可以将初始化 参数 use_stored_outlines 设为指定类别,让此类别中的大纲有效,而让其他类别中的大纲 无效。ORACLE 中还有一个缺省类别,如果 use_stored_outlines 参数的值为 TRUE,则缺省 类别中的大纲有效,其他各种类别的大纲均无效。具体的实验,我们马上就做。下面再来看 看另一种创建大纲的方法:使用 Create_stored_outlines 参数。 设置 Create_stored_outlines 初始化参数为类别名,然后,ORACLE 将自动为所有执行 过的 SQL 语句创建大纲。直到 Create_stored_outlines 参数被设置为 FALSE 为止。也可以 将 Create_stored_outlines 设为 TRUE,然后发布的所有 SQL 语句,ORACLE 将在缺省类别中 为它们创建大纲。 下面我使用方法 1 创建一个语句的大纲(注意,Object_id 列没有索引),并实验一下 大纲的使用: 步 1:创建大纲 SQL> create or replace outline t1_lx for category lx on select * from t1 where object_id=:x; 大纲已创建。 创建大纲其实就是把 SQL 语句的文本和它的执行计划存储在数据字典中,在上例中 SQL 语句“select * from t1 where object_id=:x”本身和其执行计划都被存储了起来,它的 大纲的类别名为 T1_LX。注意当前 T1 表的 OBJECT_ID 列没有索引,此时存储进大纲中的执 行计划是全表扫描。下面为 OBJECT_ID 列创建索引: SQL> create index t1_id on t1(object_id); 索引已创建。 步 2:下面创建绑定变量:x,并为它赋值: SQL> var x number; SQL> exec :x:=100; PL/SQL 过程已成功完成。 步 3:开启 SQL*Plus Autotrace,查看所执行语句的执行计划: SQL> set autot trace exp SQL> set linesize 300 SQL> select * from t1 where object_id=:x; 执行计划 ---------------------------------------------------------- Plan hash value: 2288890262 ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 177 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 177 | 2 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | T1_ID | 1 | | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------- Predicate Information (identified by operation id): ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 211 --------------------------------------------------- 2 - access("OBJECT_ID"=TO_NUMBER(:X)) Note ----- - dynamic sampling used for this statement 可以看到,当有索引后,执行计划是 INDEX RANGE SCAN。 步 4:开启大纲 SQL> alter system set use_stored_outlines=lx; 系统已更改。 步 5:再次执行相同的语句: SQL> select * from t1 where object_id=:x; 执行计划 ---------------------------------------------------------- Plan hash value: 3617692013 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 3 | 531 | 3 (0)| 00:00:01 | |* 1 | TABLE ACCESS FULL| T1 | 3 | 531 | 3 (0)| 00:00:01 | -------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("OBJECT_ID"=TO_NUMBER(:X)) Note ----- - outline "T1_LX" used for this statement 执行计划变为了大纲中的全表扫描。 要想存储大纲中的执行计划被使用,用户执行的 SQL 语句必须和大纲中 SQL 语句的文本 百分百的匹配。如果我执行如下的语句,将不会使用大纲中的执行计划,而是会重新生成语 句的执行计划: SQL> select * from t1 where object_id=100; 执行计划 ---------------------------------------------------------- Plan hash value: 2288890262 ------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 177 | 1 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 177 | 1 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | T1_ID | 1 | | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------- Predicate Information (identified by operation id): ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 212 --------------------------------------------------- 2 - access("OBJECT_ID"=100) Note ----- - dynamic sampling used for this statement 此语句和大纲中的 SQL 语句只有一点差别,就是语句尾条件中的值。大纲中的是绑定变 量,而此条语句中的数字常量。此语句新生成的执行计划是索引扫描。 在上面的例子中,如果想禁用大纲,只需将 use_stored_outlines 参数设为 False,或 设为其他类别的名字: SQL> alter session set use_stored_outlines=false; 会话已更改。 SQL> select * from t1 where object_id=:x; 执行计划 ---------------------------------------------------------- Plan hash value: 2288890262 ------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 73 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 73 | 2 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | T1_ID | 1 | | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("OBJECT_ID"=TO_NUMBER(:X)) 初始化参数use_stored_outlines、create_stored_outlines即可以在SYSTEM级修改, 也可以在 SESSION 修改。 9.5.3 大纲的使用 如果想要修改大纲中的执行计划,但同时又不会影响到其他用户正常的使用。可以使用 私有大纲。私有大纲的创建、使用和公有大纲相同。下面我们实验一下私有大纲的使用。 步 1:在当前会话中关掉 LX 类别大纲的使用: SQL> alter session set use_stored_outlines=false; 会话已更改。 步 2:创建私有大纲 私有大纲的信息也需要保存到表中。在 SYSTEM 方案中,有一组基于会话的临时表:OL$、 OL$HINTS 和 OL$NODES,每个用户的私有大纲信息,都是保存在这组临时表中。下面我们创 建一个私有大纲: SQL> create or replace private outline p1 on select * from t1 where object_id=:x; 大纲已创建。 因为此时 OBJECT_ID 列已经有了索引,因此,在此步中创建的私有大纲 P1,它的执行 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 213 计划是索引扫描。私有大纲的计划,只存在于当前用户中,其他用户是看不到的。下面我们 实验一下私有大纲的效果。 步 3:使用 use_private_outlines 参数实验一下私有大纲的效果: SQL> alter session set use_private_outlines=true; 会话已更改。 将 use_private_outlines 参数设为 TRUE,将使用本地用户的私有大纲中的执行计划。 我使用的是 alter session 命令修改的此参数,因此,即使是同一会话,其他会话仍然会使 用公有大纲。也就是说,我们在当前会话中创建的私有大纲不会影响其他会话的使用。 下面,看看在当前会话中,大纲中的执行计划: SQL> set autot on exp; SQL> select * from t1 where object_id=:x; 未选定行 执行计划 ---------------------------------------------------------- Plan hash value: 2288890262 ------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 3 | 531 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T1 | 3 | 531 | 2 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | T1_ID | 1 | | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("OBJECT_ID"=TO_NUMBER(:X)) Note ----- - outline "P1" used for this statement 私有大纲中的执行计划是索引扫描。如果对这个执行计划已经满意了,可以用此私有大 纲覆盖公有大纲。 步 4:用私有大纲覆盖公有大纲 SQL> create or replace outline t1_lx from private p1 for category lx; 大纲已创建。 注意“for category lx”子句一定要放在句尾,不能像以前那样放在“outline t1_lx” 之后。如果没有此子句,重建后的公有大纲 T1_LX 的类别将是 DEFAULT,不在是 LX。 步 5:关闭私有大纲,打开公有大纲,再次进行检测: SQL> alter session set use_private_outlines=false; 会话已更改。 SQL> alter session set use_stored_outlines=lx; 会话已更改。 SQL> set autot on exp SQL> select * from t1 where object_id=:x; 未选定行 执行计划 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 214 ---------------------------------------------------------- Plan hash value: 2288890262 ------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 3 | 531 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T1 | 3 | 531 | 2 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | T1_ID | 1 | | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("OBJECT_ID"=TO_NUMBER(:X)) Note ----- - outline "T1_LX" used for this statement 9.5.4 维护大纲 如果要修改大纲的类别,或者重建、删除大纲等等这些维护性操作都是由 Alter outline public|private 命令完成的。使用此命令,我们完成如下操作: 1.改变大纲类别 SQL> alter outline t1_lx change category to t1; 大纲已变更。 类别名 T1 可以和表名相重,也可以不存在,ORACLE 将自动新建一个类别。 2.大纲改名 SQL> alter outline t1_lx rename to ol1_t1; 大纲已变更。 3.禁用大纲 SQL> alter outline ol1_t1 disable; 大纲已变更。 4.激活大纲 SQL> alter outline ol1_t1 enable; 大纲已变更。 5.重建大纲 SQL> alter outline ol1_t1 rebuild; 大纲已变更。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 215 9.6 诊断工具 9.6.1 Statspack 报告 Statspack 报告中有很多关于 SQL 的内容。常用的有如下 6 部分,我们分别介绍一下。 在 spreport.sql 中,有一行 define top_n_sql = 65; ,它限制了这里最多只能显示 65 条 SQL。如果数据库并不繁忙,很可能显示不到 65 条。 用于记录 SQL 信息的 Stats 表是 stats$sql_summary 和 stats$sqltext。主要的资料, 如 Buffer gets、物理读、执行次数、解析次数等信息都是在 Statas$sql_summary 中,有 几个门限列,限制进入此视图中的 SQL 语句,将在下面介绍。 1.按 Buffer gets 排序的 SQL 声明: SQL ordered by Gets for DB: MYTWO Instance: mytwo Snaps: 1 -2 -> End Buffer Gets Threshold: 10000(得到的 Buffer 数量超过 10000 的 SQL,将被列 在下面) -> Note that resources reported for PL/SQL includes the resources used by all SQL statements called within the PL/SQL code. As individual SQL statements are also reported, it is possible and valid for the summed total % to exceed 100 上面的这段英文是: -> 注意 PL/SQL 的报告包含 PL/SQL 程序中所有 SQL 声明所使用的资源,而单独的 SQL 声明 也有可能被写进报告,因此 Total %的总和超过 100 是可能但仍是有效的。 有关 Buffer gets 的报告有 7 列,现摘录部分内容如下: CPU Elapsd Buffer Gets Executions Gets per Exec %Total Time (s) Time (s) Hash 第 六 节 诊断工具 常用的诊断工具有:  Statspack 报告  Explain plan for 工具  SQL_Trace  SQL*Plus autotrace ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 216 Value ----------------- -------------- -------------- ----------- ---------- 110,987 1 110,987.0 96.9 5.68 7.56 2032889471 Module: SQL*Plus select id,myid,owner from big_table (省略其余内容„„„„) Buffer Gets:在两快照间声明所完成的逻辑读。 Executions:两快照间声明的执行次数。 Gets per Exec:前两列相除,两快照间每次执行所完成的逻辑读。Buffer Gets / Executions 。 %Total:此声明的逻辑读在总的逻辑读中所占的比例。计算公式:Buffer Gets / 两快照间 的总逻辑读。此处总逻辑读来源于资料视图中的 session logical reads 资料。 CPU Time (s):两快照间声明所耗 CPU 时间,单位:秒。 Elapsd Time(s):两快照间声明的墙上壁钟时间,单位:秒。 Hash Value:声明的 Hash 值。 2.按 Physical Reads 排序的 SQL 声明 SQL ordered by Reads for DB: MYTWO Instance: mytwo Snaps: 1 -2 -> End Disk Reads Threshold: 1000(物理读超过 1000 的 SQL,将被列在下面) CPU Elapsd Physical Reads Executions Reads per Exec %Total Time (s) Time (s) Hash Value --------------- --------------- -------------- --------- 2 2 1.0 9.1 0.00 0.01 1428100621 select /*+ index(idl_ub2$ i_idl_ub21) +*/ piece#,length,piece fr om idl_ub2$ where obj#=:1 and part=:2 and version=:3 order by piece# Physical Reads:两快照间声明所完成的物理读。 Executions:两快照间声明的执行次数。 Reads per Exec:前两列相除,两快照间每次执行所完成的物理读。Physical Reads / Executions 。 %Total:此声明的物理读在总的物理读中所占的比例。计算公式:Physical Reads / 两快 照间的总物理读。此处总物理读来源于资料视图中的 physical reads 资料。 CPU Time (s):两快照间声明所耗 CPU 时间,单位:秒。 Elapsd Time(s):两快照间声明的墙上壁钟时间,单位:秒。 Hash Value:声明的 Hash 值。 3.按 Executions 排序的 SQL 声明 SQL ordered by Executions for DB: MYTWO Instance: mytwo Snaps: 1 -2 -> End Executions Threshold: 100(执行次数超过 100 的声明,将被列在下面) Executions:两快照间声明的执行次数。 Rows Processed:两快照间声明执行所操作的行数。此行数是结果行数,不是表行数。如果 从一个千万行的表中选一行,此列是 1。 Rows per Exec:上两行相除,每次执行操作多少行。 CPU per Exec (s):两快照间声明所耗 CPU 时间 / Executions ,每次执行所耗 CPU 时间。 Elap per Exec (s):两快照间声明的墙上壁钟时间 / Executions ,每次执行所耗墙上壁 钟时间。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 217 Hash Value:声明的 Hash 值。 4.按 Parse Calls 排序 SQL ordered by Parse Calls for DB: MYTWO Instance: mytwo Snaps: 1 -2 -> End Parse Calls Threshold: 1000(解析次数大于 1000 的 SQL 声明被列在下面) Parse Calls:两快照间的解析次数 Executions:两快照间的执行次数 % Total Parses:Parse Calls / parse count (total) ,两快照间的解析次数 / 两快照 间的总解析次数。此声明的解析次数在总解析次数中的比例。 Hash Value:Hash 值。 5.按声明的 Sharable Memory 排序: SQL ordered by Sharable Memory for DB: MYTWO Instance: mytwo Snaps: 1 -2 -> End Sharable Memory Threshold: 1048576 Sharable Mem (b):快照 2 时,声明所消耗的共享池内存。 Executions:两快照间声明的执行次数。 % Total:快照 2 时声明所耗共享池内存 / 快照 2 时共享池内存总量 Hash Value:声明的 Hash 值。 6.按声明 Version Count 排序 SQL ordered by Version Count for DB: MYTWO Instance: mytwo Snaps: 1 -2 -> End Version Count Threshold: 20 Version Count:快照 2 时声明子游标数量。 Executions:两快照间声明的执行次数。 Hash Value:声明 Hash 值。 9.6.2 Explain plan for 工具 步 1:先要创建执行计划表 PLAN_TABLE。 SQL> @?\rdbms\admin\utlxplan 表已创建。 在 10G 中此步可以省略,因为 10G 中有一个公有同义词 PLAN_TABLE,它是 SYS 中表 PLAN_TABLE$的同义词。每个用户都可以访问 PLAN_TABLE$表,它是一个基于会话的临时表。 如果你希望执行计划长期的保留在 PLAN_TABLE 中,就必须调用上面的脚本,在用户中创建 一个永久表 PLAN_TABLE。 步 2: SQL> explain plan for select * from t1 where object_id=:x; 已解释。 步 3:查看执行计划: SQL> @?\rdbms\admin\utlxpls PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------------------- Plan hash value: 3617692013 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 73 | 3 (0)| 00:00:01 | |* 1 | TABLE ACCESS FULL| T1 | 1 | 73 | 3 (0)| 00:00:01 | ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 218 -------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------------------- 1 - filter("OBJECT_ID"=TO_NUMBER(:X)) Note ----- - outline "T1_LX" used for this statement 已选择 17 行。 9.6.3 SQL_Trace SQL_Trace 就是 SQL 跟踪,这个我们在前面已经详细讲过了,在这里再说一下注意事项: 1.跟踪文件最大大小: MAX_DUMP_FILE_SIZE 参数,如果指定 K 和 M,此参数的单位是 K、M 字节。如果没有单 位,此参数的值将是操作系统块。 2.跟踪其他会话: Dbms_system.set_sql_trace_in_session(会话 ID,序列号,True|False) 9.6.4 SQL*Plus autotrace 具有 DBA 角色的用户可以直接使用。没有 DBA 角色的用户,可以先执行下面的脚本: SQL> @?\sqlplus\admin\plustrce 此脚本内容如下: set echo on drop role plustrace; create role plustrace; grant select on v_$sesstat to plustrace; grant select on v_$statname to plustrace; grant select on v_$mystat to plustrace; grant plustrace to dba with admin option; set echo off 它将创建一个 plustrace 角色,将授于它显示 v_$sesstat、v_$statname、v_$mystat 这三个表的对象权限。将此角色授于没有 DBA 角色的用户,用户就可以使用 Autotrace 了。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 219 附录: 1.SHOW_PLAN.SQL脚本如下: explain plan for &st; @?/rdbms/admin/utlxpls ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 220 第 10 章 资料收集 您将学习: 1. 什么是资料 2. 柱状图 3. 段层资料 4. Analyze 第 十 章 资料收集 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 221 10.1 什么是资料 10.1.1 什么是资料 在优化器为 SQL 语句生成计划时,优化器必须了解表有多少行、占多少个块,索引占多 少个块,还有如果 SQL 语句中有条件如“姓名=某某某”,那么这个条件大概会选出多少行, 等等信息。这些信息就是资料。 资料对于优化器生成更优化的执行计划,是非常重要的。比如我的 SQL 语句如下: Select * form emp where name= '张三' 假设在 name 列上有索引,如果资料中显示,name 列中重复值太多,name=’张三’的 可能有很多行。通过一个条件选择了来的行在总行数的 10%以上时,使用索引将不会有性能 的提升(我们在前面讲过,考虑为什么?),优化器将会选择全表扫描来完成语句。如果资 料中显示,NAME 列重复值很少,那么,name= '张三'有可能只会选择出很少的行,这时优 化器将会选择索引扫描来执行语句。一个列重复值的多少,ORACLE 称为选择性(或者称为 可选择性),重复值越少,选择性就越高。对于选择性高的列,如果有索引的话,ORACLE 尽 量会选择索引做为访问路径。相反,选择性比较低,重复值多,优化器将会使用全扫描为访 问路径。 选择性就是列资料中很重要的一项。除了选择性外,表的大小、索引大小、平均的行长 度等等都是优化器最常用的资料。 资料被收集到一些数据字典表中。具体如下:  表资料在 DBA_TABLES 中  索引资料在 DBA_INDEXES 中 第 一 节 什么是资料  什么是资料  资料的收集与查看  动态采样  系统资料  资料的导入、导出 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 222  列资料在 DBA_TAB_COL_STATISTICS、DBA_TAB_COLS、DBA_TAB_COLUMNS 下面,我们从表资料的收集、查看开始,讲述一下资料的收集、查看和管理。 10.1.2 资料的收集与查看 1.DBMS_STATS.GATHER_TABLE_STATS 最常用的收集资料命令是 DBMS_STATS 包中的函数 GATHER_TABLE_STATS,它可用于收集 表、列(和索引)的资料。我们可以在文档“PL/SQL Packages and Types Reference”中, 找到关于此函数的完整的使用说明。下面我们介绍一下此函数比较重要的参数:  ownname:表的所有者  tabname:表名  partname:分区名 最简单的收集表资料的命令如下: SQL> exec dbms_stats.gather_table_stats('u2','t1'); PL/SQL 过程已成功完成。 上面的命令将会收集表、列、以及表所有相关索引的资料。我们先来看看它收集的表资 料。 查看文档,在 DBA_TABLES 视图中,所有列名后加*号的列,都是存贮的资料信息。下面, 我查看这些列,看一看 T1 表的资料: SQL> select table_name,num_rows,blocks,empty_blocks,avg_space,chain_cnt,avg_row_len from user_tables where table_name='T1'; TABLE_NAME NUM_ROWS BLOCKS EMPTY_BLOCKS AVG_SPACE CHAIN_CNT AVG_ROW_LEN --------------- ---------- ---------- ------------ ---------- ---------- ----------- T1 10 4 0 0 0 73 根据上面的资料显示,T1 表共有 10 行,共占 4 个块,每行长度 73 字节。 SQL> select * from user_TAB_COL_STATISTICS; TABLE_NAME COLUMN_NAME NUM_DISTINCT LOW_VALUE ----------- ------------------------- --------------------------------------------------- T1 SECONDARY 1 4E T1 GENERATED 1 4E T1 TEMPORARY 1 4E T1 STATUS 1 56414C4944 T1 TIMESTAMP 2 323030352D30382D33303 T1 LAST_DDL_TIME 4 7869081E0E3319 (省略部分列和行) 默认情况下,gather_table_stats 也会收集所有列的资料,这些资料包括列的选择性、 最小值、最大值等等。这些资料在 DBA_TAB_COLS、DBA_TAB_COLUMNS 中也可以看到,和这里 显示的将是一模一样的。 下面再看看索引资料,gather_table_stats 默认将会收集表所有相关索引的资料: SQL> select index_name,blevel,leaf_blocks,distinct_keys,avg_leaf_blocks_per_key, avg_data_blocks_per_key,clustering_factor from user_indexes; INDEX_NAME BLEVEL LEAF_BLOCKS DISTINCT_KEYS AVG_LEAF_BLOCKS_PER_KEY AVG_DATA_BLOCKS_PER_KEY CLUSTERING_FACTOR ---------- ---------- ----------- ------------- ----------------------- ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 223 T1_ID 0 1 10 1 1 1 从这些资料可以看到,T1 表只有一个索引 T1_ID,它只有一个索引块。 这些资料的具体意义我就不再解释了。通常这些资料也不是让我们 DBA 看的,只给优化 器看到。DBA 所要做的,就是当表发生了很大的变化后,要即时的重新收集资料,以便优化 器可以生成最化的执行计划。 下面再来看此命令的一个参数:  method_opt:它的值为:FOR COLUMNS [size clause] column 我们可以使用它只收集表资料、索引资料和只定列而不是所有列的资料。在做此操作前 我先删除刚上收集的表资料: SQL> exec dbms_stats.delete_table_stats('u2','t1'); PL/SQL 过程已成功完成。 删除后再查看表、列、索引资料,都已为空。 SQL> select * from user_TAB_COL_STATISTICS; 未选定行 SQL> select index_name,blevel,leaf_blocks,distinct_keys,avg_leaf_blocks INDEX_NAME BLEVEL LEAF_BLOCKS DISTINCT_KEYS AVG_LEAF_BLOCKS_PER_KEY ---------- ---------- ----------- ------------- ----------------------- T1_ID 下面,我只收集 OBJECT_ID 列上的资料: SQL> exec dbms_stats.gather_table_stats('u2','t1',method_opt=>'for columns size 1 object_id'); PL/SQL 过程已成功完成。 显示 user_TAB_COL_STATISTICS 视图,只能看到一个列的资料: SQL> select * from user_TAB_COL_STATISTICS; TABLE_NAME COLUMN_NAME NUM_DISTINCT LOW_VALUE --------------- ------------------------------ ------------ -------------- T1 OBJECT_ID 10 C104 这个命令的 SIZE 中的数字,和柱状图有关,我们马上就是讲到了。 如下命令,将收集所有创建了索引的列: SQL> exec dbms_stats.gather_table_stats('u2','t1',method_opt=>'for all indexed columns size 1'); PL/SQL 过程已成功完成。 我们在收集表资料时,应当使用此选项,只收集常用列的资料。从来不会做为条件的列, 收集它们的资料只会浪费时间。或者,如果某一列发化了很大的变化,而其他列并没有太大 的变化,那么,我们可以只对发生了很大变化列收集资料。总之,在使用 gather_table_stats 收集表资料时,我们应该意识到,它级联的收集所有列、所有索引的资料,你应该考虑一下, 是否有必要这样做。 Oracle 中不提供单独的收集列的命令,要收集列的资料,必须使用 Gather_table_stats。 对于索引的资料,Oracle 另行提供的有专门收集索引资料的命令。因此,我们可以使用 DBMS_STATS 包中的一个函数,关闭级联收集索引资料: SQL> exec dbms_stats.set_param('cascade','false'); PL/SQL 过程已成功完成。 以上命令,设置“级联索引”为假,再使用 gather_table_stats 收集表资料时,并不 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 224 会收集索引资料。 如果表比较大的时候,我们可以像统计学那样,只对部分样本收集资料。只要样本的大 小调制的合理,这样收集的资料和精确收集时的结果相差无几,这种少量的差别并不会影响 优化器选择最优的执行计划。而且,这样做将有利于提高收集资料完成的速度。 样本收集也叫估计资料,有两种样本模式:  行样本(Row sampling):仅仅读取行,而不关心它们在磁盘上的分布。它可能读取的 数据较多,但这种方式更为随机。在最坏的情况下,行样本可能从每一个块中读取一行, 从而进行全表扫描。这样一来,收集资料所需的物理读和精确收集就一模一样了。  块样本(Block sampling)(不能用于索引):随机选择一些块,从中读取全部的行进行 样本评估。在样本大小相同的情况下,块样本的总的 I/O 更少。但是,如果行并不太随 机的分布在磁盘上,块样本降低的样本的随机性。 在 Gather_table_stats 函数中,有两个参数与估计资料有关:  estimate_percent:评估时行的百分比(为空意味着 Compute)。值是[0.000001,100] 之间。使用 DBMS_STATS.AUTO_SAMPLE_SIZE 让 Oracle 测定最佳的样本大小。  block_sample:是否使用随机块样本替代随机行样本。随机块样本收集速度更快,这一 点前面已经提到过了。如果数据在磁盘上分布的不太随机,块样本的代表性可能不是太 好。此选项仅适用于“estimate statistics” 下面的语句随机抽取 50%的行收集资料,默认收集模式为“行样本”: SQL> exec dbms_stats.gather_table_stats('u2','t1',method_opt=>'for all indexed columns size 1',estimate_percent=>50); PL/SQL 过程已成功完成。 如果希望使用块样本: SQL> exec dbms_stats.gather_table_stats('u2','t1',method_opt=>'for all indexed columns size 1',estimate_percent=>50,block_sample=>true); PL/SQL 过程已成功完成。 需要注意的是,ORACLE 并不建议使用“块样本”模式。 另外,如果我们没有把握选择一个好的比例时,可以让 Oracle 自动计算比例: SQL> exec dbms_stats.gather_table_stats('u2','t1', method_opt=>'for all indexed columns size 1', estimate_percent=>DBMS_STATS.AUTO_SAMPLE_SIZE,block_sample=>true); PL/SQL 过程已成功完成。 将 estimate_percent 参数的值定为 DBMS_STATS.AUTO_SAMPLE_SIZE,就是让 Oracle 自 动计算样本大小资料。 最后还有一个参数比较有用:  degree:并行度。为空意味着使用表的并行度。使用常量 DBMS_STATS.DEFAULT_DEGREE 基于初始化参数指定并行度。 当不指定此参数时,收集资料时默认的并行度和表的并行度一样。我们可以通过此参数 自己指定并行度。这个很简单,我就不再试了。有点说明一下,在级联收集索引资料时,是 无法并行收集的。 关于 GATHER_TABLE_STATS 的使用,我们就说到这里,下面看看如何只收集列或索引资 料。 注:附上 GATHER_TABLE_STATS 所有参数即意义: DBMS_STATS.GATHER_TABLE_STATS ( ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 225 ownname VARCHAR2, tabname VARCHAR2, partname VARCHAR2 DEFAULT NULL, estimate_percent NUMBER DEFAULT NULL, block_sample BOOLEAN DEFAULT FALSE, method_opt VARCHAR2 DEFAULT 'FOR ALL COLUMNS SIZE 1', degree NUMBER DEFAULT NULL, granularity VARCHAR2 DEFAULT 'DEFAULT', cascade BOOLEAN DEFAULT FALSE, stattab VARCHAR2 DEFAULT NULL, statid VARCHAR2 DEFAULT NULL, statown VARCHAR2 DEFAULT NULL, no_invalidate BOOLEAN DEFAULT FALSE); 参数介绍: ownname:表的所有者 tabname:表名 partname:分区名 estimate_percent:评估时行的百分比(为空意味着 Compute)。值是[0.000001,100]之间。 使用 DBMS_STATS.AUTO_SAMPLE_SIZE 让 Oracle 测定最佳的样本大小。 block_sample:是否使用随机块样本替代随机行样本。随机块样本收集速度更快,这一 点前面已经提到过了。如果数据在磁盘上分布的不太随机,样本的代表性可能不是太好。此 选项仅适用于“estimate statistics” method_opt:可接受:FOR ALL [INDEXED | HIDDEN] COLUMNS [size_clause] FOR COLUMNS [size clause] column|attribute [size_clause] [,column|attribute [size_clause]...], 此处的大小子句被定义为:size_clause := SIZE {integer | REPEAT | AUTO | SKEWONLY} 。 其中,integer—柱状图桶的数量。必须在[1,254]之间。REPEAT—只对已经收集柱状图的列 收集资料。AUTO--Oracle 基于数据分布和列的工作量,自动决定对哪列收集柱状图资料。 SKEWONLY--Oracle 基于列数据的分布决定要收集柱状图资料的列。 degree :并行度。为空意味着使用表 的 并 行 度 。 使 用 常 量 DBMS_STATS.DEFAULT_DEGREE 基于初始化参数指定并行度。 granularity:资料收集的粒度(只适用于分区表),可以取如下几种值:DEFAULT:收 集全局、分区资料。SUBPARTITION:收集子分区层资料。PARTITION:收集分区层资料。 GLOBAL:收集全局资料。ALL:收集所有相关分区的资料(subpartition, partition, and global)。 cascade:收集表的所有相关索引的资料。索引资料的收集不能并行。使用这个选项, 相当于对表的所有索引运行 GATHER_INDEX_STATS 过程。 stattab:用户资料表标识,指定保存当前资料的位置。( User stat table identifier describing where to save the current statistics) statid:用户资料表 ID。 Identifier (optional) to associate with these statistics within stattab. statown:用户资料表的所有者。 Schema containing stattab (if different than ownname). no_invalidate:如果此参数被设置为真,依赖的游标不会无效。 When the 'cascade' argument is specified, this parameter is not relevant with certain types of indexes, as described in "GATHER_INDEX_STATS Procedure". 2.只收集索引资料 DBMS_STATS 包的 GATHER_INDEX_STATS 函数,只收集索引的资料。它的参数和 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 226 GATHER_TABLE_STATS 基本相同。只不过因为索引资料不能并行收集,因此它没有 degree 并行度参数。还有,在收集估计资料时,索引不参使用“块样本”模式。因此它没有 block_sample 参数。另外,它也没有了 method_opt 选项。 下面如果我新建了一个索引: SQL> CREATE INDEX t1_name ON t1(object_name); 收集此索引的语句如下: SQL> exec dbms_stats.gather_index_stats('u2','t1_name', estimate_percent=>DBMS_STATS.AUTO_SAMPLE_SIZE); PL/SQL 过程已成功完成。 索引资料的查看上面已经说过了,这里不再重复。 10.1.3 动态采样 当用户发出语句访问一个表时,如果此表没有收集过资料,Oracle 可以先估计表的资料, 再根据估计的资料生成执行计划,这被叫做动态采样。初始化参数 optimizer_dynamic_sampling 控制动态采样。当值为 0 时,不开启动态采样。当值大于 0 时, 就开启了动态采样,而且,此参数值越大,动态采样所耗时间越长、采样时样本越大。此参 数最大值为 10。9i 默认是不使用动态采样的,10G 中此参数的值默认值为 2,默认就启用了 动态采样。 通过 Explaint plan for、SQL*Plus Autotrace 等工具,都可以看到动态采样是否使用,下 面我们试一下: 步 1:删除表 T1 的资料: SQL> exec dbms_stats.delete_table_stats('u2','t1'); PL/SQL 过程已成功完成。 步 2:开启 Autotrace: SQL> set autot trace exp; 步 3:查询 T1 表: SQL> select * from t1; 执行计划 ---------------------------------------------------------- Plan hash value: 3617692013 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 10 | 1770 | 3 (0)| 00:00:01 | | 1 | TABLE ACCESS FULL| T1 | 10 | 1770 | 3 (0)| 00:00:01 | -------------------------------------------------------------------------- Note ----- - dynamic sampling used for this statement 在 Note 部分,显示此语句的执行计划是依据动态采样而得出的。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 227 10.1.4 系统资料 系统资料就是 CPU 的性能、I/O 速度等资料。收集这些资料,可以使优化器更加智能, 有助于生成更加合理、优化的执行计划。收集系统资料的命令非常简单,以下两种方法都可 以用来收集系统资料: 方法一: 步 1:开启系统资料收集 SQL> exec dbms_stats.gather_system_stats('START'); PL/SQL 过程已成功完成。 步 2:执行操作 步 3:结束系统资料收集 SQL> exec dbms_stats.gather_system_stats('STOP'); PL/SQL 过程已成功完成。 我们看到了,系统资料收集是非常简单的。系统资料被收集到了 aux_stats$数据字典表 中,我们可以查看一下: SQL> select * from aux_stats$; SNAME PNAME PVAL1 PVAL2 -------------------- ------------------------------ ---------- -------------------- SYSSTATS_INFO STATUS COMPLETED SYSSTATS_INFO DSTART 07-13-2008 10:32 SYSSTATS_INFO DSTOP 07-13-2008 10:33 SYSSTATS_INFO FLAGS 1 SYSSTATS_MAIN CPUSPEEDNW 484.974958 SYSSTATS_MAIN IOSEEKTIM 10 SYSSTATS_MAIN IOTFRSPEED 4096 SYSSTATS_MAIN SREADTIM 这其中 CPUSPEEDNW、IOSEEKTIM、IOTFRSPEED 等等,就是 CPU 和 I/O 的性能信 息。 还有一种方法也可以收集系统资料: 方法 2: SQL> exec dbms_stats.gather_system_stats('INTERVAL',interval=>60); PL/SQL 过程已成功完成。 上面语句的作用是,在 60 分钟内,收集系统资料。注意,在收集期间,我要运行最典 型的应用,保证 ORACLE 可以收集到在典型应用下,CPU、I/O 等设备的性能数据。 10.1.5 资料的导入、导出 收集好的资料可以导出、导入。比如我们可以导出生产库的资料,导入的测试库中。这 样,测试库有了生产库的资料,所生成的执行计划将更接近于生产库。 有时候,导出资料也可以当作备份资料。因为我们每收集一次新的资料,都将覆盖原有 资料。在开始收集资料前,我们可以将原有资料导出,这样可以起到备份原有资料的作用。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 228 如果新收集的资料一旦对数据库产生了不好的影响,我们可以将备份的原有资料再导入回去, 让数据库恢复到原来的状态。 好,不多说了,导出、导入资料很简单,我们直接通过一个例子来讲述。 步 1:创建资料表 SQL> exec dbms_stats.create_stat_table('u2','my_stat','users'); PL/SQL 过程已成功完成。 导出资料,就是把资料复制进用户自己创建的一个表中。上面的语句就是完成此功能的 命令。它的使用很简单,它有三个参数,第一个是资料表所在的用户名,第二个是资料表名, 第三个资料表所在的表空间。我们上面的语句在 U2 用户中,创建了一个 MY_STAT 资料表, 它在 USERS 表空间中。下面是它的一些列。 SQL> desc my_stat; 名称 ------------------- STATID TYPE VERSION FLAGS C1 ( 省略部分行 ) 这个表有很多列,它可以保存各种资料,如表、列、索引和系统资料。它的第一列 STATID 是一个标识。用来区别每次导入进 MY_STAT 的资料。好,下面,开始第二步,把资料导出 进 MY_STAT。 步 2:导出资料到 MY_STAT SQL> exec dbms_stats.export_table_stats('u2','t1',stattab=>'MY_STAT',statid=>'T1_0713'); PL/SQL 过程已成功完成。 这个命令也很简单,stattab 参数指定了目标资料表名。Statid 则是一个标识。我们可以 如下显示 MY_STAT 中的资料。 SQL> col CH1 for a10 SQL> select * from my_stat; STATID T VERSION FLAGS C1 ------------------------------ - ---------- ---------- -- T1_0713 T 4 2 T1 T1_0713 C 4 2 T1 T1_0713 C 4 2 T1 T1_0713 C 4 2 T1 ( 省略部分行、列 ) 已选择 13 行。 不必理会这些资料的意义,我显示 MY_STAT 的目的是验证资料已经成功导出进了 MY_STAT 表。而且,这次进入 MY_STAT 的资料,标识为 T1_0713。如果再次导出资料时, 标识也定为 T1_0713,将会覆盖这 13 行资料。另外,如果有需要,我们可以通过这个标识, 将 T1_0713 的资料重新导入回系统资料表。 步 3:导入资料到系统资料表 SQL> exec dbms_stats.import_table_stats('u2','t1',stattab=>'MY_STAT',statid=>'T1_0713'); PL/SQL 过程已成功完成。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 229 上面的语句作用是 MY_STAT 资料表中标识为 T1_0713 的资料导入回系统资料表。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 230 10.2 柱状图 10.2.1 柱状图简介 基于成本优化器的一个基本的任务,就是确定一个条件中某一列的选择性。复习一个名 词,选择性,它其实是指列中重复值的多少。 选择性决定了多表访问时连接时的顺序,还有什么时候使用该使用索引,什么时候使用 全扫描。而对于数据分布极为不平均的列,比如表中有 100 行,只有一行值为 1,其他 99 行值为 50,这就是数据分布极不平均。对于这样的列,它的选择性是不高的,因为有大量 的重复值。但是,分开来说,对于列值为 50 的行,选择性的确不高,因为它有 99 个重复值。 但对于列值为 1 的行,选择性是很高的,因为它没有重复。对于这样的列,其选择性的衡量, 最好是通过柱状图决定。 在创建柱状图中时一个重要选项,就是桶数(Bucket)。看下图: 第 二 节 柱状图  柱状图简介  为列创建柱状图  实验柱状图的作用  绑定变量窥视 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 231 这个柱状图中的柱子,就是桶。现在我有两个桶,设一个桶的范围是 150,此桶只有 一个值,值为 1。另一个桶范围是 51100,此桶中有 99 个值。像这样典型的少量值在一个 范围内,而有大量值在另外范围内,这们就称为 skewed data distribution,直译就是倾斜数据 分布。意思就是数据分布不平均。如果某一列数据倾斜分布的越厉害,单一的一个选择性根 本无法代表数据分布的全部,如果想让优化器产生更好的执行计划,只能通过柱状图。下面 我们来看一下如何创建柱状图。 10.2.2 为列创建柱状图 创建柱状图命令非常简单: SQL> EXECUTE DBMS_STATS.GATHER_TABLE_STATS ('scott','aa', METHOD_OPT => 'FOR COLUMNS SIZE 10 OBJECT_ID'); SIZE 10:创建 11 个桶。也可以不指定桶个数,让 Oracle 自行统计:SIZE AUTO。如 下例: SQL> EXECUTE DBMS_STATS.GATHER_TABLE_STATS ('scott','aa',METHOD_OPT => 'FOR COLUMNS SIZE AUTO OBJECT_ID'); 如果不指定 SIZE 项,默认的桶数量是 75。 10.2.3 实验柱状图的作用 步 1:将 AA 表的 ID 列设置成 skewed data distribution sid=12 pid=12> update aa set id=3 where zw=1; 已更新 1 行。 sid=12 pid=12> update aa set id=5 where zw<>1; 已更新 30 行。 sid=12 pid=12> commit; 提交完成。 sid=12 pid=12> EXECUTE DBMS_STATS.GATHER_TABLE_STATS('scott','aa'); PL/SQL 过程已成功完成。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 232 最后统计表资料虽然包括表、索引和列,但不包括柱状图。 步 2:发布 SQL 声明,查看执行计划: sid=12 pid=12> @show_plan 输入 msql 的值: select * from aa where id=3 ------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| ------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 16 | 96 | 5 (20)| |* 1 | TABLE ACCESS FULL | AA | 16 | 96 | 5 (20)| ------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("AA"."ID"=3) sid=12 pid=12> @show_plan 输入 msql 的值: select * from aa where id=5 ------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| ------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 16 | 96 | 5 (20)| |* 1 | TABLE ACCESS FULL | AA | 16 | 96 | 5 (20)| ------------------------------------------------------------------------- Predicate Information (identified by operation id): 1 - filter("AA"."ID"=5) 已选择 12 行。 可以看到,都是全表扫描。因为此时没有柱状图,优化器根据 ID 列的选择性非常低来 判断,全表扫描是最合适的执行路径。 步 3:收集柱状图资料: sid=12 pid=12> EXECUTE DBMS_STATS.GATHER_TABLE_STATS ('scott','aa',METHOD_OPT => 'FOR COLUMNS SIZE AUTO ID'); PL/SQL 过程已成功完成。 sid=12 pid=12> @show_plan 输入 msql 的值: select * from aa where id=5 ------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| ------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 30 | 180 | 5 (20)| |* 1 | TABLE ACCESS FULL | AA | 30 | 180 | 5 (20)| ------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("AA"."ID"=5) 已选择 12 行。 sid=12 pid=12> @show_plan 输入 msql 的值: select * from aa where id=3 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 233 -------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| -------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 6 | 3 (34)| | 1 | TABLE ACCESS BY INDEX ROWID| AA | 1 | 6 | 3 (34)| |* 2 | INDEX RANGE SCAN | AA_ID | 1 | | 2 (50)| -------------------------------------------------------------------------------- Predicate Information (identified by operation id): 2 - access("AA"."ID"=3) 谓词是 ID=3 时,优化器选择了索引范围扫描。但是,当使用绑定变量时,优化器将无 法正确使用柱状图。 sid=12 pid=12> var x number; sid=12 pid=12> exec :x:=3; sid=12 pid=12> @show_plan 输入 msql 的值: select * from aa where id=:x ------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| ------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 16 | 96 | 5 (20)| |* 1 | TABLE ACCESS FULL | AA | 16 | 96 | 5 (20)| ------------------------------------------------------------------------- Predicate Information (identified by operation id): 1 - filter("AA"."ID"=TO_NUMBER(:Z)) 其实柱状图本身并不是为 OLTP 准备的,它主要在数据仓库中使用。数据仓库环境中, 本身就很少使用绑定变量。 10.2.4 绑定变量窥视 Oracle 9iR1 的一个新特性是优化程序能够在第一次硬分析一个查询前窥视绑定变量值。 这表示优化程序将看到绑定变量值,然后就像这些值是查询中的字面值一样对查询进行优化。 当执行计划生成后,再有同样的声明发布时,也就是软解析时,优化器就不再去窥视绑定量 了。 如果现有绑定变量:x,如果将它的值定为 3,发布 select * from aa where id=:x,优化器 将窥视到:x 的值为 3,从而建立执行索引扫描的执行计划。下一次,将:x 值定为 5,再发布 select * from aa where id=:x,优化器不会再去窥视绑定变量的值了,这将再执行索引扫描。 想要观察到绑定变量窥视有点麻烦,因为 Autotrace、和 explain plan 都会误导我们,要 看到真正的结果,只有使用 SQL_TRACE。 sid=12 pid=12> alter system flush shared_pool; sid=12 pid=12> @msql6 sid=12 pid=12> var x number; sid=12 pid=12> exec :x:=3; sid=12 pid=12> alter session set events '10046 trace name context forever ,level 12'; 会话已更改。 sid=12 pid=12> select * from aa where id=:x; ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 234 ZW ID ---------- ---------- 1 3 sid=12 pid=12> alter session set events '10046 trace name context off'; 会话已更改。 查看跟踪文件: Rows Row Source Operation ------- --------------------------------------------------- 1 TABLE ACCESS BY INDEX ROWID AA 1 INDEX RANGE SCAN AA_ID (object id 7807) 在另外一个会话,注意一定要换个会话,要不然在同一个会中,对同一个语句的解析只 会显示一次。 sid=10 pid=11> alter session set events '10046 trace name context forever ,level 12'; 会话已更改。 sid=10 pid=11> var x number; sid=10 pid=11> exec :x:=5; PL/SQL 过程已成功完成。 sid=10 pid=11> select * from aa where id=:x; ZW ID ---------- ---------- „„„„(结果省略) 已选择 30 行。 sid=10 pid=11> alter session set events '10046 trace name context off'; 会话已更改。 查看跟踪计划: Rows Row Source Operation ------- --------------------------------------------------- 30 TABLE ACCESS BY INDEX ROWID AA 30 INDEX RANGE SCAN AA_ID (object id 7807) ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 235 10.3 段层资料 段层资料不需要 DBA 收集,它不是用于生成最优执行计划的,而是将段发生 I/O 争用 时,用来帮助进一步确定争用发生在哪一个段上。段层资料通过以下三个视图查看: V$Segstat_name、V$Segstat、V$Segment_statistics。 在 V$Segstat_name 中,我们可以看到有哪些段资料: SQL> select * from v$segstat_name; STATISTIC# NAME SAM ---------- ---------------------------------------------------------------- --- 0 logical reads YES 1 buffer busy waits NO 2 gc buffer busy NO 3 db block changes YES 4 physical reads NO 5 physical writes NO 6 physical reads direct NO 7 physical writes direct NO 9 gc cr blocks received NO 10 gc current blocks received NO 11 ITL waits NO 12 row lock waits NO 14 space used NO 15 space allocated NO 17 segment scans NO 第 三 节 段层资料  段层资料是 Oracle 自动收集的资料  它并不用来生成执行计划,而用于查找 I/O 问题  通过以下三个视图查看: - V$Segstat_name - V$Segstat - V$Segment_statistics ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 236 已选择 15 行。 可以看到,共有 15 项段资料。在 V$Segstat 和 V$Segment_statistics 中,记录有每个对 象具体的这 15 项资料的数据。例如,我们可以如下显示 U2 用户中 T1 表的各项段层资料数 据: SQL> col STATISTIC_NAME for a30 SQL> select statistic_name,value from v$segment_statistics where object_name='T1' and owner='U2'; STATISTIC_NAME VALUE ------------------------------ ---------- logical reads 224 buffer busy waits 0 gc buffer busy 0 db block changes 0 physical reads 2 physical writes 0 physical reads direct 0 physical writes direct 0 gc cr blocks received 0 gc current blocks received 0 ITL waits 0 row lock waits 0 space used 0 space allocated 0 segment scans 0 已选择 15 行。 V$Segstat 中的数据和 V$Segment_statistics 中的一样。这里就不再显示了。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 237 10.4 Analyze 10.4.1 Analyze 简介 另有一个命令也可以用来收集表、列、索引资料。那就是 Analyze,不过这个命令在 9i 之后就不再推荐使用。如果是为了优化器而收集资料,最后使用 DBMS_STATS 包中的函数。 不过,Analyze 可以收集一些优化器不使用的资料。例如,在使用 DBMS_STATS.GATHER_TABLE_STAT 后,显示 DBA_TABLES 的结果: SQL> select num_rows,blocks,empty_blocks,avg_space,chain_cnt,avg_row_len from user_tables where table_name='T1'; NUM_ROWS BLOCKS EMPTY_BLOCKS AVG_SPACE CHAIN_CNT AVG_ROW_LEN ---------- ---------- ------------ ---------- ---------- ----------- 10 4 0 0 0 73 仍有好几列为 0。再使用 Analyze 后,这些为 0 的列将被填入具体的数据: SQL> analyze table t1 compute statistics; 表已分析。 SQL> select num_rows,blocks,empty_blocks,avg_space,chain_cnt,avg_row_len from user_tables where table_name='T1'; NUM_ROWS BLOCKS EMPTY_BLOCKS AVG_SPACE CHAIN_CNT AVG_ROW_LEN ---------- ---------- ------------ ---------- ---------- ----------- 10 4 4 7251 0 77 第 四 节 Analyze  Analyze 也可以用来收集表、索引资料  此命令在 9i 之后不再推荐使用  Analyze 可以收集一些优化器不使用的资料  对于表来说,Analyze 多收集如下三种资料 - EMPTY_BLOCKS - AVG_SPACE - CHAIN_CNT ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 238 刚才的值为 0 的列已经被填入了数据。总结一下,Analyze 将为比 DBMS_STATS 多收 集下列信息: EMPTY_BLOCKS :空块数 AVG_SPACE :平均空闲空间 CHAIN_CNT :行链接和行迁移数量。T1 表当前没有链接或迁移的行,因此,上面的显示 结果中,此列为 0。 这其中,CHAIN_CNT 最为重要,因为行链接和行迁移对访问性能的影响是很大的。通 过 Analyze table,我们可以了解行链接和行迁移的数量。如果行迁移的数量过多,可以通过 重组表来解决。我们也可以通过如下的方式,找出具体发生迁移的行是哪个: 步 1:创建 CHAINED_ROWS 表: SQL> @?\rdbms\admin\utlchain.sql 表已创建。 此步完成后,用户方案下将多一个 CHAINED_ROWS 表,我们可以显示一下此表的结 构: SQL> desc CHAINED_ROWS 名称 --------------------------- OWNER_NAME :用户名 TABLE_NAME :表名 CLUSTER_NAME :簇名 PARTITION_NAME :分区名 SUBPARTITION_NAME :子分区名 HEAD_ROWID :发生迁移、链接的行的 ROWID ANALYZE_TIMESTAMP :收集资料的时间 这些列的意义都很容易理解。下面我们开始下一步。 步 2:收集行迁移、链接的资料 SQL> analyze table t1 list chained rows; 表已分析。 如果 T1 表中有迁移、链接的行,这些行的信息将被记录进 CHAINED_ROWS 表中。我 们通过 CHAINED_ROWS 的 HEAD_ROWID 列,就可以知道到底 T1 表中哪些行放生了迁 移、链接。如果只有少量行发生迁移,我们可以通过删除再插入的方式,消除这些迁移的行。 10.4.2 收集索引资料 使用 analyze index 还能收集一些 Dbms_stats.gather_index_stats 不能收集的资料: 步 1: SQL> analyze index t1_id validate structure; 索引已分析 步 2:查看收集结果 资料被存入 INDEX_STATS 表。INDEX_STATS 是个基于会话的临时表。在此表中,我 们主要可以看到索引中已经删除行的数量,通过除以索引中总的行数,我们可以得到一个比 值。如果已经删除行的比例超过了 20%,我们就应该重建索引,以提高索引的效率。 SQL> select LF_ROWS,DEL_LF_ROWS,DEL_LF_ROWS/LF_ROWS from INDEX_STATS; LF_ROWS DEL_LF_ROWS DEL_LF_ROWS/LF_ROWS ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 239 ---------- ----------- ------------------- 10 0 0 在我的例子中,此比例为 0,因此不需要重建索引。 删除行过多的情况不仅因为你删除了表中太多行,相应的索引也跟着被删除。在你 Update 表中索引列时,索引并没有 Update 操作,对于索引来说是先删除原有行,再在新的 位置插入行。这也会引起索引中行被删除。因此,不但删除了表中过多行后,有可能需要重 建索引,当 Update 过多行后,也可能需要重建索引。重建索引的命令我就不再说了,在以 前章节已经有过讲述。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 240 附录: 1.MYSQL6.SQL脚本: declare msql varchar2(500); mcur number; mstat number; jg varchar2(4000); cg number; begin mcur:=dbms_sql.open_cursor; for i in 1..1 loop msql:='select myid from t1 where myid='||to_char(i); dbms_sql.parse(mcur,'select myid from t1 where myid='||to_char(i),dbms_sql.native); dbms_sql.define_column(mcur,1,jg,4000); mstat:=dbms_sql.execute(mcur); cg:=dbms_sql.fetch_rows(mcur); dbms_sql.column_value(mcur,1,jg); dbms_output.put_line('查询结果:'||jg); end loop; dbms_sql.close_cursor(mcur); end; / 2.SHOW_SPACE 的创建脚本: create or replace procedure show_space ( p_segname_1 in varchar2, p_space in varchar2 default 'MANUAL', ---对于 ASSM 表空间此列值应为 AUTO p_type_1 in varchar2 default 'TABLE' , p_analyzed in varchar2 default 'N', p_owner_1 in varchar2 default user) authid current_user as p_segname varchar2(100); p_type varchar2(10); p_owner varchar2(30); l_unformatted_blocks number; l_unformatted_bytes number; l_fs1_blocks number; l_fs1_bytes number; l_fs2_blocks number; l_fs2_bytes number; ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 241 l_fs3_blocks number; l_fs3_bytes number; l_fs4_blocks number; l_fs4_bytes number; l_full_blocks number; l_full_bytes number; l_free_blks number; l_total_blocks number; l_total_bytes number; l_unused_blocks number; l_unused_bytes number; l_LastUsedExtFileId number; l_LastUsedExtBlockId number; l_LAST_USED_BLOCK number; procedure p( p_label in varchar2, p_num in number ) is begin dbms_output.put_line( rpad(p_label,40,'.') || p_num ); end; begin p_segname := upper(p_segname_1); -- rainy changed p_owner := upper(p_owner_1); p_type := p_type_1; if (p_type_1 = 'i' or p_type_1 = 'I') then --rainy changed p_type := 'INDEX'; end if; if (p_type_1 = 't' or p_type_1 = 'T') then --rainy changed p_type := 'TABLE'; end if; if (p_type_1 = 'c' or p_type_1 = 'C') then --rainy changed p_type := 'CLUSTER'; end if; dbms_space.unused_space ( segment_owner => p_owner, segment_name => p_segname, segment_type => p_type, total_blocks => l_total_blocks, total_bytes => l_total_bytes, unused_blocks => l_unused_blocks, unused_bytes => l_unused_bytes, LAST_USED_EXTENT_FILE_ID => l_LastUsedExtFileId, LAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId, LAST_USED_BLOCK => l_LAST_USED_BLOCK ); ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 242 if p_space = 'MANUAL' or (p_space <> 'auto' and p_space <> 'AUTO') then dbms_space.free_blocks ( segment_owner => p_owner, segment_name => p_segname, segment_type => p_type, freelist_group_id => 0, free_blks => l_free_blks ); p( 'Free Blocks', l_free_blks ); end if; p( 'Total Blocks', l_total_blocks ); p( 'Total Bytes', l_total_bytes ); p( 'Unused Blocks', l_unused_blocks ); p( 'Unused Bytes', l_unused_bytes ); p( 'Last Used Ext FileId', l_LastUsedExtFileId ); p( 'Last Used Ext BlockId', l_LastUsedExtBlockId ); p( 'Last Used Block', l_LAST_USED_BLOCK ); /*IF the segment is analyzed */ if p_analyzed = 'Y' then dbms_space.space_usage(segment_owner => p_owner , segment_name => p_segname , segment_type => p_type , unformatted_blocks => l_unformatted_blocks , unformatted_bytes => l_unformatted_bytes, fs1_blocks => l_fs1_blocks, fs1_bytes => l_fs1_bytes , fs2_blocks => l_fs2_blocks, fs2_bytes => l_fs2_bytes, fs3_blocks => l_fs3_blocks , fs3_bytes => l_fs3_bytes, fs4_blocks => l_fs4_blocks, fs4_bytes => l_fs4_bytes, full_blocks => l_full_blocks, full_bytes => l_full_bytes); dbms_output.put_line(rpad(' ',50,'*')); dbms_output.put_line('The segment is analyzed'); p( '0% -- 25% free space blocks', l_fs1_blocks); p( '0% -- 25% free space bytes', l_fs1_bytes); p( '25% -- 50% free space blocks', l_fs2_blocks); p( '25% -- 50% free space bytes', l_fs2_bytes); p( '50% -- 75% free space blocks', l_fs3_blocks); p( '50% -- 75% free space bytes', l_fs3_bytes); p( '75% -- 100% free space blocks', l_fs4_blocks); p( '75% -- 100% free space bytes', l_fs4_bytes); p( 'Unused Blocks', l_unformatted_blocks ); ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 243 p( 'Unused Bytes', l_unformatted_bytes ); p( 'Total Blocks', l_full_blocks); p( 'Total bytes', l_full_bytes); end if; end; ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 244 第 11 章 存储空间管理 您将学习: 1. 区的注意事项 2. 大区的优、缺点 3. 块 4. 大块的优缺点 5. 小块的优缺点 6. OLTP 与 OLAP 中区和块使用的不同 第 十一 章 存储空间管理 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 245 11.1 区的注意事项 1.使用本地管理的表空间 2.段的初始大小要设置合理。 如果段初始太小,但段中数据增加很快,段必须频繁扩展。 3.监控要扩展的段。 在繁忙时刻,用户向段中插入数据时,段没有足够的空间,服务器进程必须先扩展段, 为段为配一个区,再向段中插入数据。这种在繁忙时刻的扩展,会浪费一定的时间,影响插 入操作的完成。因此,对于持续增大的段,要经常检查段的大小。如果发现段没有足够的区, 应当即时的为段手动分配足够的区,避免服务器进程自动进行段扩展。因为 DBA 完全可以在 数据库并不繁忙时进行扩展,以避开繁忙时扩展区给性能带来的压力。而服务器进程的自动 扩展,并不会选择时段,无论数据库是否繁忙,当没有空间可用时,它都会立即扩展。因此, DBA 应该经常选择不繁忙的时段,检查段的大小,为空间不足的段手动的分配空间。 使用 Analyze 收集资料后,可以在 DBA_TABLE 中看到一个表的空块有多少。但收集资料 对于大表来说是很耗时的一种操作,这里建议使用一个自己开发的过程:SHOW_SPACE。下面 是使用此过程后的输出结果: SQL> set serveroutput on SQL> exec show_space('T1','U2'); Unformatted Blocks...................... 0 FS1 Blocks(0-25)........................ 0 FS2 Blocks(25-50)....................... 0 FS3 Blocks(50-75)....................... 0 FS4 Blocks(75-100)...................... 0 Full Blocks............................. 1 第 一 节 区的注意事项  使用本地管理的表空间  段的初始大小要设置合理  监控要扩展的段  注意表中的空块数: - 使用 Analyze 收集资料,然后看到表的空块数。 - 使用 SHOW_SPACE 过程,不必收集资料。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 246 Total Blocks............................ 8 Total Bytes............................. 65,536 Total MBytes............................ 0 Unused Blocks........................... 4 Unused Bytes............................ 32,768 Last Used Ext FileId.................... 4 Last Used Ext BlockId................... 81 Last Used Block......................... 4 PL/SQL 过程已成功完成。 这其中,Unused Blocks 就是表还有多少块,当前 U2 用户中的 T1 表还有 4 个空块。使 用它还可以检查索引: SQL> exec show_space('T1_ID','U2','INDEX'); „„„„„„„„ Total Blocks............................ 8 „„„„„„„„ Unused Blocks........................... 4 „„„„„„„„ T1_ID 索引共有 8 个块,空块有 4 个。 使用 SHOW_SPACE 过程,不必收集资料,对于大表,这个过程也可以很快完成。因此, 如果想要观察段中块的使用情况,使用 SHOW_SPACE 要比收集资料更合适。SHOW_SPACE 的建 立,参见本章附录部分。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 247 11.2 大区的优、缺点 1.优点 (1)、有利于提高全扫描的性能 因为区内的块在磁盘上是连续的,因此,对于大批量、连续的访问操作如全扫描,大区 的性能会更好一些。对于从来不全扫描的段,大区并不会带来性能增加。 (2)、减少区扩展的次数 区大,每个区中块的数目就多,这减少了扩展区的次数。 2.缺点 大区的缺点也是很明显的,就是有可能会使大量块空闲。比如 A 表空间是统一区大小, 每个区大小为 10M。如果在 A 表空间创建一个表甲,假设它只占 100 个字节。那么表甲仍必 须至少分配一个区,区中除这 100 字节外,剩下的都是不能再被其他对象使用的空闲空间。 第 二 节 大区的优缺点 优点  有利于提高全扫描的性能  减少区扩展的次数 缺点  造成空间的浪费 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 248 11.3 块 块的注意事项,有一些基本规则,如它是 ORACLE 读、写 I/O 的最基本单位。块的大小 是在创建数据库时由 DB_BLOCK_SIZE 参数指定的。除非重建数据库,否则不可更改。块大小 应该是操作系统块大小的倍数。而且块的最小、最大的限制,由操作系统决定。除此之外, 我们再说三点注意事项: 1.使用大块 块越大其中所容纳的数据量就越多,访问相等大小的表,所需的块就越少。使用大块, 将有助于提高各种访问操作(如全扫描、索引访问等)的速度。使用大块还有助降低 B-树 索引的高度,这对于提高索引访问效率也是很有帮助的。 2.在块中填入更多的行 每个块中都会预留一部分空闲空间,如果你的表很少更改,应该将这一部分空闲空间设 置的尽量少。这样一样,容纳同样大小的表,所需块数将更少一些。访问效率将会增加。 3.注意事项 不幸的是,以上两点都会增加 DML 操作对块的争用。因为如果一个块中的行越多,同时 修改这些行的进程也会增加,这将会增加块争用的可能性。试想,如果块 A 中只有两行,那 么,只有两个进程可以同时修改这个块。而块 B 中有 200 行,那么就有 200 个进程可以同时 修改块 B,因此块 B 发生争用的概率将更大。 还有第二点需要注意,如果块中预留空间少,发生行迁移的可能性也会增大。而行迁移 对性能的影响是很大的。 第 三 节 块  它是 ORACLE 读、写 I/O 的最基本单位  大块提升全表扫描性能,但增加了 DML 争用的可能  在块中填入更多的行可以增加全表扫描的性能,但 同时也增加了 DML 争用的可能  设置合适的预留空间(PCTFREE、PCTUSED),避免 行迁移 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 249 11.4 大块的优点和缺点 1.优点 (1)、顺序读取、索引读取的性能更好。 这一点的原因上面已经说过了。 (2)、适于比较大行 2.缺点 (1)、增加了块争用的可能性 这一点的原因上面也已经说过了。 (2)、在 Buffer cache 中占用了更多的空间 ORACLE 每次都是至少将一个块读进 Buffer cache。Buffer cache 中空间的使用,也是 按块大小来划分的,Buffer cache 中空间的最小单位就是一个块的大小。即使只为了从磁 盘上读一个字节,ORACLE 也必须把那个字节所在块全部的读进缓存。因此,如果块越大, 势必占用的缓存空间就越大。 第 四 节 大块的优点和缺点 优点  顺序读取、索引读取的性能更好。  适于比较大的行 缺点  增加了块争用的可能性  在 Buffer cache 中占用了更多的空间 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 250 11.5 小块的优缺点 1.优点 (1)、减少 DML 时的争用 原因很简单,这一点是和大块增加争用是对应的。 (2)、占用更少的 Buffer cache 这一点也是和大块对应的。以更小的粒度使用缓存,在随机性访问操作中,可以使缓存 更有效率。相反,对于大量连续操作,大块对缓存的使用来说,将更有效率。 (3)、适于比较小的行 2.缺点 (1)、访问效率不如大块 小块有可能会增加索引树高,这不利于通过索引的访问。而且小块中行少一些,访问同 样大小的表,小块所需的 I/O 次数要更多一些。 第 五 节 小块的优缺点 优点  减少 DML 时的争用  占用更少的 Buffer cache  适于比较小的行 缺点  访问效率不如大块 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 251 11.6 OLTP 与 OLAP 中区和块使用的不同 总结以上优缺点,不难看,大块更适合数据仓库类型的应用。 小块更适于 OLTP 型的应用。OLTP 大都是小的随机访问,大块不会带来太大的性能收益, 反而会因为 Buffer cahce 使用不充分,而导致性能下降。并且 OLTP 的 DML 会很频繁,这无 疑会加大大块争用的可能性。 大块更适合 OLAP 型的应用。OLAP 型应用 DML 操作很少,因此不会有太多的块争用。而 且大量、连续访问操作――全扫描更多一些,大块的性能优势可以得到发挥。 还有,相对于区,OLAP 适合于统一区大小、并且区比较大的表空间。因为这类表空间 的读性能会更好。而且,从表空间的设置上来说,MSSM(手动段空间管理)型的表空间性能 更高。因为 ASSM(自动段空间管理)主要用于减少插入时的争用,OLAP 中的操作以查询为 主,不会有大量并发的插入。而 OLTP 的区大小没有统一的标准,应该根据情况而定。插入 操作频繁的表,应该放在 ASSM 表空间中。对于可以预知大小的段,应该使统一区大小的表 空间,而具体区大小的设定,可以参考表空间中段大小来定。而对于段大小不可预知的段, 可以放在系统管理区大小的表空间中,这样空间的使用将更有效率。 第 六 节 OLTP 与 OLAP 中区和块使用的不同  OLTP 适合使用小块  OLTP 适合 ASSM 型表空间  OLTP 适合系统管理区大小的表空间  OLAP 适合使用大块  OLAP 适合 MSSM 型表空间  OLAP 适合统一区大小的 表空间 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 252 r 第 12 章 聚簇 您将学习: 1. 索引聚簇的应用与管理 2. HASH 聚簇的应用与管理 第 十二 章 聚簇 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 253 12.1 索引聚簇的应用与管理 12.1.1 什么是索引聚簇 在 SQL Server 和 Sybase 中,有一个聚簇索引的概念,许多人都把它与索引聚簇相混淆 了,但它们并不一样。聚簇(Cluster)是指:如果一组表有一些共同的列,则将这一组表 存储在相同的数据库块中。聚簇还表示把相关的数据存储在同一个块上。SQL Server 中的 聚簇索引(Cluster index)则要求行按索引键有序的方式存储,这类似于 Oracle 中一个我 们马上要讲的概念:索引组织表。利用聚簇,一个块中可能包含多个表的数据。从概念上讲, 这是将数据“预联结“地存储。聚簇还可以用于单个表,可以按某个列将数据分组存储。例 如,部分 10 的所有员工都存储在同一个块上(或者如果一个块放不下,则存储在尽可能少 的几个块中)。聚簇并不是有序地存储数据(这是索引组织表的工作),它是按某个键以聚簇 方式存储数据,但数据存储在堆中,所以部门 100 可能挨在部门 1 旁边,而与部门 101 和 99 离的很远(这是指磁盘上的物理位置)。 第 一 节 索引聚簇的应用与管理  什么是索引聚簇  索引聚簇的创建  索引聚簇表与普通表的区别  索引聚簇适合 DML 操作较少且需经常进行连接的 表  索引聚簇可以减少 I/O 但会增加 CPU 的负担 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 254 如上图所示,图的左边使用了传统的表,EMP 会存储在它的段中。DEPT 表也存储在自己 的段中,它们可能位于不同的文件和不同的表空间,而且绝对在单独的区段中。从图的右边 可以看到将这两个表聚簇起来会是什么情况 。方框表示数据库块,再在将值 10 抽取出来, 只存储一次。这样聚簇的所有表中对应部门 10 的所有数据就存储在这个块上。如果部门 10 的所有数据在一个块上放不下,可以为原来的块中再添加另外的块,来包含这些溢出的部分, 这里溢出的概念在其他类型的表也有使用如马上要说到的索引组织表。 12.1.2 索引聚簇的创建 下面来看如何创建一个聚簇对象。在对象中创建表的一个聚簇很直接。对象的存储参数 如 PCTFREE、PCTUSED、INITIAL 等,与 CLUSTER 相关,而不是与表相关。这是有道理的,因 为聚簇中会有多个表,而有关同一个块上。有多个不同的 PCTFREE 没有意义。因此,CREATE CLUSTER 非常类似于只有很少几个列的 CREATE TABLE(只有聚簇键列): SQL> create cluster e_d_clu(deptno number(2)) size 1024; 簇已创建。 在此,我们创建了一个索引聚簇(Index clustr,还有一种类型是 HASH 聚簇,是我们 下一章的内容)。这个聚簇的聚簇列是 DEPTNO 列。表中的列不必非得叫 DEPTNO,但是必须 是 NUMBER(2),这样才能与定义匹配。我们在这个聚簇定义中加了一个 SIZE 1024 选项。这 个选项用来告诉 Oracle:我们希望与每个聚簇键值关联大约 1024 字节的数据。Oracle 会使 用这个选项设置来计算每个块最多能放下多少个聚簇键。假设块大小为 8KB,Oracle 会在每 个数据库块上放上最多 7 个聚簇键(但是如果数据比预想的更大,聚簇键可能还会少一些)。 也就是说,对应部门 10、20、30、40、50、60 和 70 的数据会放在一个块上,一旦插入部门 80,就会使用一个新块。这并不是说数据按一种有序的方式存储,而是说如果按这种顺序插 入部门,它们会很自然地放在一起,如果按下面的顺序插入部门,即先插入 10、80、20、 30、40、50、60,然后插入 70,那么最后一个部门(70)将放在新增的块上。稍后会看到, 数据的大小以及数据插入的顺序都会影响每个块上能存储的聚簇键个数。 因此,SIZE 参数控制着每块上聚簇键的最大个数。这是对聚簇空间利用率影响最大的 因素。如果把这个 SIZE 设置得太高,那么每个块上的键就会很少,我们会不必要地使用更 多的空间。如果设置得太低,以会导致数据过分串键,这又与聚簇本来的目的不符,因为聚 簇原本是为了把所有相关数据都存储在一个块上。对于聚簇来说,SIZE 是最重要的参数。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 255 下面来看聚簇上的聚簇索引,向聚簇中放数据之前,需要对聚簇建立索引。可以现在就 在聚簇中创建表。但是由于我们想同时创建和填充表,而有数据之前必须有一个聚簇索引, 所以我们先来建立聚簇索引。聚簇索引的任务是拿到一个聚簇键值,然后返回包含这个键的 块的块地址。实际上这是一个主键,其中每个聚簇键值指向聚簇本身中的一个块。因此 , 我要求部门 10 的数据时,Oracle 会读取聚簇键,确定相应的块地址,然后读取数据。聚簇 键索引如下创建: SQL> create index edclu_idx on cluster e_d_clu; 索引已创建。 对于索引平常有的所有存储参数,聚簇索引都可以有,而且聚簇索引可以存储在另一个 表空间中。它就像是一个常规的索引,所以同样可以在多列上建立。聚簇索引只不过恰好是 一个聚簇的索引,另外可以包含对应完全 NULL 值的条目。注意,在这个 CREATE INDEX 语句 中,并没有指定列的一个列表,索引列可以由 CLUSTER 定义本身得出。再在我们可以在聚簇 中创建表了。如下的两条语句在我们上面创建的聚簇中建立两个表:Clu_dept 和 Clu_emp: SQL> create table clu_dept (deptno number(2) primary key,dname varchar2(14),loc varchar2(13)) cluster e_d_clu(deptno); 表已创建。 SQL> CREATE TABLE clu_EMP 2 (EMPNO NUMBER(4) PRIMARY KEY, 3 ENAME VARCHAR2(10), 4 JOB VARCHAR2(9), 5 MGR NUMBER(4), 6 HIREDATE DATE, 7 SAL NUMBER(7, 2), 8 COMM NUMBER(7, 2), 9 DEPTNO NUMBER(2) REFERENCES clu_dept(deptno) 10 ) 11 cluster e_d_clu(deptno); 表已创建。 在此,与正常表唯一的区别是,我们使用了 CLUSTER 关键字,并告诉 Oracle 基表的哪 个列会映射到聚簇本身的聚簇键。要记住,这里的段是聚簇,因此这个表不会有诸如 TABLESPACE、PCTFREE 等段属性,它们都是聚簇段的属性,而不是我们所创建的表的属性。 现在可以向这些表加载初始数据了。我用如下的方式向这两个簇表中插入行: 步 1:显示我们的数据源表之一:Scott.dept SQL> select * from scott.dept; DEPTNO DNAME LOC ---------- -------------- ------------- 10 ACCOUNTING NEW YORK 20 RESEARCH DALLAS 30 SALES CHICAGO 40 OPERATIONS BOSTON 可以看到共有 4 个部门,部门编号分别是 10、20、30 和 40。我下面为两个表分别的的 插入 10 号、20 号、30 号和 40 号部门的行。 步 2:分别插入四个部门的行: SQL> insert into clu_dept select * from scott.dept where deptno=10; ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 256 已创建 1 行。 SQL> insert into clu_emp select * from scott.emp where deptno=10; 已创建 3 行。 上面两条语句先将 DEPT 表的 10 号部门插入到 Clu_dept 聚簇表中,再将 EMP 表中所有 部门为 10 的行插入到聚簇表 Clu_emp 中。下面,依次插入 20、30 和 40 号部门的行。 插入 20 号部门的行: SQL> insert into clu_dept select * from scott.dept where deptno=20; 已创建 1 行。 SQL> insert into clu_emp select * from scott.emp where deptno=20; 已创建 5 行。 插入 30 号部门的行: SQL> insert into clu_dept select * from scott.dept where deptno=30; 已创建 1 行。 SQL> insert into clu_emp select * from scott.emp where deptno=30; 已创建 6 行。 插入 40 号部门的行: SQL> insert into clu_dept select * from scott.dept where deptno=40; 已创建 1 行。 SQL> insert into clu_emp select * from scott.emp where deptno=40; 已创建 0 行。 最后提交: SQL> commit; 提交完成。 你可能会奇怪,为什么不是插入所有 DEPT 数据,然后再插入所有 EMP 的数据呢?或者 反之,先插入所有 EMP 数据,然后再插入所有 DEPT 的数据?为什么要想这样按 DEPTNO 逐个 的插入数据行呢? 原因就在于聚簇的设计,我们在模拟一个聚簇的大批量初始加载。如果先加载所有 DEPT 行,每个块上就会有 7 个键(根据前面指定的 SIZE 1024 设置),这是因为 DEPT 行非常小 (只有几十字节)。等到加载 EMP 行时,可能会发现有些部门的数据远远超过了 1024 字节。 这样就会在那些聚簇键块上导致过度的串连。Oracle 会把包含这些信息的一组块串连或联 接起来。如果同时加载对应一个给定聚簇键的所有数据,就能尽可能紧凑地塞满块,等空间 用完时再开始一个新块。Oracle 将不是在每个块中放最多 7 个聚簇键值,而是会适当地尽 可能多地放入聚簇键值。 12.1.3 索引聚簇表与普通表的区别 我们继续上面的实验,来观察一下索引聚簇表与普通表的性能有什么不同: 步 3:观察聚簇表的物理读 打开 SQL Autotrace: SQL> set autot trace 先选择几遍表: SQL> select * from clu_dept a, clu_emp b where a.deptno=b.deptno; 已选择 14 行。 在另一会话中将 Buffer cache 池清空: ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 257 SQL> alter system flush buffer_cache; 系统已更改。 再观察物理读: SQL> select * from clu_dept a, clu_emp b where a.deptno=b.deptno; 已选择 14 行。 执行计划 ---------------------------------------------------------- ( 此部分与本例无关,在此省略) 统计信息 ---------------------------------------------------------- 0 recursive calls 0 db block gets 15 consistent gets 7 physical reads 0 redo size 1627 bytes sent via SQL*Net to client 385 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 14 rows processed 可以看到逻辑读是 7 次。 在这里我只所以要先执行几遍选择语句,再把 Buffer cache 池清空后观察物理读,原 因是减少其他池对观察结果的影响。 步 4:观察普通表的物理读: 我们采用和步 3 同样的方法,把聚簇表换为行数完全相同的 EMP、DEPT 表,最终的结果 为: SQL> select * from dept a, emp b where a.deptno=b.deptno; 已选择 28 行。 执行计划 ---------------------------------------------------------- ( 省略执行计划信息 ) 统计信息 ---------------------------------------------------------- 0 recursive calls 0 db block gets 12 consistent gets 12 physical reads 0 redo size 2451 bytes sent via SQL*Net to client 396 bytes received via SQL*Net from client 3 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 258 28 rows processed 物理读为 12 次。 这所以结果是这样,原因很简单,对于聚簇表,两个聚簇表其实只对应同一个聚簇段, 也就是说 Clu_dept 和 Clu_emp 其实是存放在一起的,在这个简单的例子中,这两个表合起 来只占一个块。这一点我们可以使用 DBMS_ROWID 包来证明: SQL> select dbms_rowid.rowid_block_number(rowid),deptno from clu_dept; DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) DEPTNO ------------------------------------ ---------- 184 10 184 20 184 30 184 40 SQL> select dbms_rowid.rowid_block_number(rowid),deptno from clu_emp; DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) DEPTNO ------------------------------------ ---------- 184 10 184 10 184 10 184 20 184 20 184 20 184 20 184 20 184 30 184 30 184 30 DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) DEPTNO ------------------------------------ ---------- 184 30 184 30 184 30 已选择 14 行。 可以看到,这两个表只占 184 这一个块,而普通表 DEPT 和 EMP 则不同了,它们是普通 表,对应两个段,在这个例子中,它们的数据分别占了两个数据块。因此,它们两个表连接 查询当然会消耗更多的 I/O。 而逻辑读方面,在此例中聚簇表的逻辑读高于两个普通表,这是因为聚簇表要比普通表 复杂一些,查询时所需的操作比普通表多一些,因此需要更多的逻辑读。 通过这个例子我们也明白两点,一是聚簇顾名思义,就是将数据聚在一起,因此,它特 别适合需要经常连接查询的表。另一点是,聚簇表的操作,比普通表更复杂一些,好的聚簇 表在查询时可以减少物理 I/O,但会增加 CPU 的负担,这包括逻辑读和一些其他的额外的相 关 CPU 的消耗。另外,在 DML 时,CPU 的负担也会增加。 因此,我们要根据情况选用聚簇表。通常很少更新的表,又需要经常连接起来查询,同 时数据库的 CPU 负载并不过重,这时就特别适合使用聚簇。如果只是前两条满足,更新很少 且经常连接起来查询,但此时主机一向 CPU 负载都比较很高,这时使用聚簇表就需要考虑考 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 259 虑了,因为聚簇表的作用就是通过让 CPU 作更多的工作,来减少物理 I/O。在 CPU 负载过高 的情况下,不太适合再去额外的增加 CPU 的负担。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 260 12.2 HASH 聚簇的应用与管理 12.2.1 什么是 HASH 聚簇 索引聚簇都是对多个表起作用,它是把多个表聚在一起,而 HASH 聚簇则只针对一个表。 在 HASH 聚簇中,每个簇键我们又称为 HASH 关键字。在创建 HASH 聚簇时,我们仍然要 为每个 HASH 关键字预留空间,不过,相比索引聚簇、和堆组织表,HASH 聚簇又多一个选项, 就是 HASH 关键字数量。还以上节中雇员表为例,如果表中不同的部门共有 100 个,那么 HASH 关键字的数量就是 100。这个数字是固定的,一但建立不可能再发生改变。Oracle 会根据你 设定的 HASH 关键字数量,为每个不同的部门值分配一个 HASH 值。如果你以后插入了第 101 个不同的部门,哪么这多出来的一个,将和原有某一个部门共有同一 HASH 值,这被称为 HASH 值的冲突。每个 HASH 值将对应一个数据块地址,也就是说,如果以后你要查找部门为 N 的 行时,Oracle 会根据 N 计算出 HASH 值,根据此 HASH 值,将可直接定位部门为 N 的行所在 的块有哪些。在冲突很少的情况下,利用 HASH 聚簇进行等值查找,将比索引使用更少的逻 辑和物理读。因为它可以根据你的条件,计算出 HASH 值,根据 HASH 值即可定位到满足条件 所在的块。但是,在这个过程中,CPU 的使用将大幅度增加。因为计算 HASH 值是一个很消 耗 CPU 的操作。 我们仍然要为每个 HASH 关键字指定预留空间。在这里,我假设根据统计,每个部门中 雇员最多 500 名,每名雇员在雇员表中大概占 70 个字节,那可以为 HASH 关键字预留空间定 为 3500 字节。如果块大小为 8K,每个块中可以容纳两个 HASH 关键字。也就是说,部门为 1 和部门为 2 的,可以在一个块中,假设是块 A。即使部门为 1 的行当前只有一行,也必须为 1 号部门在块中保留三千多字节。但假如 1 号部门中的员工数量后来多于 500 名了,也就是 超出了 3500 字节的预留空间,即使这时部门 2 的预留空间还有很多,也不能去占用它的预 第 二 节 HASH 聚簇的应用与管理  什么是 HASH 聚簇  HASH 簇的创建  HASH 聚簇性能测试  HASH 聚簇适用于 DML 较少且需经常进行等值查询 的单表  HASH 聚簇可以减少 I/O,但会增加 CPU 的负担 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 261 留空间。这时,ORACLE 会再分配一个块,假设是块 B,来存贮这额外的信息。这一点与索引 聚簇是一样的。有一点不同的是,在 HASH 聚簇中,ORACLE 还会把块 B 链接在块 A 之后,这 个块 B 又被称为溢出块。除了这种情况会有溢出块外,如果发生了 HASH 关键字冲突,比如 初始设定的 HASH 关键字数量为 100,部门编号从 1 到 100,后 来又增加了一个第 101 号部门。 这第 101 号部门肯定要与 1 至 100 中的某一个共享同一 HASH 值,这就是冲突,这我们已经 在上面说过了。假设此处 101 号与 1 号部门共享同一 HASH 值,那么,它们两个的行将被放 在相同的块中,共享 3500 字节的预留空间。如要超过了,也会发生溢出。 溢出后为了访问部门等于 1 的行,我们要多访问一个块,因此,我们要尽量避免不必要 的溢出。尤其要减少 HASH 关键字冲突而引发的溢出。因为普通的溢出,为了使 HASH 关键字 不溢出,必须预留多一些的空间,它同样要占多个块。但是冲突引发的溢出则不同,它将不 同的部门混杂在一起存贮,为了访问部门 1,不得不读取部门 101,这增加了不必要的 I/O。 好,下面我们开始创建 HASH 簇。 12.2.2 HASH 簇的创建 步 1:创建 HASH 簇: SQL> create cluster hash_cluster(hash_key number) hashkeys 100 size 3500; 簇已创建。 和索引聚簇不同是,在创建 HASH 簇时,将会立即为它分配空间。我们可以使用 SHOW_SPACE 过程查看 HASH 簇的空间分配情况(此过程在上一章附录中有介绍): SQL> exec sys.show_space('HASH_CLUSTER','U2','CLUSTER'); Unformatted Blocks...................... 0 FS1 Blocks(0-25)........................ 0 FS2 Blocks(25-50)....................... 0 FS3 Blocks(50-75)....................... 0 FS4 Blocks(75-100)...................... 0 Full Blocks............................. 51 Total Blocks............................ 64 Total Bytes............................. 524,288 Total MBytes............................ 0 Unused Blocks........................... 7 Unused Bytes............................ 57,344 Last Used Ext FileId.................... 4 Last Used Ext BlockId................... 313 Last Used Block......................... 1 PL/SQL 过程已成功完成。 每个 HASH 关键字预留 3500 字节,一个块中大约可以存贮两个 HASH 键。共 100 个 HASH 键,也就是总共需要 50 个块。但是从上面的结果看,一共分配了 64 个块。这主要是因为有 些块不是用来存储数据的,是 ASSM 表空间中的管理块,如段头、L2、L1 块。(还有一个原 因,是因为 HASH 键的数量会被最终定为大于 100 的最小质数)。下面,我们在此 HASH 聚簇 中创建表。 步 2:在 HASH 聚簇中创建表 SQL> create table hash_cluster(id number, name varchar2(20)) cluster hash_cluster(id); ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 262 表已创建。 这里需要注意的是,无论是索引聚簇还是 HASH 聚簇,表中聚簇列的类型、长度与设定 的聚簇中聚簇列的类型、长度应该一致。HASH 聚簇也可以有多个表,这一点和索引聚簇是 一样的,它可以提高多表等值连接的效率。 聚簇表的使用对用户是透明的,也就是说,用户可以像使用普通表一样使用聚簇表。聚 簇表的插入、删除、更新都和堆表一样,这些操作我就不再试了。 12.2.3 HASH 聚簇性能测试 我创建一个有 1000 块左右的表,分别以 HASH 聚簇和堆表的形式创建它,然后分别查询 它们,比较二者的差别。我插入一些行,我将表的行长度定为 1000 左右。这样每个块中只 容纳 7 行。每个 HASH 键预留大小也定为 1000 字节,和行长度一样。也就是不为 HASH 键预 留额外的空间。7 行占一个块,1000 个块共需要 7000 行。我的 HASH 关键字数量也定为 7000 个,以避免冲突。好下面我们开始测试。 步 1:创建 HASH 表: SQL> create cluster hh_lx(hash_key number) hashkeys 7000 size 1000; 簇已创建。 SQL> create table lx1_hc(id number, name char(1000)) cluster hh_lx(id); 表已创建。 SQL> exec sys.show_space('HH_LX','U2','CLUSTER'); Unformatted Blocks...................... 0 FS1 Blocks(0-25)........................ 0 FS2 Blocks(25-50)....................... 0 FS3 Blocks(50-75)....................... 0 FS4 Blocks(75-100)...................... 0 Full Blocks............................. 876 Total Blocks............................ 1,024 Total Bytes............................. 8,388,608 Total MBytes............................ 8 Unused Blocks........................... 124 Unused Bytes............................ 1,015,808 Last Used Ext FileId.................... 4 Last Used Ext BlockId................... 1,289 Last Used Block......................... 4 PL/SQL 过程已成功完成。 此时观察可知,HASH 聚簇 HH_LX 共占 1024 个块。下面向 HAHS 聚簇表 LX1_HC 中插入 7000 行。 SQL> insert into lx1_hc select rownum,'abc' from dba_objects where rownum<=7000; 已创建 7000 行。 SQL> commit; 提交完成。 再次查看整个簇空间的占用情况 SQL> exec sys.show_space('HH_LX','U2','CLUSTER'); Unformatted Blocks...................... 0 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 263 FS1 Blocks(0-25)........................ 0 FS2 Blocks(25-50)....................... 0 FS3 Blocks(50-75)....................... 0 FS4 Blocks(75-100)...................... 0 Full Blocks............................. 876 Total Blocks............................ 1,024 Total Bytes............................. 8,388,608 Total MBytes............................ 8 Unused Blocks........................... 124 Unused Bytes............................ 1,015,808 Last Used Ext FileId.................... 4 Last Used Ext BlockId................... 1,289 Last Used Block......................... 4 PL/SQL 过程已成功完成。 打开 Autotrace: SQL> set autot trace 多进行几次同样的查询,查看最终结果: SQL> select * from lx1_hc where id=3000; 执行计划 ---------------------------------------------------------- Plan hash value: 3822754829 ----------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| ----------------------------------------------------------------- | 0 | SELECT STATEMENT | | 62 | 31930 | 0 (0)| |* 1 | TABLE ACCESS HASH| LX1_HC | 62 | 31930 | | ----------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("ID"=3000) Note ----- - dynamic sampling used for this statement 统计信息 ---------------------------------------------------------- 0 recursive calls 0 db block gets 2 consistent gets 0 physical reads 0 redo size 460 bytes sent via SQL*Net to client 384 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 264 0 sorts (disk) 1 rows processed 步 2:观察普通的堆组织表: SQL> create table lx1_hd(id number, name char(1000)); 表已创建。 SQL> insert into lx1_hd select rownum,'abc' from dba_objects where rownum<=7000; 已创建 7000 行。 SQL> commit; 提交完成。 SQL> exec sys.show_space('LX1_HD','U2'); Unformatted Blocks...................... 0 FS1 Blocks(0-25)........................ 4 FS2 Blocks(25-50)....................... 0 FS3 Blocks(50-75)....................... 0 FS4 Blocks(75-100)...................... 0 Full Blocks............................. 996 Total Blocks............................ 1,024 Total Bytes............................. 8,388,608 Total MBytes............................ 8 Unused Blocks........................... 0 Unused Bytes............................ 0 Last Used Ext FileId.................... 4 Last Used Ext BlockId................... 2,313 Last Used Block......................... 128 PL/SQL 过程已成功完成。 和簇一样,它也占 1024 个块。如果现在执行和簇同样的语句,逻辑读和物理读肯定都 很多,因为现在表没有索引,只有执行全表扫描。我为 ID 列创建一个索引: SQL> create index lx1_hd_id on lx1_hd(id); 索引已创建。 SQL> set autot trace 多进行几次同样的查询,查看最终结果: SQL> select * from lx1_hd where id=3000; 执行计划 ---------------------------------------------------------- Plan hash value: 215873010 ----------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ----------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 1015 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| LX1_HD | 1 | 1015 | 2 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | LX1_HD_ID | 1 | | 1 (0)| 00:00:01 | ----------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 265 2 - access("ID"=3000) Note ----- - dynamic sampling used for this statement 统计信息 ---------------------------------------------------------- 0 recursive calls 0 db block gets 4 consistent gets 0 physical reads 0 redo size 1468 bytes sent via SQL*Net to client 384 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed 最终结果逻辑读为 4。 通过上面的实验可以看到,通过索引,最少要 4 个逻辑读,而查询 HASH 聚簇表,则只 需要两个逻辑读。相比之下,自然是 HASH 聚簇更高速。不过,HASH 聚簇也有它的不足之处, 归纳如下: 1.和索引聚簇一样,HASH 聚簇增加了插入或更新聚簇列时的负担。因此,不适于在大量插 入、更新聚簇列的情况下使用。 2.HASH 键数据量固定。如果 HASH 列的值超过 HASH 键数量太多,则会引起太多的冲突。这 将增加查询 HASH 聚簇表时的 I/O。 3.相比索引的访问路径,HASH 聚簇占用更多的 CPU 时间。 如果可以确定在 HASH 聚簇中只有一个表,我们可以将 HASH 聚簇创建为单表 HASH 聚簇。 单表 HASH 聚簇所耗用的逻辑读、CPU 都要比普通的 HASH 聚簇少一些。下面我们实验一下: SQL> create cluster hh_lx2(hhky number) hashkeys 7000 size 1000 single table ; 簇已创建。 创建单表 HASH 聚簇非常简单,只需要在创建普通 HASH 聚簇的命令后加上一个“single table”子句就行了。 SQL> create table lx2_hc(id number, name char(1000)) cluster hh_lx2(id); 表已创建。 SQL> insert into lx2_hc select rownum,'abc' from dba_objects where rownum<=7000; 已创建 7000 行。 SQL> commit; 提交完成。 SQL> select * from lx2_hc where id=3000; 统计信息 ---------------------------------------------------------- 0 recursive calls 0 db block gets 1 consistent gets ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 266 0 physical reads 0 redo size 1462 bytes sent via SQL*Net to client 384 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed 可以看到,逻辑读降为只有一个了。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 267 第 13 章 索引组织表 您将学习: 1. 索引组织表与堆表的不同 2. 索引组织表的创建 3. 索引组织表上的次要索引 4. 映像表与位图索引 5. 溢出段 6. 索组织表的相关视图 第 十三 章 索引组织表 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 268 13.1 索引组织表 13.1.1 索引组织表与堆表的不同 堆表就是普通表,也叫堆组织表。之所以这样叫,是因为他使用数据结构中堆的算法来 组织表。堆表的特点就是插入的行没有顺序。Oracle 随机的将新插入的行按排在段的可用 块中。如果你想按照某一列(或是几列)的顺序,顺序的安排行的顺序,就需要使用索引组 织表了。索引组织表的缺点也是很明显的,在堆表中,行可以直接的进行插入,而在索引组 织表,要额外的确定行的存储位置,这将消耗更多的 CPU。如果想要使插入、修改这些 DML 操作的开销最小,堆表将是最好的选择。 13.1.2 索引组织表的创建 下面的语句创建一个索引组织表: SQL> create table t3_iot(id number(5), name varchar2(30), primary key(id)) organization index tablespace tp1 ; 表已创建。 索引组织表一定要有索引,其实也就是一定要有一个主键,表中行的顺序将按照主键的 顺序,依次排列。也就是表中行的顺序将按照主键的顺序来组织,主键的索引和表的其他列 存放在一起,这就是索引组织表。和堆表的区别是,索引和表存放在一起。这样更加节省存 贮空间。 注意,“organization index”子句说明这是一个索引组织表,此子句一定要跟在括号 第 一 节 索引组织表  索引组织表与堆表的不同  索引组织表的创建  索引组织表上的次要索引  映像表与位图索引  溢出段  索组织表的相关视图 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 269 的后面。 下面我插入两行来看看访问索引组织表的 I/O: SQL> insert into t3_iot values(1,'aaa'); 已创建 1 行。 SQL> insert into t3_iot values(2,'bbb'); 已创建 1 行。 SQL> commit; 提交完成。 打开资料跟踪: SQL> set autot trace; 执行多次下面的语句(至少两次),观察结果比较稳定时的资料: SQL> select * from t3_iot where id<=2; 执行计划 ---------------------------------------------------------- Plan hash value: 3296959069 -------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 2 | 60 | 1 (0)| 00:00:01 | |* 1 | INDEX RANGE SCAN| SYS_IOT_TOP_51936 | 2 | 60 | 2 (0)| 00:00:01 | -------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("ID"<=2) Note ----- - dynamic sampling used for this statement 统计信息 ---------------------------------------------------------- 0 recursive calls 0 db block gets 2 consistent gets 0 physical reads 0 redo size 503 bytes sent via SQL*Net to client 384 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 2 rows processed 可以看到,结果只有两次逻辑读。无论表有多少个块,因为索引组织表是有序存储的, 当查到最后一条 ID 为 2 的行后,就不需要再查找下去了。但堆表就不同,下面,看一个堆 表的例子: SQL> create table t2(id number(5), name varchar2(30)) tablespace tp1; ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 270 表已创建。 SQL> insert into t2 values(1,'aaa'); 已创建 1 行。 SQL> insert into t2 values(2,'bbb'); 已创建 1 行。 和上面的索引组织表一样,只插入两行。执行一个和上面一样的查询: SQL> select * from t2 where id<=2; 执行计划 ---------------------------------------------------------- Plan hash value: 1513984157 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 2 | 60 | 3 (0)| 00:00:01 | |* 1 | TABLE ACCESS FULL| T2 | 2 | 60 | 3 (0)| 00:00:01 | -------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("ID"<=2) Note ----- - dynamic sampling used for this statement 统计信息 ---------------------------------------------------------- 0 recursive calls 0 db block gets 8 consistent gets 0 physical reads 0 redo size 503 bytes sent via SQL*Net to client 384 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 2 rows processed 可以看到,逻辑读有 8 次。这是因为堆表是无序存储的,为了找出所有 ID 小于等于 2 的行,Oracle 不得不扫描表中所有块。因此,逻辑读自然要比索引组织表高。 对于等值条件,索引组织表也比堆表需要更少的逻辑读: SQL> select * from t3_iot where id=2; 执行计划 ---------------------------------------------------------- Plan hash value: 240381283 --------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 271 --------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 30 | 1 (0)| 00:00:01 | |* 1 | INDEX UNIQUE SCAN| SYS_IOT_TOP_51936 | 1 | 30 | 1 (0)| 00:00:01 | --------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("ID"=2) 统计信息 ---------------------------------------------------------- 0 recursive calls 0 db block gets 1 consistent gets 0 physical reads 0 redo size 460 bytes sent via SQL*Net to client 384 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed 只需一次 I/O,就可以查询到所需数据。堆表就不行了,对于堆表,ID=2 这样的条件同 样要进行全表扫描,如果为堆表另建索引,像这样的条件至少也需要两个 I/O:索引一个、 表一个。下面我们为堆表 T2 创建个索引试试: SQL> create index t2_id on t2(id); 索引已创建。 SQL> select * from t2 where id=2; 执行计划 ---------------------------------------------------------- Plan hash value: 3119810522 ------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 30 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 30 | 2 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | T2_ID | 1 | | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("ID"=2) Note ----- - dynamic sampling used for this statement 统计信息 ---------------------------------------------------------- ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 272 0 recursive calls 0 db block gets 2 consistent gets 0 physical reads 0 redo size 464 bytes sent via SQL*Net to client 384 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed 如上所试,堆表需要两个 I/O。从执行计划也可以看到来,一个 I/O 是针对索引的,“INDEX RANGE SCAN”。另一个 I/O 是针对表的,“TABLE ACCESS BY INDEX ROWID”。 因此,如果我们经常需要以主键为条件访问表,我们应该使用索引组织表。它可以减少 访问时的 I/O。 但时,索引组织表增加了插入时所做的工作,如果需要大并发的插入,或频繁的修改主 键列,一定不能使用索引组织表。这会消耗更多的 CPU 资料,性能也会更慢。 13.1.3 索引组织表上的次要索引 虽然索引组织表本身就是以索引的顺序组织的,但时,有时,我们仍然需要在索引组织 表中的非主键列上创建索引。普通的堆表中有好几个索引是很平常的事,也是经常的需要。 索引组织表有时同样也需要多个索引。在索引组织表上创建索引和在普通堆表上创建索引方 法是一样的。如下语句在索引组织表 T3_IOT 的 NAME 列上创建索引: SQL> create index t3_iot_name on t3_iot(name); 索引已创建。 像这样索引组织表上非主键列上的索引我们经常称为次要索引(secondary index)。索 引组织表的次要索引有一个最重要的资料,我们可以在 DBA_INDEXES 视 图 中 的 PCT_DIRECT_ACCESS 列查看它。它的意义是次要索引的猜测比例。下面我们就来说一说什么 是猜测比例。 在通过次要索引访问索引组织表时,有时需要进行猜测。 我们在通过堆表索引访问堆表时,主要靠存储在堆表索引中的 ROWID。堆表行的 ROWID 中包含行所在的文件号、块号和行号,通过 ROWID,Oracle 可以以最快的速度找到行。堆表 的 ROWID 也称为物理 ROWID。索引组织表的 ROWID 则不同,它被称为 UROWID,也被称为逻辑 ROWID,它的主要成份是主键。因为 ROWID 是和行位置相关的,在索引组织表中,行的位置 就是由主键决定的。由此,我们要明白两点:一是索引组织表的行 ROWID 是可变的。二是我 们通过索引组织表的 UROWID 访问行的过程,其实就是通过主键索引查找行的过程。好,下 面就这两点我们分别证明一下: 首行,我们说明索引组织表的 UROWID 是可变的,证明此点非常简单,只要改变主键的 值就行了: 步 1:显示改变前 UROWID 的值: SQL> select rowid,id,name from t4_iot; ROWID ID NAME -------------------- ---------- ------- ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 273 *BAEAFDwCwQL+ 1 aaa *BAEAFDwCwQP+ 2 bbb 步 2:更改主键的值: SQL> update t4_iot set id=3 where id=1; 已更新 1 行。 SQL> commit; 提交完成。 步 3:再次显示 UROWID: SQL> select rowid,id,name from t4_iot; ROWID ID NAME -------------------- ---------- ------ *BAEAFDwCwQP+ 2 bbb *BAEAFDwCwQT+ 3 aaa 注意NAME值为“aaa”的行,它的UROWID值从“*BAEAFDwCwQL+”,变为了“*BAEAFDwCwQT+”。 UROWID 之所以会变,道理很简单。无论是 UROWID 还是 ROWID,它都是行位置的代表,现在 “aaa”的行位置发生了变化,代表它的位置的 UROWID 当然要随之变化了。因为索引组织表 行的位置是按主键值组织的,只要改变了主键的值,行的位置很可能会发生变化。 好,下面我们再来说第二点,通过 UROWID 访问行,其实就是通过主键索引访问行。这 一点非常容易证明,只要查看一下执行计划就可以了: SQL> set autot on exp; SQL> select * from t4_iot where rowid='*BAEAFDwCwQT+'; ID NAME ---------- ------------------------------ 3 aaa 执行计划 ---------------------------------------------------------- Plan hash value: 520929525 --------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 63 | 1 (0)| 00:00:01 | |* 1 | INDEX UNIQUE SCAN| SYS_IOT_TOP_52024 | 1 | 63 | 3 (34)| 00:00:01 | --------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access(ROWID=:B1) Note ----- - dynamic sampling used for this statement 访问路径是“INDEX UNIQUE SCAN”,索引唯一扫描,扫描的索引是“SYS_IOT_TOP_52024”, 它正是索引组织表主键的索引。 其实 UROWID 中主要部分是主键,除主键外它里面也包含了文件号、块号这些行的物理 位置信息。只不过在有些情况下,行的物理位置发生变化时,UROWID 中的行物理位置信息 不会随之变化,这时根据 UROWID 中的物理位置访问行,将找不到真正的目标行。此时,就 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 274 需要利用 UROWID 中的主键值,在索引组织表的主键索引的 B 树查找,才能最终确定目标行 在表中的真正位置。 Oracle 在根据 UROWID 访问行时,先会根据 UROWID 中的物理位置查找行,如果找不到 了,再根据主键值在索引组织表的 B 树中进行查找。根据物理位置查找行的过程,就是猜测。 如果猜中的话,访问速度是很快的,而且不需要额外的 I/O。如果猜测失败,就需要到索引 组织表的 B 树索引中进行查找了,这需要额外的一些 I/O,I/O 的数量因 B 树大小而不同。 UROWID 中的物理位置之所以会不再准确,主要是因为行位置变动引起的。索引组织表 行的位置在两种情况下会变动。一个就是我们上面试过的,当主键值被修改时,行的位置自 然就发生了变化。在这种情况下,UROWID 中的物理位置也会随之变化,因为主键值在改变 时,要马上修改 UROWID 中的主键值部分,随带着也会更新 UROWID 中的物理位置部分。也就 是说这一种情况不会引起用 UROWID 访问行时的猜测错误。另一种行位置的变动情况是索引 块的分裂。索引组织表本身是一个索引,当索引块分裂时,部分行的位置分发化变化。因为 此时主键值并无变化,ORACLE 不会去修改相关行的 UROWID 值。因此,当索引块分裂后,受 影响的行其 UROWID 中的物理位置将不再指向行现在的位置,这时就会发生根据 UROWID 访问 行猜测失败的情况,这时将不得不使用 UROWID 中的主键值进行查找。 我们一直在说 UROWID,下面回到次要索引上来。我们知道,索引中要保存行的 ROWID, 在索引组织表中,没有物理的 ROWID,只有 UROWID。因此,次要索引中记录的就是行的 UROWID。 当你每次改变主键值时,行的 UROWID 变化,次要索引中的 UROWID 也会随之被修改。也就是 说次要索引中的 UROWID,将会和表中的 UROWID 时刻的保持一致。 根据 UROWID 访问行,我们上面说过了,是有一个猜测过程的。如果你想查看通过次要 索引访问表时,猜测命中的比例,就可以查看 DBA_INDEXES 视图中的 PCT_DIRECT_ACCESS 列。这一列必须在收集资料后才能看到。 下面,我们来试一下改变主键还有索引块分裂对猜测比率的影像。为了看到索引块分裂, 我重新建立一个索引组织表,它有一个很大的列: 步 1:创建索引组织表: SQL> create table iot1(id number(5), name char(1000),primary key(id)) organization index; 表已创建。 它有一个长度 1000 字节的列,我的块大小是 8K,这样一个块中最多插入 7 行,到第 8 行时,就会分裂。 步 2:插入 7 行,并观察 PCT_DIRECT_ACCESS insert into iot1 values(1,'aaa'); insert into iot1 values(2,'bbb'); insert into iot1 values(3,'ccc'); insert into iot1 values(4,'ddd'); insert into iot1 values(5,'eee'); insert into iot1 values(6,'fff'); insert into iot1 values(7,'ggg'); commit; 观察 PCT_DIRECT_ACCESS: SQL> exec dbms_stats.gather_table_stats(user,'IOT1',cascade=>TRUE); PL/SQL 过程已成功完成。 SQL> select index_name,PCT_DIRECT_ACCESS,leaf_blocks,blevel from user_indexes where index_name='IOT1_NAME'; ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 275 INDEX_NAME PCT_DIRECT_ACCESS LEAF_BLOCKS BLEVEL ------------------------------ ----------------- ----------- ---------- IOT1_NAME 100 1 1 可以看到,现在 PCT_DIRECT_ACCESS 是 100,也就是猜测的命中率是 100%。这时通过 次要索引访问索引组织表的效率是比较高的。 下面我插入第 8 条记录: 步 3:插入第 8 行: SQL> insert into iot1 values(8,'hhh'); 已创建 1 行。 SQL> commit; 提交完成。 步 4:查看猜测率: SQL> exec dbms_stats.gather_table_stats(user,'IOT1',cascade=>TRUE); PL/SQL 过程已成功完成。 SQL> select index_name,PCT_DIRECT_ACCESS,leaf_blocks,blevel from user_indexes where index_name='IOT1_NAME'; INDEX_NAME PCT_DIRECT_ACCESS LEAF_BLOCKS BLEVEL ------------------------------ ----------------- ----------- ---------- IOT1_NAME 0 2 1 猜测率变为了 0,这主要是因为第一块分裂,要增加根块,变动比较大,每一行都被移 到了新的块中所至。要想使猜测率升高很简单,只需重建索引即可: SQL> alter index iot1_name rebuild; 索引已更改。 SQL> exec dbms_stats.gather_table_stats(user,'IOT1',cascade=>TRUE); PL/SQL 过程已成功完成。 SQL> select index_name,PCT_DIRECT_ACCESS,leaf_blocks,blevel from user_indexes where index_name='IOT1_NAME'; INDEX_NAME PCT_DIRECT_ACCESS LEAF_BLOCKS BLEVEL ------------------------------ ----------------- ----------- ---------- IOT1_NAME 100 2 1 重建后,猜测率变为了 100%。 我们可以再试一下,更新主键列引起的行位置变化,是不会影响猜测过程的。因为在改 变次要索引中主键值的同时,也更新了 UROWID 中行的物理位置: SQL> update iot1 set id=9 where id=1; 已更新 1 行。 SQL> commit; 提交完成。 SQL> exec dbms_stats.gather_table_stats(user,'IOT1',cascade=>TRUE); PL/SQL 过程已成功完成。 SQL> select index_name,PCT_DIRECT_ACCESS,leaf_blocks,blevel from user_indexes where index_name='IOT1_NAME'; INDEX_NAME PCT_DIRECT_ACCESS LEAF_BLOCKS BLEVEL ------------------------------ ----------------- ----------- ---------- IOT1_NAME 100 3 1 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 276 13.1.4 映像表与位图索引 如果要在索引组织表上创建次要的位图索引,必需要先在索引组织表上创建映像表。下 面,我在 T3_IOT 的 NAME 列上创建位图索引,我还没有为 T3_IOT 创建映像表,结果如下: SQL> create bitmap index t3_name on t3_iot(name); create bitmap index t3_name on t3_iot(name) * 第 1 行出现错误: ORA-28669: 在没有映射表的情况下, 不能在 T3_IOT 上创建位图索引 在创建索引组织表时,只需要加上“MAPPING TABLE”子句,就可以创建映像表。在索 引组织表创建之后,可以通过如下语句添加映像表: SQL> alter table t3_iot MOVE MAPPING TABLE; 表已更改。 映像表名称的格式是 SYS_IOT_MAP_对象 ID。 SQL> select object_id from dba_objects where object_name='T3_IOT'; OBJECT_ID ---------- 51936 SQL> col SYS_NC_01 for a30 SQL> select * from sys_iot_map_51936 where rownum<=2; SYS_NC_01 ------------------------------ *BAAAAAACwQL+ *BAFAAE0CwQP+ 映像表只有一列:SYS_NC_01,保存表的 UROWID。映像表的 UROWID 会随着表中主键的 变化而变化。位图索引中记录对应映像表的地址。每次通过位图索引访问表时,都要先通过 位图索引在映像表注找到目标行的 UROWID,然后根据此 UROWID 访问表中的行。我们前面说 过,根据 UROWID 访问行,有一个猜测过程。因此位图索引也有猜测比率这个概念。查看位 图索引猜测比率和查看一般的次要索引一样,我就不再重复了。 要使用非常简单: SQL> alter table t2 MINIMIZE RECORDS_PER_BLOCK; 表已更改。 如下命令可以删除映像表: SQL> alter table t3_iot move NOMAPPING; 表已更改。 当映像表被删除后,相应的位图索引将随之被删除。 13.1.5 溢出段 看下面这种情况,索引组织表 AA 中有一个备注列,此列的最大长度是 2000 字节。因为 备注信息有可能是比较长一些。这样一来,块大小按 8K 算,每个索引叶块中只能存放少量 的行。如果此备注列平常很少使用,只有某些特殊的、很少使用的应用程序,才会查询此备 注列。这样,让备注列和 AA 表的其他列放在一起,将大量增加查询其他列时的 I/O,严重 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 277 的影响索引组织表的性能。索引组织表的“溢出”就是为这个目的而设计的。我们可以选择 将备注列“溢出”到另一段(称为溢出段)中,而索引组织表中的每一行中,只存放对应备 注列的地址。这样,访问其他列而不访问备注列时,所需的 I/O 将大大的减少,性能也可以 因此而大大提高。这就是溢出段的主要作用。下面我们介绍一下溢出段的使用。 在创建索引组织表时,如果使用了 OVERFLOW 选项,将会自动创建溢出段,溢出段的名 字是 SYS_IOT_OVER_,溢出段不能直接访问,只能通过索引组织表访问。另有 两个选项决定了那些列被放在溢出段中:  PCTTHRESHOLD :此选项是一个比例,如果此选项取值 10,块大小是 8K,那么,如果行 的长度大于 8K 的 10%,也就是 800 字节时,超出的列会被存到溢出段中。此参数的缺 省值是 50,取值范围是 0 至 50。  INCLUDING :按创建索引组织表时列的顺序,从第一列到此选项指定的列(也包括此列) 都存储在索引块中,剩下的列存储到溢出段中。 这两个选项中,在你可以明确确定某些列是很少访问的列时,可以使用 INCLUDING,将 这些列放在溢出段中。如果在不能明确确定哪些列较少访问,但是表可能会有一些比较大的 列时,可以使用 PCTTHRESHOLD,将大小超过某一比例值的某些列送到溢出段中。 好,我们创建一个带有溢出段的索引组织表: SQL> create table iot2(id number(5),rq date,bz varchar2(2000),primary key(id)) organization index pctthreshold 5 overflow; 表已创建。 上面的语句创建索引组织表 IOT2,包含有溢出段,溢出规则是行大小超过块大小的 5%, 也就是 400 字节。在 IOT2 表中,Bz 列是一个可变长度的字符型列。如果 ID 列、RQ 列和 BZ 列的合计大小没有超过 400 字节,每一列都不会被放在溢出段中。如果 BZ 列长度比较长, 使整个行的大小超过了 400 字节,Oracle 将按照从后向前的顺序,将后面的列放进溢出段。 在这里,也就是会将 BZ 列放进溢出段。 上面的例子中我使用了 pctthreshold,因此,根据 BZ 列的大小,BZ 列有时会被送进溢 出段,有时不会。如果可以确定 BZ 列是一个很少使用的列,我们可以使用 INCLUDING 指定 无论 BZ 列的大小,都将它送进溢出段中,创建命令如下: SQL> create table iot3(id number(5),rq date,bz varchar2(2000),primary key(id)) organization index tablespace users including rq overflow tablespace tp1; 表已创建。 在 Including 后指定 RQ 列,也就是按照创建列时的顺序,RQ 列前包含 RQ 列在内的列 在索引组织表中,RQ 列后的列,这里就是 BZ 列将被送进溢出段。另外,在上面的语句中我 指定索引组织表本身在 USERS 表空间中,溢出段在 TP1 表空间中。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 278 13.2 索引组织表相关视图 与索引组织表相关的段类型有三种,可以在 DBA_TABLES 中的 IOT_TYPE 列中看到,当此 列值为“IOT”时,代表段是索引组织表段,“IOT_MAPPING”代表是映像表段,“IOT_OVERFLOW” 是溢出段。对于映像表段和溢出段,DBA_TABLES 中的 IOT_NAME 列段对应索引组织表的名字, 下面显示一下此视图: SQL> select table_name,tablespace_name,iot_name,iot_type from user_tables where table_name='IOT3' or iot_name='IOT3'; TABLE_NAME TABLESPACE_NAME IOT_NAME IOT_TYPE ----------------- ------------------------------- ------------------------------ IOT3 IOT SYS_IOT_OVER_52048 TP1 3 IOT_OVERFLOW 根据上面的显示,我们可以了解到溢出段在 TP1 表空间中,名字是 SYS_IOT_OVER_52048, 最后的数字 52048 就是 OBJECT_ID。而在 DBA_TABLES 系列视图中,索引组织表 IOT3 没有显 示出来表空间,这是因为 IOT3 实际上并不是一个表,而是一个索引,下面,查询索引图视 图: SQL> select index_name,tablespace_name from user_indexes where table_name='IOT3'; INDEX_NAME TABLESPACE_NAME ------------------------------ ------------------------------ SYS_IOT_TOP_52048 USERS 可以看到,IOT3 对应的索引是 SYS_IOT_TOP_52048。索引组织表 IOT3 实际的数据都在 索引中存储,此索引段在 USERS 表空间中。因为表 IOT3 并没有存储实际的数据,因此它也 没有实际的段。 第 二 节 索引组织表的相关视图 与索引组织表相关的段类型有三种,可以在 DBA_TABLES 中的 IOT_TYPE 列中看到。  IOT_TYPE 列为 IOT 代表段是索引组织表段,  IOT_TYPE 列为 IOT_MAPPING 代表是映像表段  IOT_TYPE 列为 IOT_OVERFLOW 代表段是溢出 段 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 279 第 14 章 重要的 ORACLE 特性 您将学习: 1. 非 B*树型索引 2. 在线重定义表 3. OLTP 与 OLAP 的注意事项 第 十四 章 重要的 ORACLE 特性 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 280 14.1 非 B*树型索引 14.1.1 索引类型 索引类型共有 B 树索引、位图索引、压缩索引和反向键索引,前两种索引我们已经讲过 了,下面主要介绍一下压缩索引和反向键索引。 14.1.2 压缩索引 压缩索引主要通过减少存储重复值的次数,以减少索引所占空间。在压缩索引中,重复 的列值在同一块中只被存贮一次。压缩索引的创建非常简单,下面我们来看一个例子: 步 1:创建表并插入行: SQL> create table test1 (id1 int,id2 int, id3 int, name varchar2(20)); 表已创建。 SQL> insert into test1 select mod(rownum,50),mod(rownum,100),rownum,'aaa' from dba_objects; 已创建 49880 行。 SQL> commit; 提交完成。 TEST1 表的 ID1 列和 ID2 列都有大量重复值。 步 2:创建普通的 B 树索引: SQL> create index test1_id on test1(id1,id2,id3); 第 一 节 非 B*树型索引 常用的索引中,除 B*树外,还有以下几种  位图索引  压缩索引  反向键索引 其中位图索引我们在前面已经有所介绍,这一部分 我们主要讲述压缩索引和反向键索引的使用。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 281 索引已创建。 索引 TEST1_ID 是复合索引,每个索引关键字包含 ID1、ID2 和 ID3 三个列。下面观察一 下此索引所占用的空间。 步 3:观察索引所占空间: SQL> set serveroutput on SQL> exec sys.show_space('TEST1_ID','U3','INDEX'); Unformatted Blocks...................... 0 FS1 Blocks(0-25)........................ 0 FS2 Blocks(25-50)....................... 1 FS3 Blocks(50-75)....................... 0 FS4 Blocks(75-100)...................... 0 Full Blocks............................. 152 Total Blocks............................ 256 Total Bytes............................. 2,097,152 Total MBytes............................ 2 Unused Blocks........................... 91 Unused Bytes............................ 745,472 Last Used Ext FileId.................... 4 Last Used Ext BlockId................... 5,513 Last Used Block......................... 37 PL/SQL 过程已成功完成。 普通索引共占 256 个块,其中空块 91 个,也就是共使用 165 个块。下面看看压缩索引: 步 4:观察压缩索引所占空间: SQL> drop index test1_id; 索引已删除。 SQL> create index test1_id on test1(id1,id2,id3) compress 2; 索引已创建。 因为 ID3 列并不重复,我只压缩前两列,因此,compress 选项的取值为 2。下面看看压 缩索引所占的空间: SQL> exec sys.show_space('TEST1_ID','U3','INDEX'); Unformatted Blocks...................... 0 FS1 Blocks(0-25)........................ 0 FS2 Blocks(25-50)....................... 1 FS3 Blocks(50-75)....................... 0 FS4 Blocks(75-100)...................... 0 Full Blocks............................. 111 Total Blocks............................ 128 Total Bytes............................. 1,048,576 Total MBytes............................ 1 Unused Blocks........................... 6 Unused Bytes............................ 49,152 Last Used Ext FileId.................... 4 Last Used Ext BlockId................... 5,129 Last Used Block......................... 2 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 282 PL/SQL 过程已成功完成。 共占用块 128 个,未使用块 6 个,共使用 122 个,相比未压缩时的 165 个,节省了 43 个块,约合普通索引的四分之一。 14.1.3 反向键索引 如果有一个索引的列,列值是大都是顺序增加的,如列值可能是 1,2,3,4,5,„„。 对于这样的列,1,2,3 等值必定被插入到同样的块中,如果并发插入较多,必然会造成块 的争用。反向键的作用是将列值取反,再插入到表中。这样一来,1 和 2 可能就不会插入到 同样的块中,因为 1 和 2 的反向值相差很大。我们可以使用 DUMP 函数观察数据值的取值: SQL> select dump(1),dump(2) from dual; DUMP(1) DUMP(2) ------------------ ------------------ Typ=2 Len=2: 193,2 Typ=2 Len=2: 193,3 1 在 Oracle 内部值是“193,002”, 2 的值是“193,003”。它们两个现在是紧挨着的,在 索引中它们应该被插入到同一块中。取反后,1 为“2,193”, 2 是“3,193”,取反后的值相 差很远,在索引中可能会被分别插入到不同的块中。这就是反向键索引的作用,减少顺序增 加列插入时的争用。创建反向键索引非用简单,命令如下: SQL> create unique index test1_id3 on test1(id3) reverse; 索引已创建。 只需要增加 reverse 选项就可以了。 Oracle 将会把 ID3 列的值取反后存储到索引中。如果我们在进行如下的等值查询时: SQL> select * from test1 where id3=3; 执行计划 ---------------------------------------------------------- Plan hash value: 1614522841 ----------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ----------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 51 | 1 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| TEST1 | 1 | 51 | 1 (0)| 00:00:01 | |* 2 | INDEX UNIQUE SCAN | TEST1_ID3 | 1 | | 1 (0)| 00:00:01 | ----------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("ID3"=3) 通过查询执行计划,反向索引可以被使用。Oracle 会将 3 取反,再在索引中进行查找。 但是,如果是范围扫描,将不能再使用反向键索引,只能进行全表扫描: SQL> select * from test1 where id3 between 1 and 2; 执行计划 ---------------------------------------------------------- Plan hash value: 4122059633 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 283 --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 51 | 2 (0)| 00:00:01 | |* 1 | TABLE ACCESS FULL| TEST1 | 1 | 51 | 2 (0)| 00:00:01 | --------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("ID3">=1 AND "ID3"<=2) Note ----- - dynamic sampling used for this statement 即使只查找 ID3 列值为 1 到 2 这期间的行,也必需进行全表扫描,因为在索引中不再是 按照列值本身的顺序存储了,而是按照每个列值的取反后值的顺序进行存储。 最后说明一点,反向键索引和压缩索引都可以在索引创建后,使用重建命令将普通 B 树索引重建为反向键索引或压缩索引。还有,可以创建反向压缩索引,语句如下: SQL> drop index test1_id ; 索引已删除。 SQL> create index test1_id on test1(id1,id2,id3) compress 2 reverse; 索引已创建。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 284 14.2 在线重定义表 14.2.1 在线重定义表 DBA 经常需要对表的结构进行修改,大多数结构的修改,都将对表加独占锁,这将阻塞 所有对表的 DML 操作。这有可能会极大的影响数据库中其他的应用,为此,Oracle 设计了 在线重定义表,使用它会将对表加独占锁的时间减少到最低。不过,在线重定义表的步骤也 非常复杂,下面来看一下它是如何使用的。 14.2.2 实例 步 1:创建单独的用户 SQL> create user u4 identified by abcde; 用户已创建。 SQL> grant dba to u4; 授权成功。 SQL> conn u4/abcde 已连接。 步 2:创建练习用表 SQL> create table test1(a int, b int); 表已创建。 第 二 节 在线重定义表 DBA 经常需要对表的结构进行修改,大多数结构的 修改,都将对表加独占锁,这将阻塞所有对表的 DML 操作。这有可能会极大的影响数据库中其他的应用, 为此,Oracle 设计了在线重定义表,使用它会将对表 加独占锁的时间减少到最低。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 285 SQL> insert into test1 select rownum,100-rownum from dba_objects where rownum<=100; 已创建 100 行。 SQL> commit; 提交完成。 我们下面的目的,是将此表重定义为一个分区表。 步 3:为表添加触发器、主键约束、索引 SQL> create table audit_test1(c int); 表已创建。 SQL> create or replace trigger tr_test1 2 before insert or update or delete on test1 3 declare 4 pragma autonomous_transaction; 5 begin 6 update audit_test1 set c=c+1; 7 end; 8 / 触发器已创建 上面的这些语句创建了一个 Audit_test1 表和 TEST1 表上的触发器,触发器的动作非常 简单,就是当有 DML 时,将 Audit_test1 表的 C 列值加 1。 SQL> insert into audit_test1 values(0); 已创建 1 行。 SQL> commit; 提交完成。 Audit_test1 表必须先有一行,上面的语句为 Audit_test1 插入一行。 下面再为表分别添加一个主键约束和一个唯一约束: SQL> alter table test1 add constraint pk_test1_a primary key(a); 表已更改。 SQL> alter table test1 add constraint un_test1_b unique(b); 表已更改。 步 4:检查 TEST1 表是否可以在线重定义 SQL> exec dbms_redefinition.can_redef_table('U4','TEST1'); PL/SQL 过程已成功完成。 这一步非常简单,只需调用 can_redef_table 即可。如果过程成功完成,就说明表可以 在线重定义。如果报出错误终止,就说明表不符合重定义的要求。比如: SQL> exec dbms_redefinition.can_redef_table('U4','AUDIT_TEST1'); BEGIN dbms_redefinition.can_redef_table('U4','AUDIT_TEST1'); END; * 第 1 行出现错误: ORA-12089: 不能联机重新定义无主键的表 "U4"."AUDIT_TEST1" ORA-06512: 在 "SYS.DBMS_REDEFINITION", line 137 ORA-06512: 在 "SYS.DBMS_REDEFINITION", line 1478 ORA-06512: 在 line 1 错误原因很简单,AUDIT_TEST1 表没有主键。 步 5:创建中间表: ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 286 SQL> create table md_test1( a int , b int , c int) partition by range(a) 2 ( partition p1 values less than(100), 3 partition p2 values less than(1000) 4 ); 表已创建。 步 6:在线重定义: SQL> exec dbms_redefinition.start_redef_table('U4','TEST1','MD_TEST1','a a,b b,0 c'); PL/SQL 过程已成功完成。 这个函数中参数的意义是: start_redef_table('用户名','原表','中间表','原表列1中间表列1, 原表列2 中间表列 2 ,„„ ') 步 7:检查重定义结果 SQL> select count(*) from md_test1; COUNT(*) ---------- 100 数据已经转到了 MD_TEST1 中,不过原表中仍然保存的有数据: SQL> select count(*) from test1; COUNT(*) ---------- 100 MD_TEST1 表比原表多了一个列: SQL> desc md_test1; 名称 是否为空? 类型 ----------------------------------------- -------- ------------- A NUMBER(38) B NUMBER(38) C NUMBER(38) 步 8:在中间表上创建主键 SQL> alter table md_test1 add constraint md_pk_test1_a primary key(a); 表已更改。 SQL> create or replace trigger md_tr_test1 2 before insert or update or delete on md_test1 3 declare 4 pragma autonomous_transaction; 5 begin 6 update audit_test1 set c=c+2; 7 commit; 8 end; 9 / 触发器已创建 此触发器每次被触发后,C 列的值将加 2。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 287 步 9:执行表的同步 SQL> exec dbms_redefinition.sync_interim_table('U4','TEST1','MD_TEST1'); PL/SQL 过程已成功完成。 在创建中间表、并执行 start_redef_table 后,Oracle 将为原表创建一个日志表,所 有对原表的 DML 将被记入日志表,执行此同步函数的作用,就是将日志表中所有的修改,再 对中间表作一次。此函数执行完后,将把日志表中已经应用到中间表上的修改删除。 步 10: DBMS_REDEFINITION.FINISH_REDEF_TABLE ( uname IN VARCHAR2, orig_table IN VARCHAR2, int_table IN VARCHAR2, part_name IN VARCHAR2 := NULL); SQL> exec dbms_redefinition.finish_redef_table('U4','TEST1','MD_TEST1'); PL/SQL 过程已成功完成。 此函数将日志表的所有修改应用到中间表,然后,将中间表与原表名字对换。对换完成 后,中间表将换成原表,原表将变为中间表。 下面我们查看一下对换结果,先看一下约束的情况: SQL> select CONSTRAINT_NAME,CONSTRAINT_TYPE,TABLE_NAME from user_constraints; CONSTRAINT_NAME C TABLE_NAME ------------------------------ - ------------------------------ PK_TEST1_A P MD_TEST1 UN_TEST1_B U MD_TEST1 MD_PK_TEST1_A P TEST1 我显示当前用户下所有的约束,从中可以看到,中间表上有主键约束和唯一约束,而原 表只有主键约束。因为原表是刚才的中间表,所有,原表的约束,就是刚才中间表的约束。 索引的情况和约束一样: SQL> select index_name,TABLE_NAME from user_indexes; INDEX_NAME TABLE_NAME ------------------------------ ------------------------------ PK_TEST1_A MD_TEST1 TEST1_B MD_TEST1 MD_PK_TEST1_A TEST1 再看看触发器: SQL> select * from audit_test1; C ---------- 6 现在 C 列值为 6。修改 TEST1 表中的一行: SQL> update test1 set b=b+0 where a=3; 已更新 1 行。 再看 AUDIT_TEST1: SQL> select * from audit_test1; C ---------- ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 288 8 变为了 8。在重定义前,中间表的触发器是一次加 2。这说明现在 TEST1 表上的触发器, 也是重定义前中间表上的。 其他我就不再试了,总之,在执行 finish_redef_table 完成重定义前,中间表上有什 么约束、索引、触发器、权限,在 finish_redef_table 完成重定义后,都会被变成原表的。 Oracle 在执行 finish_redef_table 期间,首先将日志表中的修改应用到中间表上,然后, 封锁所有修改,对换原表和中间表的名字,以及修改原表、中间表的所有索引等这些属性。 将中间表所有属性改为原表的,将原表改为中间表的,然后解开封锁,重定义结束。原表就 变为了过去的中间表,原表的一切特性,都是 finish_redef_table 前中间表的。整个重定 义期间,只有最后的对换表名、修改所有属性、所有对象这一步是要封锁表的。不过,这一 步只是修改数据字典,速度是很快的。其他各步骤,对表并不加锁。对原表可以正常修改。 重定义后的原表,所有的索引、约束名字都变成了过去中间表的,如认为有必要,可以 使用重命名命令,将索引、约束、触发器的名字改过了。 下面是更改约束名的例子: SQL> alter table md_test1 rename constraint PK_TEST1_A to md_test1_a1; 表已更改。 SQL> alter table test1 rename constraint md_pk_test1_a to PK_TEST1_A; 表已更改。 更改索引名: SQL> alter index pk_test1_a rename to pk_test1_a1; 索引已更改。 SQL> alter index md_pk_test1_a rename to pk_test1_a; 索引已更改。 更改触发器: SQL> alter trigger tr_test1 rename to tr_test2; 触发器已更改 SQL> alter trigger md_tr_test1 rename to tr_test1; 触发器已更改 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 289 14.3 OLTP 与 OLAP 的注意事项 14.3.1 OLTP 型应用 1.特点 高吞吐量,插入和更新巨烈 数据持续的、大量的增加 众多用户的并发访问 2.调优目标 高可用性(24×7)、高并发性、高速度、保护数据安全减少与恢复时间。 3.具体方法 (1)、空间分配:要随时调查表空间分配的情况,发现索引、表空间不足时,即时的手动为 表、索引分配区。最后不要让系统自动分配空间。 (2)、索引:索引有利于提高查询性能,但增加了修改时的负担。因此,要把握好索引的数 量。对于外键列,最好要有索引,这样可以减少对父表加锁的次数。应该定期的重建索引。 (3)、要注意合理的使用索引簇、HASH 簇、索引组织表还有物化视图等这些特性。 4.需要注意的问题 对于 OLTP,SQL 语句以及 PL/SQL 代码的共享是关键。为此,应该设置合理的共享池大 小,还要注意绑定变量以及 CURSOR_SHAREING 参数。 第 三 节 OLTP 与 OLAP 的注意事项 要注意 OLTP 与 OLAP 各自的:  特点的不同  调优目标的不同  具体方法的不同  各自需要注意的问题 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 290 14.3.2 OLAP 型应用 1.通常的注意事项: OLAP 型应用通常都是查询巨大量的数据,大量使用全表扫描。调优的目标是快速的响 应时间,调优的焦点是 SQL 声明。可以使用并行查询来提高速度。 注意 DB_FILE_MULTIBLOCK_READ_COUNT 参数和区大小的设置。 对基于主键的查询使用索引组织表。 对分布不均匀的列使用柱状图。 使用合适类型的聚簇 2.索引 在 OLAP 型应用中,位图索引被大量使用。而在 OLTP 中,位图索引则很少使用。这是因 为位图索引只适合 DML 操作很少且重复值很高的环境,OLTP 中即使重复值很高,但由于 DML 操作过多,因此一般不适合使用位图索引。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 291 第 15 章 分区 您将学习: 1. 区间分区 2. HASH 分区 3. 列表分区 4. 组合分区 5. 局部索引分区 6. 全局索引分区 第 十五 章 分区 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 292 15.1 区间分区 15.1.1 分区 分区的作用就是把一个很大的表分成多个较小的部分存贮。其实每个较小的部分都被算 作一个单独的表,可以分别存储在不同的表空间中。分区有很多优点,当然也有很多注意事 项。任何一个工具,如果使用错误,不但不会提高性能反而会带来负担。下面我们通过区间 分区,介绍一下分区的优、缺点。 15.1.2 创建和管理区间分区 如下的语句创建一个区间分区表 Range1: SQL> create table range1(rk date, id number(10)) partition by range(rk) 2 ( partition p1 values less than (to_date('2005-01-01','yyyy-mm-dd')), 3 partition p2 values less than (to_date('2006-01-01','yyyy-mm-dd')) 4 ); 表已创建。 上面的语句中, partition by range(rk)子句中,指定 RK 列为分区列,也叫分区关键 字。Range1 共包含两个区,分别是 P1 和 P2,P1 包含 RK 在 2005 年 1 月 1 日之前的行,P2 区包含 RK 在 2006 年 1 月 1 日之前的行。 插入如下四行: insert into range1 values(to_date('2004-08-20','yyyy-mm-dd'),1); insert into range1 values(to_date('2004-10-24','yyyy-mm-dd'),2); 第 一 节 区间分区  分区  创建和管理区间分区  分区消除  行移动  分区资料的收集 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 293 insert into range1 values(to_date('2005-3-16','yyyy-mm-dd'),3); insert into range1 values(to_date('2005-5-19','yyyy-mm-dd'),4); commit; 其中,前两行在 P1 区中,后两行在 P2 区中。分区表的所有操作也都是透明的,插入、 删除、更新都和普通表一样。下面是显示分区表: SQL> select * from range1; RK ID -------------- ---------- 20-8 月 -04 1 24-10 月-04 2 16-3 月 -05 3 19-5 月 -05 4 如果只想显示某一个分区中的行,可以使用如下语句: SQL> select * from range1 partition(p1); RK ID -------------- ---------- 20-8 月 -04 1 24-10 月-04 2 上面的语句只显示 P1 区中的行。 上面的分区表 Range1,分区列最大的值是 2005 年 12 月 31 日,如果插入的日期超过了 这一数字,插入将不会成功,因为没有分区可以接纳它。 SQL> insert into range1 values(to_date('2006-1-1','yyyy-mm-dd'),5); insert into range1 values(to_date('2006-1-1','yyyy-mm-dd'),5) * 第 1 行出现错误: ORA-14400: 插入的分区关键字未映射到任何分区 另外,空值也不能插入到 Range1 表中: SQL> insert into range1 values(null,5); insert into range1 values(null,5) * 第 1 行出现错误: ORA-14400: 插入的分区关键字未映射到任何分区 如果想让任何日期值都可以插入 Range1 表,可以如下修改 Range1 表: SQL> alter table range1 add partition p3 values less than (maxvalue); 表已更改。 这条语句为 Range1 表添加了一个 P3 分区,它可以接受不在 P1 和 P2 范围内的区。空值 也会被插入到这个新的 P3 分区中: SQL> insert into range1 values(null,5); 已创建 1 行。 SQL> insert into range1 values(to_date('2006-1-1','yyyy-mm-dd'),6); 已创建 1 行。 SQL> commit; 提交完成。 SQL> select * from range1 partition(p3); ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 294 RK ID -------------- ---------- 5 01-1 月 -06 6 空值和大于 P2 区范围的值,都在 P3 区中。 我们可以使用如下语句删除分区: SQL> alter table range1 drop partition p3 ; 表已更改。 分区内所有的行将被随之删除: SQL> select * from range1 partition(p3); select * from range1 partition(p3) * 第 1 行出现错误: ORA-02149: 指定的分区不存在 SQL> select * from range1; RK ID -------------- ---------- 20-8 月 -04 1 24-10 月-04 2 16-3 月 -05 3 19-5 月 -05 4 不再有 ID 为 5、6 的两行。 15.1.3 分区消除 分区消除(也可以称为分区剪切)这是在分区中常见到一个名词。如果可以分区消除, 将可以大幅度提高语句速度。那么,什么是分区消除呢?就是优化器根据语句中的条件,可 以确定只访问部分分区就可以满足用户的需求,而不必访问表的所有分区。这就是分区消除, 例如,看下面的例子: SQL> set autot trace exp; SQL> select * from range1 where rk <= to_date('2004-9-1','yyyy-mm-dd'); 执行计划 ---------------------------------------------------------- Plan hash value: 3310246912 -------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | ------------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1 | 22 | 2 (0)| 00:00:01 | | | | 1 | PARTITION RANGE SINGLE| | 1 | 22 | 2 (0)| 00:00:01 | 1 | 1 | |* 2 | TABLE ACCESS FULL | RANGE1 | 1 | 22 | 2 (0)| 00:00:01 | 1 | 1 | ------------------------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter("RK"<=TO_DATE('2004-09-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss')) ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 295 上面的执行计划中有一个“PARTITION RANGE SINGLE”,这就证明为了选择出用户所要 求的数据,优化器发现,只用访问一个分区就行了。“SINGLE”就是“单个”的意思。这就 是分区消除。 再为 Range1 表添加一个分区: alter table range1 add partition p3 values less than (maxvalue); insert into range1 values(null,5); insert into range1 values(to_date('2006-1-1','yyyy-mm-dd'),6); 下面再试一下如果我的条件必需扫描三个分区才可以找到的情况: SQL> select * from range1 where rk < to_date('2006-2-1','yyyy-mm-dd'); 执行计划 ---------------------------------------------------------- Plan hash value: 282449168 ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | -------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 22 | 2 (0)| 00:00:01 | | | | 1 | PARTITION RANGE ALL| | 1 | 22 | 2 (0)| 00:00:01 | 1 | 3 | |* 2 | TABLE ACCESS FULL | RANGE1 | 1 | 22 | 2 (0)| 00:00:01 | 1 | 3 | -------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter("RK" select * from range1 where rk < to_date('2006-1-1','yyyy-mm-dd'); 执行计划 ---------------------------------------------------------- Plan hash value: 1281315934 ------------------------------------------------------------------------------------------------- | Id | Operation | Name | Cost (%CPU)| Time | Pstart| Pstop | ----------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 2 (0)| 00:00:01 | | | | 1 | PARTITION RANGE ITERATOR| | 2 (0)| 00:00:01 | 1 | 2 | | 2 | TABLE ACCESS FULL | RANGE1 | 2 (0)| 00:00:01 | 1 | 2 | ------------------------------------------------------------------------------------------------- (注意,由于版面限制,去除了部分与此例子无关的列) “ITERATOR”,有重复的意思,此访问路径的意思就是需要重复的扫描一个又一个分区, 但不需扫描所有分区,即可查询出所有满足条件的行。这种情况当然也是进行了分区消除。 分区消除后,不需要访问的分区即使损坏了,用户的操作照样可以完成。关于这一点, 我们来实验一下。 首先,我们要再增加一个在其他表空间中的分区,为了再增加分区,必须删除 P3 区。 只要有“values less than (maxvalue)”的分区在,新增分区操作将不成功,下面是它报 出的错误: ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 296 SQL> alter table range1 add partition p4 values less than (to_date('2007-01-01','yyyy-mm-dd')) tablespace tp1 ; alter table range1 add partition p4 values less than (to_date('2007-01-01','yyyy-mm-dd')) tablespace tp1 * 第 1 行出现错误: ORA-14074: 分区界限必须调整为高于最后一个分区界限 无论最后一个分区 P3 中,是否含有可能在新增区范围中的行,只要表已经包含了 P3 这样类型范围为 maxvalue 的区,就算是区中没有一行,新增区的操作,都为报出上面的错 误。我们必须新删除 P3 区,才能再新增分区。如果 P3 区中有重要的数据,可以新将数据转 移到其他表中。我下面直接删除 P3 区,然后增加 P4 区,它在另一表空间中: SQL> alter table range1 drop partition p3; 表已更改。 SQL> alter table range1 add partition p4 values less than (to_date('2007-01-01','yyyy-mm-dd')) tablespace tp1 ; 表已更改。 P4 区在 TP1 表空间中。值的范围是分区列大于等于 2006 年 1 月 1 日且小于等于 2006 年 12 月 31 日的行。下面插入一个在此范围中的行: SQL> insert into range1 values(to_date('2006-1-1','yyyy-mm-dd'),5); 已创建 1 行。 SQL> commit; 提交完成。 查看一下 range1 中的所有行: SQL> select * from range1; RK ID -------------- ---------- 20-8 月 -04 1 24-10 月-04 2 16-3 月 -05 3 19-5 月 -05 4 01-1 月 -06 5 如果语句中的条件是“rk < to_date('2006-1-1','yyyy-mm-dd')”,优化器根据记录在 数据字典中分区的元数据(也就是分区的定义),将可以判断出只需扫描 P1、P2 这两个分区, 就可以找出所有满足条件的行,这个过程根本不必访问 P1 到 P4 这四个分区,因此,此语句 执行时,就算是 P3 和 P4 分区损坏了,语句仍可以成功执行: SQL> alter tablespace tp1 offline ; 表空间已更改。 SQL> select * from range1 where rk < to_date('2006-1-1','yyyy-mm-dd'); RK ID -------------- ---------- 20-8 月 -04 1 24-10 月-04 2 16-3 月 -05 3 19-5 月 -05 4 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 297 但此时如果对 Range1 进行全表扫描的话,将会报出错误: SQL> select * from range1; ERROR: ORA-00376: 此时无法读取文件 5 ORA-01110: 数据文件 5: 'D:\ORACLE\PRODUCT\10.2.0\ORADATA\ONE1\TP1.DBF' 未选定行 这说明分区的消除不但可以提高速度,还可以提高可用性。在一部分数据已损坏的情况 下,只要不访问这部分已经损坏的数据,整个表还可以正常的访问,可用性比普通表要好一 些。 15.1.4 行移动 我们以前的更新命令不会改变行的 ROWID,也就是行的物理位置不会发生变化,但在分 区中则不一定。如果分区列被修改,修改后的值应该在别的分区中,行就必须从此分区中移 到另一区中。另一区很可能和前一区并不隶属同一表空间,当然,也就不属于同一数据文件。 这样的话,改变后行的 ROWID 一定会发生变化。按照文档中标准的说法,一个分区表的每一 个区都是一个独立的段,称为分区段,如果行从一个分区段被移到了另一个分区段,那么它 的 ROWID 一定会发生变化。下面我们来实验一下这种情况: SQL> update range1 set rk=to_date('2005-1-1','yyyy-mm-dd') where id=1; update range1 set rk=to_date('2005-1-1','yyyy-mm-dd') where id=1 * 第 1 行出现错误: ORA-14402: 更新分区关键字列将导致分区的更改 这条语句将会失败,原因是在默认情况下,分区表并不支持行的移动。下面的语句将使 Range1 表的行可以被移动: SQL> alter table range1 enable row movement; 表已更改。 在移动前我先显示一下行的 ROWID: SQL> select rowid from range1 where id=1; ROWID ------------------ AAAMnnAAEAAAAmcAAA 修改行: SQL> update range1 set rk=to_date('2005-1-1','yyyy-mm-dd') where id=1; 已更新 1 行。 修改后行的 ROWID: SQL> select rowid from range1 where id=1; ROWID ------------------ AAAMnoAAEAAAAmkAAC 行移动完成的操作将和删除再重新插入行类似。这比正常的在区内的更新要消耗更多的 资源,我们在设计分区表时,应该尽量避免这种情况的出现。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 298 15.1.5 分区资料的收集 Oracle 提供了两套命令收集资料,一套是 Analyze 另一套是 DBMS_STATS 包,有关分区 的资料,我们一定要使用 DBMS_STATS 包。因为 Analyze 有时会收集到错误的资料。而且 DBMS_STATS 包可以并行收集资料,而 Analyze 只 能 串 行 的 收 集 。 DBMS_STATS.GATHER_TABLE_STATS 在收集分区资料时,有个粒度选项:granularity。使用 它我们可以选择在全局层、分区层、子分区层收集资料。 在全局层收集资料是只收集表层资料,并不涉及各个分区的资料。下面看一个例子: SQL> exec dbms_stats.delete_table_stats('U3','PT3'); PL/SQL 过程已成功完成。 先删除原来收集的资料。 SQL> exec dbms_stats.gather_table_stats('U3','PT3',granularity=>'GLOBAL'); PL/SQL 过程已成功完成。 已用时间: 00: 00: 00.60 查看收集结果。表层资料在 DBA_TABLES 系列视图中显示,而分区层资料,则是在 dba_tab_partitions 系列视图中显示: SQL> select table_name, partition_name ,num_rows, blocks, EMPTY_BLOCKS, avg_space, avg_row_len, global_stats from user_tab_partitions 2 union all 3 select table_name, ' 无' partition_name ,num_rows, blocks, EMPTY_BLOCKS, avg_space, avg_row_len, global_stats from user_tables where table_name='PT3'; TABLE_NAME PARTITION_ NUM_ROWS BLOCKS EMPTY_BLOCKS AVG_SPACE AVG_ROW_LEN GLO ---------- ---------- ---------- ---------- ------------ ---------- ----------- --- PT3 FY_2006 NO PT3 FY_2005 NO PT3 FY_2007 NO PT3 无 99652 365 0 0 12 YES 注意,最后一个列:global_stats。它的作用是指示表的资料是作为一个整体被收集的, 还是由下层各个分区中求和或求均计算出来的。此处 global_stats 列显示为“YES”,即 PT3 表的全局层资料是真正收集出来的,而各个分区则没有资料。这条命令的稳定用时约是 0.60 秒左右。下面再看看在分区层收集资料: SQL> exec dbms_stats.gather_table_stats('U3','PT3',granularity=>'PARTITION'); PL/SQL 过程已成功完成。 已用时间: 00: 00: 01.30 上面的语句选择在分区层收集资料。 SQL> select table_name, partition_name ,num_rows, blocks, EMPTY_BLOCKS, avg_space, avg_row_len, global_stats from user_tab_partitions 2 union all 3 select table_name, ' 无' partition_name ,num_rows, blocks, EMPTY_BLOCKS, avg_space, avg_row_len, global_stats from user_tables where table_name='PT3'; TABLE_NAME PARTITION_ NUM_ROWS BLOCKS EMPTY_BLOCKS AVG_SPACE AVG_ROW_LEN GLO ---------- ---------- ---------- ---------- ------------ ---------- ----------- --- PT3 FY_2006 49335 180 0 0 12 YES PT3 FY_2005 49317 180 0 0 12 YES ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 299 PT3 FY_2007 1000 5 0 0 11 YES PT3 无 99652 365 0 0 11 NO 在分区层收集资料是只对各个分区收集资料,表层的资料是根据收集到的分区层资料估 算出来的,因此 global_stats 列显示为“NO”。这条命令稳定耗时约为 1.30 秒。比刚才只 在表层收集全局资料所耗时间多出了一倍。 下面,再来试同时收集表层、分区层资料,对于分区表,如果不指定收集资料的层次, 将默认对所有层收集资料: SQL> exec dbms_stats.gather_table_stats('U3','PT3') PL/SQL 过程已成功完成。 已用时间: 00: 00: 01.88 收集结果如下: TABLE_NAME PARTITION_ NUM_ROWS BLOCKS EMPTY_BLOCKS AVG_SPACE AVG_ROW_LEN GLO ---------- ---------- ---------- ---------- ------------ ---------- ----------- --- PT3 FY_2006 49335 180 0 0 12 YES PT3 FY_2005 49317 180 0 0 12 YES PT3 FY_2007 1000 5 0 0 11 YES PT3 无 99652 365 0 0 12 YES global_stats 列的值都为“YES”。另外,平均行长度有一点变化。其他都和上面估算 出来的全局资料一样。这条命令所耗时间最多,1.88 秒,是只在表层收集资料所耗时间的 三倍。 从收集速度上来说,像:dbms_stats.gather_table_stats('U3','PT3',granularity=>'GLOBAL') 这样只在表层收集资料,是最快的。我们在以后的使用中,应该根据需要使用。另外,使用 Analyze 收集资料的速度最慢: SQL> analyze table pt3 compute statistics; 表已分析。 已用时间: 00: 00: 02.52 Analyze 相当于在只在分区层收集资料,表层资料是估算出来的。但是,它所耗时间, 是使用 DBMS_STATS 只收集分区层资料所耗时间的两倍还要多。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 300 15.2 HASH 分区 Oracle 中,对 HASH 算法的利用,真是无处不在啊。不过,这也难怪,在有些情况下, HASH 应该是查找速度最快的搜索算法了。和范围分区比起来,HASH 分区并不是为了加快查 找速度,而是为了更平均的分布数据。 使用 HASH 分区,Oracle 将根据分区列值计算出一个 HASH 值,根据此 HASH 值将数据分 布到相应的分区中。用户无法决定某一个列值将会分布在哪个分区中,这是由 Oracle 根据 分区列的 HASH 值自动决定的。Oracle 会尽量平均的在表的所有分区中分布行,也就是尽量 保证每个分区中的行数相同。这是 HASH 分区的特点。如果你希望所有行平均的分布到表的 所有分区中,就可以使用 HASH 分区。创建方式如下: SQL> create table hash1 (hk date, id number(10)) partition by hash(hk) 2 ( partition p1 tablespace users, 3 partition p2 tablespace tp1 4 ); 表已创建。 不需要指定行如何在 P1 和 P2 分区中分配,这由 Oracle 根据 HASH 值自动计算。因此, 从创建语法上,HAHS 分区比范围分区更简单。 需要注意的一点是,分区的数量一定要是 2 的幂,这样 Oracle 才能保证平均的在各个 分区中分布行。否则的话,各个分区中行的数量可能会有较大差别。 下面我插入四行,实验一下 HASH 分区的分区消除: insert into hash1 values(to_date('2004-08-20','yyyy-mm-dd'),1); insert into hash1 values(to_date('2004-10-24','yyyy-mm-dd'),2); insert into hash1 values(to_date('2005-3-16','yyyy-mm-dd'),3); insert into hash1 values(to_date('2005-5-19','yyyy-mm-dd'),4); 第 二 节 HASH 分区  利于 HASH 算法实现  有利于均衡各分区大小  等值查询条件可以使用分区消除  范围查询条件无法进行分区消除 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 301 commit; 分别查看 P1 和 P2 区中都有哪个行: SQL> select * from hash1 partition (p1); HK ID -------------- ---------- 20-8 月 -04 1 19-5 月 -05 4 SQL> select * from hash1 partition (p2); HK ID -------------- ---------- 24-10 月-04 2 16-3 月 -05 3 P1 中是第 1、4 行,P2 区中是 2、3 行。 对于 HASH 分区,除非条件是等值条件,否则分区消除不可用: SQL> select * from hash1 where hk = to_date('2006-1-1','yyyy-mm-dd'); 未选定行 执行计划 ---------------------------------------------------------- Plan hash value: 974080404 ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | ----------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 22 | 3 (0)| 00:00:01 | | | | 1 | PARTITION HASH SINGLE| | 1 | 22 | 3 (0)| 00:00:01 | 2 | 2 | |* 2 | TABLE ACCESS FULL | HASH1 | 1 | 22 | 3 (0)| 00:00:01 | 2 | 2 | ----------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter("HK"=TO_DATE('2006-01-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss')) Note ----- - dynamic sampling used for this statement 等值条件,Oracle 可以根据条件值“to_date('2006-1-1','yyyy-mm-dd')”,计算 HASH 值,根据 HASH 值到指定分区中进行扫描。因此,扫描路径是“PARTITION HASH SINGLE”, 只有一个分区被扫描。 而如果条件是非等值的话: SQL> select * from hash1 where hk < to_date('2004-9-1','yyyy-mm-dd'); HK ID -------------- ---------- 20-8 月 -04 1 执行计划 ---------------------------------------------------------- Plan hash value: 1717041925 ------------------------------------------------------------------------------- ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 302 | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | -------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 22 | 4 (0)| 00:00:01 | | | | 1 | PARTITION HASH ALL| | 1 | 22 | 4 (0)| 00:00:01 | 1 | 2 | |* 2 | TABLE ACCESS FULL| HASH1 | 1 | 22 | 4 (0)| 00:00:01 | 1 | 2 | -------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter("HK" create table list1(state varchar2(4), id number(10)) partition by list(state) 2 ( partition p1 values('京','津'), 3 partition p2 values('鄂','豫','皖','湘','赣') 4 ); 表已创建。 上面的列表分区包含了两个区:P1 和 P2。如果插入的 State 列值为'京'、'津',行将 被放进 P1 区中。如果 State 值为'鄂'、'豫'、'皖'、'湘'、'赣',行将被插入到 P2 分区中。 这就是列表分区。如果 State 列不是'鄂','豫','皖','湘','赣'或'京'、'津',那么,插入 操作将会失败。 下面插入两行: insert into list1 values('京',1); insert into list1 values('豫',2); commit; 上面的第一行在 P1 区中,第二行在 P2 区中,分别显示如下: SQL> select * from list1 partition (p1); STAT ID ---- ---------- 京 1 SQL> select * from list1 partition (p2); STAT ID 第 三 节 列表分区  根据分区列的值,决定行被插入进那个分区  适用于分区列的值并不很多时  如果分区列的值非常多,列表分区并不适用  等值查询条件可以进行分区消除  范围查询条件无法进行分区消除 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 304 ---- ---------- 豫 2 再插入一个其他省的行试试: SQL> insert into list1 values('沪',3); insert into list1 values('沪',3) * 第 1 行出现错误: ORA-14400: 插入的分区关键字未映射到任何分区 我们可以为 List1 添加一个 Default 区,它的作用就像范围分区中的“maxvalue ”一 样,任何无法插入到其他分区中的行,将被插入到此 Defaulte 区中: SQL> alter table list1 add partition p3 values(default); 表已更改。 有了 Defaulte 区,下面的插入就可以成功了: SQL> insert into list1 values('沪',3); 已创建 1 行。 它被插入到 Defualte 的 P3 区中: SQL> select * from list1 partition (p3); STAT ID ---- ---------- 沪 3 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 305 15.4 组合分区 组合分区就是先按某列进行范围分区,然后对每个区再按某列进行 HASH 或列表分区。 注意组合分区必须是先范围后 HASH,或是先范围后列表。总之顶层分区必须是范围分区。 在组合分区中,顶层分区并没有对应的分区段,它像最上层的分区表一样,只是逻辑层 上的概念。每个最下层的子分区,才对应一个分区段。下面我实验一个 Range-Hash 组合分 区: SQL> create table ce( rk date, hk number, id number) 2 partition by range(rk) subpartition by hash(hk) subpartitions 2 3 ( 4 partition p1 values less than(to_date('2005-01-01','yyyy-mm-dd')) 5 ( subpartition p1_sub1, 6 subpartition p1_sub2 7 ), 8 partition p2 values less than(to_date('2006-01-01','yyyy-mm-dd')) 9 ( subpartition p2_sub1, 10 subpartition p2_sub2 11 ) 12 ) 13 / 表已创建。 顶层的范围分区以 RK 列为分区关键字,底层的 HASH 分区以 HK 列为分区关键字。 在使用组合分区时,每个分区的子分区数目可以不一致,这并不会对效率有任何的影响。 第 四 节 组合分区 组合分区: 注意组合分区的创建与组合方式:  先范围后 HASH  先范围后列表 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 306 15.5 局部索引分区 15.5.1 局部索引 局部索引就是对表的每个分区,独立的创建索引。局部索引又分两种,局部前缀索引和 局部非前缀索引。在局部前缀索引中,分区列就是索引列。如果索引是组合索引,分区列是 索引关键中的第一个列。这就是局部前缀索引。如果索引关键字中不包含分区列,就是局部 非前缀索引。 这两种局部索引,在性能上并没有好坏之分,应该根据应用、以最大化分区消除为目的 进行选择。分区消除是我们使用分区的一个很重要的因素。如果根据应用程序,局部前缀索 引可以分区消除,那么就使用局部前缀索引。反之,如果局部非前缀索引可以使用分区消除, 我们应该使用局部非前缀索引。 下面,我用一个例子来实验一下这两种局索引的创建和它们的分区消除: 步 1:创建分区表: SQL> create table pt1( a int, b int, data char(20)) partition by range(a) 2 ( partition p1 values less than(2) tablespace users, 3 partition p2 values less than(3) tablespace tp1 4 ); 表已创建。 步 2:创建局部前缀索引 SQL> create index local_pre on pt1(a,b) local; 索引已创建。 第 五 节 局部索引分区  局部索引就是对表的每个分区,独立的创建索引  局部索引又分两种: - 局部前缀索引 - 局部非前缀索引  注意局部索引不适合需要索引的约束 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 307 步 3:随机插入一些行,并收集表、索引的资料: SQL> insert into pt1 select mod(rownum-1,2)+1, rownum,'x' from all_objects; 已创建 49325 行。 SQL> commit; 提交完成 SQL> exec dbms_stats.gather_table_stats(user,'PT1',cascade=>TRUE); PL/SQL 过程已成功完成。 步 4:检验分区消除 SQL> explain plan for select * from pt1 where a=1 and b=1; 已解释。 SQL> select * from table(dbms_xplan.display); PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------------------------- Plan hash value: 1330365032 ------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | ------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 28 | 2 (0)| 00:00:01 | | | | 1 | PARTITION RANGE SINGLE| | 1 | 28 | 2 (0)| 00:00:01 | 1 | 1 | | 2 | TABLE ACCESS BY LOCAL INDEX ROWID|PT1 | 1 | 28 | 2 (0) | 00:00:01 | 1 | 1 | |* 3 | INDEX RANGE SCAN | LOCAL_PRE | 1 | | 1 (0)| 00:00:01 | 1 | 1 | ------------------------------------------------------------------------------------------------- PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 3 - access("A"=1 AND "B"=1) 在执行计划“PARTITION RANGE SINGLE”中也可以看到,上面的语句,使用 LOCAL_PRE 局部前缀索引,是会进行分区消除的。Pstart 和 Pstop 的值分别是 1。 步 5:创建一个局部非前缀索引: SQL> create index local_nonpre on pt1(b) local; 索引已创建。 SQL> drop index local_pre; 索引已删除。 SQL> exec dbms_stats.gather_index_stats(user,'local_nonpre'); PL/SQL 过程已成功完成。 SQL> explain plan for select * from pt1 where a=1 and b=1; 已解释。 SQL> select * from table(dbms_xplan.display); PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------------------------- Plan hash value: 517156853 ------------------------------------------------------------------------------------------------ | Id | Operation | Name | Cost (%CPU)| Time | Pstart| Pstop | ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 308 ------------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 2 (0)| 00:00:01 | | | | 1 | PARTITION RANGE SINGLE | | 2 (0)| 00:00:01 | 1 | 1 | |* 2 | TABLE ACCESS BY LOCAL INDEX ROWID| PT1 | 2 (0)| 00:00:01 | 1 | 1 | |* 3 | INDEX RANGE SCAN | LOCAL_NONPRE | 1 (0)| 00:00:01 | 1 | 1 | ------------------------------------------------------------------------------------------------ PLAN_TABLE_OUTPUT (因版面宽度,上表中省略了部分与此章节内容无关的列) ------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter("A"=1) 3 - access("B"=1) 已选择 16 行。 根据执行计划“PARTITION RANGE SINGLE”,可以看出,当只有局部非前缀扫描时,仍然可 以进行分区消除。 在这个例子中,为表创建非分区索引其实更合适。因为凭条件 a=1 就可以决定只在 P1 区中扫描,而 P1 区中所有行的 A 列值都为 1,这时,A 列也是索引关键字的一部分就没有任 何意义了。只会增加索引的大小。如果 P1 区中 A 列的值有好多种,让 A 列是索引关键字的 一部分才有意义。 不过,另需注意,局部非前缀索引不能是唯一索引: SQL> create unique index local_nonpre on pt1(b) local; create unique index local_pre on pt1(b) local * 第 1 行出现错误: ORA-14039: 分区列必须构成 UNIQUE 索引的关键字列子集 15.5.2 局部索引和唯一约束 下面我们来试试创建约束时,Oracle 将会我们创建什么样的约束: 步 1:为了观察,我首先新建一个新用户: SQL> create user u3 identified by abcde; 用户已创建。 SQL> grant dba to u3; 授权成功。 SQL> conn u3/abcde 已连接。 步 2:创建带有约束的分区表: SQL> create table pt2 (load_date date, id int, constraint ptpk primary key(id)) partition by range (id) 2 ( partition p1 values less than(10), 3 partition p2 values less than(20) 4 ); 表已创建。 步 3:查看 Oracle 为我们创建的段: ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 309 SQL> col SEGMENT_NAME for a20 SQL> col PARTITION_NAME for a20 SQL> select segment_name, partition_name, segment_type from user_segments; SEGMENT_NAME PARTITION_NAME SEGMENT_TYPE -------------------- -------------------- ------------------ PTPK INDEX PT2 P2 TABLE PARTITION PT2 P1 TABLE PARTITION PTPK 主键约束对应的索引 PTPK 是全局索引,它没有被分区。像这个例子,主键列和分 区列是同一列,我们可以让为主键创建的索引随着表的分区而分区: 步 4:重建约束: 首先,先删除 PTPK 约束: SQL> alter table pt2 drop constraint ptpk; 表已更改。 查看一下: SQL> select segment_name, partition_name, segment_type from user_segments; SEGMENT_NAME PARTITION_NAME SEGMENT_TYPE -------------------- -------------------- ------------------ PT2 P2 TABLE PARTITION PT2 P1 TABLE PARTITION 随约束而创建的索引也随之被删除了。下面手动的在分区列 ID 上创建局部前缀索引: SQL> create index pt2_inx on pt2(id) local; 索引已创建。 SQL> select segment_name, partition_name, segment_type from user_segments; SEGMENT_NAME PARTITION_NAME SEGMENT_TYPE -------------------- -------------------- ------------------ PT2_INX P1 INDEX PARTITION PT2_INX P2 INDEX PARTITION PT2 P2 TABLE PARTITION PT2 P1 TABLE PARTITION 可以看到,PT2_INX 索引是分区的索引。最后,再创建主键约束: SQL> alter table pt2 add constraint ptpk primary key(id); 表已更改。 观察段的数量: SQL> select segment_name, partition_name, segment_type from user_segments; SEGMENT_NAME PARTITION_NAME SEGMENT_TYPE -------------------- -------------------- ------------------ PT2_INX P1 INDEX PARTITION PT2_INX P2 INDEX PARTITION PT2 P2 TABLE PARTITION PT2 P1 TABLE PARTITION 还是这四个段,Oracle 并没有另行创建索引,它使用了 ID 列上原有的索引 PT2_INX。 上面是约束列和分区列是同一列,如果不是同一列的话,约束所使用的索引必须是全局 索引,下面继续实验: ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 310 步 5:删除 ID 列上的约束和索引: SQL> alter table pt2 drop constraint PTPK; 表已更改。 SQL> drop index pt2_inx; 索引已删除。 如果索引是随约束自动创建的,删除约束时索引也将自动被删除。如果索引是在创建约 束前已有的,删除约束将不会自动删除索引。我们需要单独的删除索引。删除索引的目的是 为了查询段时的结果更清楚,下面我们查询一下 U3 用户中都有什么段: SQL> select segment_name, partition_name, segment_type from user_segments; SEGMENT_NAME PARTITION_NAME SEGMENT_TYPE -------------------- -------------------- ------------------ PT2 P2 TABLE PARTITION PT2 P1 TABLE PARTITION 只有两个分区段。 步 6:在非分区列 Date 上添加主键约束: 如是没有局部索引,Oracle 将自动创建全局索引。因此,先创建好已分区的局部索引: SQL> create index pt2_inx on pt2(load_date) local; 索引已创建。 查看结果,PT2_INX 索引分两个区: SQL> select segment_name, partition_name, segment_type from user_segments; SEGMENT_NAME PARTITION_NAME SEGMENT_TYPE -------------------- -------------------- ------------------ PT2_INX P2 INDEX PARTITION PT2_INX P1 INDEX PARTITION PT2 P2 TABLE PARTITION PT2 P1 TABLE PARTITION 下面为 Load_date 列添加约束: SQL> alter table pt2 add constraint ptpk primary key(load_date); alter table pt2 add constraint ptpk primary key(load_date) * 第 1 行出现错误: ORA-01408: 此列列表已索引 Oracle 不能使用我们在 Load_date 列上创建的已分区的索引 PT2_INX,它又无法创建全 局索引,因为同样的列不被能索引两次,因此,添加约束失败。 对于这样约束列和分区列不一致的情况,约束的索引不能是局部索引。因为,如果 Oracle 允许这样的情况存在,将丧失很多分区的特性。例如,任何的插入、更新,都必须 扫描所有索引分区。如果分区数量比较多,这将极大的影响速度。并且,这降低了可用性。 因为任何插入、更新都要扫描所有索引分区,如果有一个分区损坏,将导致操作失败。分区 的本意是“分而治之”,这个成语在形容分区时常被用到。但仅这四个字还不足以表达分区 的中心思想。分区的精神是让操作可以只访问一个或一些分区,而不必访问所有分区。也就 是说,可以只访问一部分数据就可以完成操作,而不是必须访问全部的数据。简单点说,就 是要可以进行分区消除。如果约束列和分区列可以不一致,插入、更新都必须扫描所有索引 分区,而不是只扫描一部分分区即可。这违反了分区的精神,不能进行分区消除。如果你只 将表和索引分成了多个区,但是在操作中完全不能进行分区消除,分区和不分区将一模一样。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 311 那么分区的效率将更低。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 312 15.6 全局索引分区 15.6.1 全局索引 全局索引把分区表的各个分区当作一个整体进行索引。索引后可以对索引再次进行分区, 这个索引的分区和表的分区无关。当然也可以不分区。需要注意的是,全局索引分区必须是 前缀的。 下面我们以一个比较典型的应用实例来实验一下全局索引: 假设我有一个数据仓库表,表中行按日期分区存储。通常有两个区,保存最近两年的数 据。在分区列上建有局部索引,在非分区列上,建有全局索引: 步 1:创建分区表: SQL> create table pt3( time date, id int) partition by range(time) 2 ( partition fy_2004 values less than ( to_date('2005-01-01','yyyy-mm-dd')), 3 partition fy_2005 values less than ( to_date('2006-01-01','yyyy-mm-dd')) 4 ); 表已创建。 步 2:创建全局分区索引: SQL> create index pt3_idx_g on pt3(id) global partition by range(id) 2 ( partition p1 values less than(1000), 3 partition p2 values less than(2000) 4 ); ) * 第 六 节 全局索引分区  全局索引把分区表的各个分区当作一个整体进行 索引  索引分区和表的分区并不对应  可用性不如局部索引  适用于 OLTP ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 313 第 4 行出现错误: ORA-14021: 必须指定所有列的 MAXVALUE 注意,这里会报出一个错误,为了使索引中可以容纳表中的任何值,因此此处必须有一 个区的范围是 Maxvalue。添加 Maxvalues 重新创建一次: SQL> create index pt3_idx_g on pt3(id) global partition by range(id) 2 ( partition p1 values less than(1000), 3 partition p2 values less than(MAXVALUE) 4 ); 索引已创建。 步 3:创建局部索引: SQL> create index pt3_idx_l on pt3(time) local; 索引已创建。 步 4:为表插入一些行: SQL> insert into pt3 partition(fy_2004) select to_date('2004-12-31','yyyy-mm-dd')-mod(rownum,360), object_id from all_objects; 已创建 49335 行。 SQL> commit; 提交完成。 SQL> insert into pt3 partition(fy_2005) select to_date('2005-12-31','yyyy-mm-dd')-mod(rownum,360), object_id from all_objects; 已创建 49317 行。 SQL> commit; 提交完成。 上面两条插入声明,分别为 P1 和 P2 区插入了将近 50000 万。 下面我们设想这样一种情况,假如年底到了,2006 年度的数据要被传送进此数据仓库 表 TP3,而 2004 年度的数据要被从 TP3 表中转移到另外的归档表中。 步 5:准备环境 我下面创建一个归档表,它和 TP3 表有同样的结构: SQL> create table fy_2004 (time date, id int); 表已创建。 SQL> create index fy_2004_idx on fy_2004(time); 索引已创建。 2006 年度的数据在 FY_2006 表中,我先创建此表: SQL> create table fy_2006 (time date, id int); 表已创建。 SQL> create index fy_2006_idx on fy_2006(time); 索引已创建。 再为此表插入数据: SQL> insert into fy_2006 select to_date('2006-12-31','yyyy-mm-dd')-mod(rownum,360), object_id from all_objects; 已创建 49319 行。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 314 SQL> commit; 提交完成。 步 6:将 2004 年度的数据从 PT3 表中转移到 Fy_2004 中。 SQL> alter table pt3 exchange partition fy_2004 with table fy_2004 including indexes without validation; 表已更改。 交换命令非常的简单,也完成的非常快。因为它并没有将 Pt3 表中 FY_2004 中的数据逐 行的插入到 FY_2004 表中,只是在数据字典层修改了 FY_2004 区和 FY_2004 表以及它们的索 引的元数据。交换过后,甚至连索引都一起交换了。fy_2004_idx 索引变成了 PT3 表 FY_2004 区的局部索引。原来 PT3 表 FY_2004 区的局部索引变成了交换后的 FY_2004_IDX 索引。 FY_2004 表是空表,没有数据,交换后,现在 FY_2004 区已经没有数据了,可以将其删除: SQL> alter table pt3 drop partition fy_2004; 表已更改。 接下来,将 FY_2006 表中的数据交换进 TP3 表。 步 7:在 TP3 中创建新的 FY_2006 区,将 FY_2006 表中的数据交换进 TP3 表中: SQL> alter table pt3 add partition fy_2006 values less than(to_date('2007-01-01','yyyy-mm-dd')); 表已更改。 开始交换: SQL> alter table pt3 exchange partition fy_2006 with table fy_2006 including indexes without validation; 表已更改。 交换命令几乎不占时间,在你键入回车键后,几乎是马上就完成了。这一点我们已经说 过了,因为它只修改了表和索引的数据字典。 在交换完成后,本地索引不会有任何影响。因为我们的交换命令中,带有“including indexes”子句,它已经将 FY_2004 和 FY_2006 表的索引和 PT3 表中 FY_2004、FY_2006 区的 局部索引进行了交换。分区的局部索引是可以这样随分区一起交换的。因为每个局部索引本 身只针对表的某一个分区。而全局索引是针对整个分区表,它不可能随着区交换命令一起被 转换。因此,在交换命令完成后,局部索引仍然可用,而全局索引则都变得不可用了。在 user_indexes视图中有一个STATUS列,我们在此列中也可查到索引的状态。但user_indexes 视图中的 STATUS 列只针对非分区的索引: SQL> select index_name, status from user_indexes; INDEX_NAME STATUS ------------------------------ -------- FY_2006_IDX VALID FY_2004_IDX VALID PT3_IDX_G N/A PT3_IDX_L N/A PT3_IDX_G 和 PT3_IDX_L 都是分区表的索引,它们的 STATUS 列值都是 N/A,N/A 是“ Not Applicable”的缩写,意即此列不适于当前的类型的索引。对于分区的索引,我们可以查询 user_ind_partitions 视图,将可以得到更准确的结果: SQL> select index_name, status from user_ind_partitions; INDEX_NAME STATUS ------------------------------ -------- ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 315 PT3_IDX_L USABLE PT3_IDX_G UNUSABLE PT3_IDX_G UNUSABLE PT3_IDX_L USABLE 上面的结果显示,PT3_IDX_L 的状态是 USABLE,它是可用的。而 PT3_IDX_G 的状态则是 UNUSABLE,在分区交换后,它的状态变为了不可用。 我们还可以通过下面的例子来验证这一点: SQL> set autot trace exp; SQL> select /*+index(pt3 PT3_IDX_G)*/* from pt3 where id=1378; select /*+index(pt3 PT3_IDX_G)*/* from pt3 where id=1378 * 第 1 行出现错误: ORA-01502: 索引 'U3.PT3_IDX_G' 或这类索引的分区处于不可用状态 我在执行计划中指定使用 PT3_IDX_G 索引,但是它已经处于不可用状态,因此报出了错 误。对于这样不可用的索引,有一个初始化参数:skip_unusable_indexes,如果设置为真, 就可以跳过处于不可用状态的索引。在10G中,此参数将被默认的设置为真(9i默认为FALSE), 因此,上面的语句,如果我不使用 Hints 明确要求使用 PT3_IDX_G 索引,优化器发现此索引 不可用后,将自动采取全扫描的方式处理语句。 这一点需要注意,在 10G 下,当因某个操作而使得索引变得不可用后,优化器将自动绕 过索引,而不会报出错误,这有可能极大的影响性能,但从表面上看,DBA 却无法知道原因 是什么。通常当索引不可用后,优化器将使用全表扫描来代替索引,这将大大的增加逻辑读、 物理读,DBA 只能在发生性能问题后,查找逻辑读、物理读最多或在近一段时期内有大幅度 增加的语句,查看它们的执行计划,通过观察执行计划的变化,才能确定问题。 下面再试一个局部索引的例子: SQL> select /*+index(pt3 PT3_IDX_L)*/* from pt3 where time=to_date('2005-12-31','yyyy-mm-dd'); 执行计划 ---------------------------------------------------------- Plan hash value: 1381457717 -------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Cost (%CPU)| Time | Pstart| Pstop | -------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 2 (0)| 00:00:01 | | | | 1 | PARTITION RANGE SINGLE | | 2 (0)| 00:00:01 | 1 | 1 | | 2 | TABLE ACCESS BY LOCAL INDEX ROWID| PT3 | 2 (0)| 00:00:01 | 1 | 1 | |* 3 | INDEX RANGE SCAN | PT3_IDX_L | 1 (0)| 00:00:01 | 1 | 1 | ------------------------------------------------------------------------------------------------ (因版面宽度,上表中省略了部分与此章节内容无关的列) Predicate Information (identified by operation id): ( --------------------------------------------------- 3 - access("TIME"=TO_DATE('2005-12-31 00:00:00', 'yyyy-mm-dd hh24:mi:ss')) Note ----- - dynamic sampling used for this statement ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 316 局部索引可以正常使用。 对于不可用的全局索引,现在只能通过重建来解决问题了。 SQL> alter index pt3_idx_g rebuild partition p1; 索引已更改。 SQL> alter index pt3_idx_g rebuild partition p2; 索引已更改。 重建后,索引又恢复为可用状态: SQL> select index_name, status from user_ind_partitions; INDEX_NAME STATUS ------------------------------ -------- PT3_IDX_L USABLE PT3_IDX_L USABLE PT3_IDX_G USABLE PT3_IDX_G USABLE 但是,在重建期间索引一直将处于不可用状态。准确说从交换分区开始,全局索引将一 直处于不可用的状态。为了提高可用性,我们可以在交换分区时增加“Update global indexes” 子句,这将在交换分区时,自动联机重建全局索引。这保证了在交换过程中,全局索引也一 直处于可用状态。但是,此语句将耗用更多的时间,并占用更多的资源。 下面我们来试验一下此语句: SQL> alter table pt3 exchange partition fy_2006 with table fy_2004 including indexes without validation Update global indexes; 表已更改。 SQL> select index_name, status from user_ind_partitions; INDEX_NAME STATUS ------------------------------ -------- PT3_IDX_G USABLE PT3_IDX_L USABLE PT3_IDX_L USABLE PT3_IDX_G USABLE 交换过分区后,PT3_IDX_G 仍处于可用状态。 15.6.2 全局索引的应用 从分区交换这一点上来说,全局索引的确不如局部索引可用性更高。但分区交换操作一 般只应用于 OLAP 型的应用。OLTP 型的应用中全局索引还是很有用武之地的。试考虑在 OLTP 中,有如下形式的一个表,它有雇员编号、雇员姓名、部门、职位、地址和联系方式六列。 假设按部门对表进行了列表分区,并在雇员编号列上建有主键约束和相应的全局索引。现在, 这个表我们还需要经常按雇员姓名、职位进行查找,此两列上也需要有索引。总的来说,局 部索引比全局索引的可用性要高。好,我在此两列上添加了局部索引。下面考虑一下,如果 我要以“姓名=‘张三’”为条件进行查询,这个查询将不得不查找完所有的分区,才可以 找找出所有姓名为‘张三’的行。每个分区的索引都有自己的一层根块,若干层枝块,一层 叶块。假设这里每个局部索引都只有一层枝块,Oracle 每访问一个分区的索引都至少要 3 个 I/O。如果表一共分为 10 个区,单是索引的访问,就要 30 个 I/O。如果把“雇员姓名” 列上的索引建一个不分区的全局索引,估计树高也就 4 层左右,最少只要 4 个 I/O 就可以完 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 317 成“姓名=‘张三’”这样的条件。我们还可以将此全局索引进行分区,比如建为 HASH 分区。 全局索引的树高可以进一步减低,性能会更好一些。因此,在 OLTP 中,全局索引使用的会 更多一些。 分区在 OLAP 的环境中,的确可以提高效率。但在 OLTP 环境中,分区的目的是为了可管 理性、可用性,同时又不影响性能。 另外,我们上面的例子一直都在说查询,对于 DML 来说,分区可以有效的减少争用。考 虑有一个表,它有两个对应的索引,如果它有很多并发的 DML,它的争用的可能性会很高。 如果将表会为 10 个区,一个索引为局部索引,随着表也分为 10 个区,另一索引为全局索引, 另行分为 5 个区。那么,这时对表的插入、更新、删除将针对的是 10 个表,而不是 1 个表, 索引的维护操作也是多个索引而不是一个索引。争用肯定会减少。但是,有一点需要注意, 分区也增加了 CPU 的使用。过去要修改时直接就修改,分区后多了一个判断的步骤。比如说 插入时,未分区的表直接进行插入,分区的表还要判断将行插入进那个区,然后才可以进行 插入。如果系统目前是 CPU 密集型的应用,而且 CPU 的使用不是因为竞争和 Latch 的自旋 (Spin),那么,引入分区可能并不能提高效率,而只会让性能更糟。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 318 第 16 章 实体化视图 您将学习 1. 实体化视图概述 2. 创建实体化视图 3. 实体化视图刷新 4. 查询重写 5. DBMS_MVIEW 程序包 第 十六 章 实体化视图 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 319 16.1 实体化视图概述 实体化视图管理是用于汇总,预计算,复制或分发数据的对象, 在大型的数据库中使用它 可以提高涉及到的 SUM,COUNT,AVG,MIN,MAX 等的表的查询的速度,只要在实体化视图管 理上创建了统计,查询优化器将自动的使用实体化视图管理,这特性被称为 QUERY REWRITE(查询重写).与普通的视图不同的是实体化视图管理存储数据,占据数据库的物理 空间的。 创建实体化视图管理的用户的权限必须有: CREATE MATERIALZED VIEW,CREATE TABLE,CREATE VIEW,SELECT 等,如果在其他的模式中创建的话要在表上有 CREATE ANY MATERIALIZED VIEW 和 SELECT 权限。 要查询重引用别的模式中的实体化视图管理的话,还要有引用的表的 GLOBAL QUERY REWRITE OR QUERY REWRITE 权限。 如果计划使用实体化视图管理的话 , 要修改参数文件中加: QUERY_REWRITE_ENABLE=TRUE。 第 一 节 实体化视图概述  多用于 OLAP 系统  在大型的数据库中使用它可以提高涉及到的 SUM,COUNT,AVG,MIN,MAX 等操作的速度  必须有 CREATE MATERIALZED VIEW,CREATE TABLE,CREATE VIEW,SELECT 权 限 , 以 及 CREATE ANY MATERIALIZED VIEW 和 SELECT ANY TABLE 权限 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 320 16.2 创建实体化视图 16.2.1 创建实体化视图注意事项 创建之后,是否要填写数据、多长时间刷新一次、使用那种刷新类型: COMPLE(完 全),FAST(快速),FORCE(强制),NEVER(从不)。 16.2.2 创建实体化视图 可以用下面的命令创建实体化视图: CREATE MATERIALIZED VIEW TEST3 PCTFREE 0 TABLESPACE MTEST STORAGE (INITIAL 1M NEXT 1M PCTINCREASE 0) BUILD DEFERRED REFRESH FAST ON COMMIT ENABLE QUERY REWRITE AS SELECT EMP_NO,SUM(QTY_PSC) AS QTY_PSC FROM BSEMPMS GROUP BY EMP_NO; 第 二 节 创建实体化视图  使用命令:CREATE MATERIALIZED VIEW  需要注意: - 是否立即填写数据 - 多长时间刷新一次 - 使用那种刷新类型 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 321 16.3 实体化视图刷新 16.3.1 自动刷新 实体化视图使用与快照相同的内部机制,并支持几种刷新技术。实体化视图的刷新包 括,截断现有数据并重新插入所有基于详细表的数据,这通过重新执行 CREATE 命令的查 询定义来实现。 快速刷新仅适用于上次刷新以来所进行的更改。有两种可用的快速刷新内容: 1、使用实体化视图日志的快速刷新,在这种情况下,对基表的所有更改会为日志所捕 获,然后应用于实体化视图。 2、使用行 ID 范围的快速刷新,在直接路径加载后,可以根据新行的 ID,快速刷新来 实现实体化视图。这种刷新类型需要直接加载日志。 如有可能,用“强制”刷新类型定义的视图,使用快速机制进行刷新,否则它将完全 使用刷新,强制刷新是缺省的刷新类型。 使用 DBMS_MVIEW 程序包执行手动刷新。DBMS_MVIEW 程序提供了许多过程和函 数,以管理实体化视图。 自动刷新可以在以下情况下执行: 1、提交时,为实体化视图指定“提交时”选项后,每当提交对其中一个基表的更改时, 该视图都被更新。 2、在指定的时间,可以安排实体化视图在指定时间刷新。例如,通过使用 START WITH 和 NEXT 子句,在每周一的上午 9:00 进行刷新。 第 三 节 实体化视图刷新  实体化视图有如下几种刷新方式: - COMPLE(完全) - FAST(快速) - FORCE(强制) - NEVER(从不) ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 322 16.3.2 手动刷新 以下为实体化视图可能的几种刷新情况: 1、使用 REFRESH 过程刷新指定的实体化视图 2、使用 REFRESH_DEPENDENT 过程刷新所有依赖一组给定的基表的实体化视图。 3、使用 REFRESH_ALL_MVIEWS 过程,刷新自从上次批量加载一个或多个表以来仍 未刷新的实体化视图。 该程序包中的过程使用许多参数来指定: 刷新方法、如果遇到错误是否继续进行、是否使用单一的事务处理(一致刷新)、要使 用的回退段。 服务器作业队列用于运行刷新作业。因此必须设置合适的初始化参数。 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 323 16.4 查询重写 16.4.1 查询重写的概念 如果初始化参数 query_rewrite_enabled 设置为 TRUE,且使用 CBO 时,当发出对基表 的查询,Oracle 会自动判断是否能利用这个基表的所有指定了 ENABLE QUERY REWRITE 语句的物化视图,如果可以且根据统计信息判断通过查询物化视图代价更小,则 Oracle 自 动重写查询语句,通过查询物化视图得到正确的结果。 优化程序执行查询重写对应用程序来说该重写是透明的。使用 QUERY_REWRITE_ENABLED 参数必须能够在会话级别上或例程级别上启用执行重写。 若要为查询重写启用或禁用个人的实体化视图,用户必须拥有 GLOBAL,QUERY REWRITE 或 QUERY REWRITE 系统权限这两种权限都允许用户在其自己的方案中启用 实体化视图,GLOBAL 形式的权限允许用户启用他们拥有的任何实体化视图。而简单的 QUERY REWRITE 权限要求基表和视图都在用户的方案中。 16.4.2 查询重写示例 创建实体化视图的语法类似于 CREATE SNAPSHOT 命令它有一些附加的选项。在这 个示例中选择 BUILD IMMEDIATE 选项以便在执行 CREATE 命令时置入实体化视图。这 是缺省行为,您可以选择 BUILD DEFERRED 选项,该选项创建该结构,但直到第一次刷 新发生时,才置入实体化视图数据。当希望将在 Oracle8i 之前创建的表作为实体化视图的 资源时请使用 ON PREBUILD TABLE 选项。ENABLE/DISABLE QUERY REWRITE 子句 第 四 节 查询重写  查询重写的启用条件: - 初始化参数 query_rewrite_enabled 设置为 TRUE - 使用 CBO 优化器 查询重写使用优化器可以使用实体化视图重写 SQL 语句,以减少 I/O 和 CPU 时间的消耗 ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 324 确定是否为实体化视图自动地启用查询重写 SQL> create MATERIALIZED VIEW sales_summary 2 tablespace sales_ts 3 parallel (degree 4) 4 BUILD IMMEDIATE REFRESH FAST 5 ENABLE QUERY REWRITE 6 AS 7 select s.zip, p.product_type 8 , sum(s.amount) 9 from sales s, product p 10 where s.product_id = p.product_id 11 group by s.zip, p.product_type; 查询的执行计划,即来自计划表的格式化输出,显示查询并未针对两个基表运行而是仅 扫描了实体化视图表。该示例具体表现了实体化视图和查询重写的能力。组成实体化视图的 表完全替代了基表,以及在查询中指定的该基表上的所有操作。 16.4.3 启用、禁用和控制查询重写 下述参数控制查询重写: OPTIMIZER_MODE 仅在基于成本的优化时,才可使用查询重写。如果使用基于规则的优化则 不发生查询重写。 QUERY_REWRITE_ENABLED 可以将该参数设置为 FALSE,即便使用基于成本的优化时也是如此。 以抑制查询重写在整个例程以及在单独的会话中可以动态地更改该参数。 QUERY_REWRITE_INTEGRITY 也可以为整个例程和单独的会话动态重置该参数。它接受下述值: • ENFORCED 缺省值仅在 Oracle ,可以保证一致性时启用查询重写,仅最新的实体化 视图和启用的有效约束可以用于查询重写。 • TRUSTED 允许基于已声明但不一定是强制的的关系,进行查询重写所有更新的实体化 视图和带有 RELY 标志的约束都用于查询重写。 • STALE_TOLERATED 允许查询重写使用自上次 DML 操作以来还未刷新的实体化视图和 已声明的关系。 查询重写被认为与执行计划路径类似,因此没有有关它们的对象权限,访问从表的用户 隐含地受益于概要重写。新的提示 REWRITE 可以用于限制将查询重写的实体化视图。另一 个提示 NOREWRITE 可用于抑制查询块的重写。 维是 Oracle8i 数据字典结构它基于现有数据库表中的列定义层次结构,虽然它们是可 选的但仍极力推荐它们。因为它们: • 启用不使用约束的附加重写可能性,由于性能原因在数据仓库中约束的实现可能并不理想 • 对文件的维和层次结构有明显的帮助 • 可以由 OLAP 工具使用 在下面的示例中,NOREWRITE 提示告诉优化程序不要重写查询,以便使用实体化视图。 该语句在其它方面与上一个示例相同。从查询导出的执行计划显示,原始语句已执行其中包 括两个表之间的散列连接和用于获得组的排序。REWRITE/NOREWRITE 提示覆盖实体化视图的 定义该定义,通过 ENABLE,QUERY REWRITE 子句,在 CREATE 和 ALTER MATERIALIZED VIEW 命令中设置: SQL> select /*+ NOREWRITE */ ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 325 2 s.zip, p.product_type, sum(s.amount) 3 from sales s, product p 4 where s.product_id = p.product_id 5 group by s.zip, p.product_type; OPERATION NAME ----------------------------- ----------- SELECT STATEMENT SORT GROUP BY HASH JOIN TABLE ACCESS FULL SALES ORACLE 交流第一群:48949977 TG-903 Oracle 性能调优 326 16.5 DBMS_MVIEW 程序包 DBMS_MVIEW 程序包包含以下过程: EXPLAN_MVIEW、EXPLAIN_REWRITE、REFRESH、REFRESH_ALL_VIEWS。 EXPLAN_MVIEW 过程,可以了解使用实体化视图或者潜在的实体化视图所能完成的 工作。例如,可以确定一个实体化视图是否快速刷新,和对特定的实体化视图可以执行哪些 类型的查询重写。 EXPLAIN_REWRITE 过程,使用此过程,可以了解某个查询不能重写的原因,使用此 过程的结果,可以进行所需的相应操作,使查询可以重写。 REFRESH 过程,此过程可以一致性刷新属于同一刷新组的一个或多个实体化视图。 REFRESH_ALL_VIEWS 过程,刷新所有那些不反映其主表或主实体化视图更改的实体 化视图。 第 五 节 DBMS_MVIEW 程序包 DBMS_MVIEW 程序包包含以下过程:  EXPLAN_MVIEW  EXPLAIN_REWRITE  REFRESH  REFRESH_ALL_VIEWS

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享文档获得金币 ] 1 人已下载

下载文档

相关文档