不平衡的索引?
有1人发表了评论 | 赶紧发表评论吧 | 作者:jametong
本文翻译自Jonathan Lewis早年写在dbazine上的文章unbalanced indexes? 本文的word版本可以到此处下载
原文参见: 不平衡的索引?
不平衡的索引?
by Jonathan Lewis
网络上有多篇介绍Oracle索引实现机制的文章,都提及需要经常重建索引.在这些文章中的某处,总是会出现这样一段简短的描述,索引会如何变的不平衡,以及可能导致的后果.很不幸,它们好像忽视了这样一个事实,Oracle使用的B-tree机制是一种”平衡B-tree”索引,也就是说,索引无法变得不平衡.
“平衡”到底意味着什么?
既然Oracle的索引使用的是平衡B-tree,为什么还有如此多的人相信他们的索引会变得不平衡呢?
另外,平衡B-tree到底又是什么呢?
第二个问题的答案可能能够帮助我们得到第一个问题的答案.
从技术角度讲,平衡B-tree的显要特性是,在任意时间点,任何叶子节点(leaf block)到根节点(root block)的距离都相等,平衡是指从顶部到底部的平衡.
就Oracle来说,执行一个treedump命令就可以很容易发现这一点,如图-1所示:
select object_id from user_objects where object_type = 'INDEX' and object_name = 'T1_IDX1' -- and subobject_name = . . . ; alter session set events ' 'immediate trace name treedump level N';
图-1: 一次索引树转储涉及到的步骤
首先,需要找到你想要转储(dump)索引或者索引分区(index partition)的object_id;接着,将object_id作为level的参数来调用treedump事件. 如果检查这个索引树(tree dump)转储生成的跟踪文件,你将发现类似于图-2中所示的结果.
branch: 0x14001aa 20971946 (.. level 2)
branch: 0x14003ef 20972527 (.. level 1)
leaf: 0x14001ab 20971947 (..)
leaf: 0x14001ac 20971948 (..)
图-2: 从索引树转储结果中提取出的一段内容
这个跟踪文件以递归降序的方式展示索引的分支块(branch block,根块只是分支块的一个特例)与叶子块.注意,转储内容的第一个块(根块)记录了一个高度(level),并且它下面的每一个分支块也都记录了一个高度,但是叶子块没有记录高度.
根块的高度就是索引的blevel(执行analyze index命令之后会记录在视图dba_indexes中).索引的高度(height,执行命令validate index后会记录在视图index_stats中)就是blevel + 1.
每一个叶子块到根块的距离就正好有这么多步.索引总是平衡的.
那么为什么有那么多人相信Oracle会允许索引变得不平衡呢?
我应负的责任
此时,我必须承认我有罪,仅仅在一年前(2002年5月),我也重复了一个关于块分裂(index block split)的众所周知但是却完全错误的描述.虽然我知悉(在我的书(2000年12月,Practical Oracle 8i)中对此做了说明) treedump的细节,但我还是这样做了.
我猜想,这种错误的观念最初产生于Oracle5(很多年以前)手册的说明,其大意是,因为”没有一个叶子块到根块的距离比任何其他叶子块到根块的距离远.”, 所以Oracle索引是平衡的.将这个表述与一个过于简化的块分裂的图结合到一起,瞧,一个几乎牢不可破的神话就诞生了.
图-3和图-4描绘了一个非常常见,但是完全错误的关于叶子块如何分裂的概念.

图-3: 叶子块将要分裂前的索引结构

图-4 这不是叶子块分裂发生的方式
有这样一个流传广泛的说法,叶子块分裂到两个全新的块,这两个块分享它的所有数据;接着, Oracle在原来的叶子块的位置插入一个新的分支块来持有指向这两个块的指针.因此,在这样一个错误的视角下,这个索引的右边就会比左边更深.(经常会有人说,创建在基于序列的字段上的索引会带来最大的问题,因为,从这个理论来推导,最右边的叶子块到根块的距离会越来越远,没分裂一次,就会降低一层.)
事实上,这个工作Oracle做得更加精致前瞻并有效得多. 图-5所示是一个复杂的叶子块分裂的结果.

图-5: 一次递归分裂后的索引结构图
由于这个叶子块分裂成了两个块,Oracle会尝试往当前指向这个叶子块的分支块中插入一个额外的指针.
但是,如果这个分支块也满了,Oracle会继续将这个分支块也分裂成两个块,并且在两个分支块之间分配现存块里的指向叶子块的指针,并且(递归地) 在这个分支块的上一级的分支块中插入一个指向这个新的分支块的新的分支指针.
如果在这个过程中,Oracle抵达了根块,然而这个根块也满了,那么根块也必须分裂.在这种情况下,Oracle将创建一个新的根块来持有这两个分支指针.(事实上,Oracle处理根块分裂的方式与处理普通的分支块的分裂有一点细微的差异,以确保无论根块上发生了多少次的分裂,总是可以在物理段的同一个位置找到根块.)
注意,这个递归的分裂操作这样沿着索引树不断攀升意味着,无论何时,索引总能保持平衡.
为什么这个神话如此牢固?
为什么这个关于不平衡的索引的神话能够长盛不衰,是否存在一些原因呢?我这答案是确实存在一些原因.
要切记,当我们讨论B-tree的时候,单词”balanced”的定义有非常严格的含义.然而,这个单词还有一种完全不同的解释.
例如,你将如何描述图-6中的这个索引,其中根块指向六个叶子块,但是其中一个叶子块是空的,有三个块几乎是空的,还有两个块塞的很满.(注意,从根块到叶子块的这些额外的说明是为了强调索引填充分布的多么不均匀;实际上,根块到每个叶子块都只有一个指针.)

图-6 “不平衡”的一种非技术理解
看到这种图案的索引,一个”人”的真实反应都会人为它”不平衡”.很明显,索引的右手边要比左手边”重”.很不幸,当技术表达意味着完全不同的东西的时候,这种非正式的人为表达应该更加恰当.
或许正是这种技术表达与非正式的人为表达之间的冲突导致了这种混淆.
在这种非正式的意义上,在基于序列值的字段上索引很容易就会变得”不平衡”,特别是它们被用来表征/处理先进先出(FIFO)队列机制的时候.然而,即使它们(在非正式语境中)是不平衡的,它们(在技术上)仍然是平衡的B-tree.
(推动使用类似于”扭曲”或者”分布不均匀”作为术语来描述这种类型的索引,或许会是个好主意.)
有时,仅仅几篇草率地使用术语的文章或报告就可能构建一个神话,在这个例子中,就是一个导致众多DBA浪费无数小时的时间去做不必要的索引重建的神话.
记住,你下一次认为Oracle表现愚蠢或者低效的时候,很可能问题是出在一个古老的误解上面,而不是Oracle软件本身的问题.
告警提醒
如果你还想进一步地研究,treedump选项还有一系列的问题需要注意.对于大部分的Oracle版本,它看似对索引段中的每个块都生成一行输出,这样可能会非常昂贵并且速度缓慢,因为它需要按顺序访问索引中的每一个数据块.然而,在Oracle 9.0中,跟踪文件看似会对每一个块做一个整块转储,这样会使得转储文件非常巨大,转储速度也会非常缓慢.
第二个问题是所有版本都一致的.如果这个索引是在定义主键约束或者唯一键约束时生成的,Oracle就会设置ind$表的flags字段的第13位,而这将导致treedump程序崩溃并报出错误”invalid value.”分区索引的分区段不会产生这个问题,但是对于所有其他类型的主键索引与唯一索引(包含非分区索引组织表,IOT),这都很恼人.先创建索引,再基于这个索引创建约束通常是个好主意, 这样处理可以避免除索引组织表外的其他所有条件下的问题. 在紧急情况下,你可以修改ind$表来清除这一位,但是很明显,需要先取得Oracle支持的认可.
结论
当谈到平衡B-tree索引的时候,术语”平衡”指的是从顶部到底部,而不是从左到右.
Oracle确实实现了一个版本的”平衡B-tree索引”,因此在任何时候,索引中的所有叶子块到根块的距离都是完全相同的,如果最近对这个索引作过分析的话,可以从视图user_indexes的字段blevel找到它,如果刚刚对这个索引执行过validate index的话,可以从视图index_stats的height字段(等价于blevel+1)得到.
当听到你应该经常重建索引,因为”这些索引已经变得不平衡”时,要抵制这种理由.因为它不是一个靠得住的理由.
为什么Oracle不使用我的索引?!
目前还没有人发表评论 | 赶紧发表评论吧 | 作者:jametong
本文翻译自Jonathan Lewis发表在DBAZine上的文章:Why Isn’t Oracle Using My Index?!,可以从此处下载本文的Word版本.
原文参见: 为什么Oracle不使用我的索引?!
为什么Oracle不使用我的索引?!
by Jonathan Lewis
标题的这个问题可能是在Metalink论坛与Usenet新闻组出现的最频繁的问题了.这篇文章使用一个测试用例(可以在你自己的系统来重现的)来演示基于成本的优化器的基本工作原理.在看完这篇文章之后,当再次遇到这个令人讨厌的问题时,你应该就可以自信的解答了.
由于在安装Oracle的时候存在大量的选项,因此当某人执行一条你口授的脚本时,通常很难精确的预测即将出现什么结果. 当时我想要尝试一下,希望你的数据库选择了一个相对普通的安装选项,并且最常用的关键的参数是取得默认值. 这个例子是在Oracle 8.1.7下创建并测试的,参数db_block_size被设置成最常用的值(8k),参数db_file_multiblock_read_count也设置了一个很常用的值(8).在Oracle 9.2下跑图-1中的这个脚本(创建了一组表,在表上添加索引并分些表与索引),结果可能出现部分差异.
create table t1 as
select trunc((rownum-1)/15) n1, trunc((rownum-1)/15) n2, rpad('x', 215) v1
from all_objects
where rownum <= 3000;
create table t2 as
select
mod(rownum,200) n1,
mod(rownum,200) n2,
rpad('x',215) v1
from all_objects
where rownum <= 3000;
create index t1_i1 on t1(N1);
create index t2_i1 on t2(n1);
analyze table t1 compute
statistics;
analyze table t2 compute statistics;
图 1: 测试的数据集
在你准备好数据之后,你可能认为这两组数据是一样的,尤其是,在两个数据集中字段N1有的值范围相同(从0-199),并且每个值都出现了15次.你可能会这样检查数据:
select n1, count(*) from t1 group by n1;
查询表T2也会证实你的上述观点.
如果你接着执行下面的查询语句:
select * from t1 where n1 = 45; select * from t2 where n1 = 45;
你将发现每个查询语句都返回了15条记录.然而如果你执行
set autotrace traceonly explain
语句,你将发现这两个查询语句拥有两种不同的执行路径.针对表T1的查询使用了索引,而针对表T2的查询则做了一个全表扫描.
因此,在拥有完全相同的数据的情况下,同一个查询语句戏剧性地出现了两个不同的执行路径.
索引到底怎么了?
注意: 如果你曾经听到如下的关于使用索引的”魔法”准则,例如,”在数据少于23%/10%/2%(随机取一个数字)的时候,Oracle将使用索引,”那么,此时你将怀疑他们的准确性.例如,在这个例子中,Oracle对于一个在3000记录中取15条的查询使用了全表扫描,仅仅0.5%的数据.
要调查诸如此例的问题,只有一个手段(我经常首先尝试使用的):添加一些索引以使得Oracle选择我们认为它应该选择的执行路径,并检查这能否给我们部分提示.
在这个例子中,添加一个简单的提示:
/*+ index(t2, t2_i1) */
就足够让Oracle从选择全表扫描切换到选择使用索引访问.图-2展示了这三种路径的成本(简化为C=nnn).
select * from t1 where n1 = 45;
EXECUTION PLAN
--------------
TABLE ACCESS BY INDEX ROWID OF T1 (C=2)
INDEX(RANGE SCAN) OF T1_I1 (C=1)
select * from t2 where n1 = 45;
EXECUTION PLAN
--------------
TABLE ACCESS FULL OF T2 (C=15)
select /*+ index(t2 t2_i1) */
*
from t1
where n1 = 45;
EXECUTION PLAN
--------------
TABLE ACCESS BY INDEX ROWID OF T2 (C=16)
INDEX(RANGE SCAN) OF T2_I1 (C=1)
图 2: 不同的查询以及它们的成本
因此,为什么在T2相关的查询中Oracle没有选择索引作为默认的执行路径?很简单,正如执行计划所展示,选择执行全表扫描的成本要低于使用索引的成本.
为什么使用全表扫描成本更低?
当然,这仅仅是在重复问题.为什么全表扫描的成本会低于使用索引的成本呢?
通过深入调查这个问题,你将揭开了基于成本的优化器的关键机制(也是致命的错误假设).
让我们通过运行下面这个查询来开始我们的考查:
select
table_name,
blevel,
avg_data_blocks_per_key,
avg_leaf_blocks_per_key,
clustering_factor
from user_indexes;
下表是对应的输出结果:
| T1 | T2 | |
|---|---|---|
| Blevel | 1 | 1 |
| Data block / key | 1 | 1 |
| Leaf block / key | 1 | 15 |
| Clustering factor | 96 | 3000 |
请特别关注”data block per key”的值. 如果你执行一个完全基于这个索引的键值的等值校验的查询语句,这个值就是Oracle认为必须访问的不同数据块的数量.
因此,我们的查询语句的成本是从哪儿来得呢?就Oracle而言,如果输入的键值为45,当从表T1获取数据时,我们就可以访问一个索引叶子块以及一个表块(两个块),因此成本为2.
如果对表T2执行同样的查询,我们就必须访问一个索引叶子块以及15个表块(总共16个块),因此成本为16.
很明显,根据这种观点,表T1的索引比表T2的索引更加理想.不过,这就带来了两个未解决的问题:
全表扫描的成本来自何处,为什么两张表之间的avg_data_blocks_per_key的数值差别如此大?
第二个问题的答案很简单.回头再看看表T1的定义,它使用trunc()函数来生成N1的值,使用”rownum -1″除以15并截成整数.
Trunc(675/15) = 45
Trunc(676/15) = 45
…
Trunc(689/15) = 45
所有值为45的记录实际上都是一条接着一条连续(可能所有的都能够保存到一个数据块中)的出现的.
表T2使用mod()函数来生成N1的值,使用对rownum取200的模的方式.
mod(45,200) = 45
mod(245,200) = 45
…
mod(2845,200) = 45
值为45的记录要隔200才会在表中出现一次(可能导致每一个相关数据块中都不会超过1条记录).
通过对表的分析,Oracle可以得到我们表中的数据分布的完美的描述.从而优化器才确切的明白,对于我们的查询,Oracle将需要访问多少个数据块,在这个简单的例子中,这个查询的成本就是需要访问的数据块的数量.
但是为什么是全表扫描呢?
我们看到,对于同样的执行路径,对表T2进行索引访问的成本要远远高于对表T1的索引访问成本,但是为什么会选择使用全表扫描呢?
这个问题将让我们发现Oracle做的两个过于简单甚至不恰当的假设.
第一个假设是,每个块访问都视为一次物理磁盘读,第二个假设是,多块读的速度与单块读一样.
因此,这些假设将对我们的这个实验产生什么影响?
如果使用下面的查询语句查询user_tables表:
select
table_name,
blocks
from user_tables;
你将发现这两张表每个都是占用96个数据块.
在文章开头,我说过这个测试用例运行在一个db_file_multiblock_read_count的值为8的版本为8的Oracle系统系统上.
粗略地讲,Oracle认为它可以通过12(96/8)次磁盘读请求来读出所有的96个数据块.
由于通过索引访问表需要16个块(等于物理读)请求, 从Oracle的可悲的受骗的视角看的话,选择全表扫描显得更清晰也更快捷.
瞧!如果你要访问的数据适当地散布在表上,即使只有很小比例的数据也会选择使用全表扫描,在数据块非常多(也就是表很大)而返回的记录数很少的时候,这个问题还会被放大.
校正
实际上,可能你已经发现,我计算出来的扫描读次数为12,而执行计划中报告的成本是15.一种轻微的简化版本认为表扫描(或者索引快速全扫描)的成本为
‘number of blocks’ /
db_file_multiblock_read_count.
Oracle使用一种”调整后”的多块读的值来做这种计算(然而,在扫描开始之后,它仍然尽力使用这个真实的请求值来扫描).
为了方面查询,下表对比了几个真实值与调整后的值:
| Actual | Adjusted |
|---|---|
| 4 | 3.175 |
| 8 | 6.589 |
| 16 | 10.398 |
| 32 | 16.409 |
| 64 | 25.895 |
| 128 | 40.865 |
你还将发现,当你为这个参数提供一个不切实际的很大的值后,Oracle可以为你提供保护来避免发生错误.
顺便提一下,Oracle 9中有一点小小的改变,表扫描的成本还会做进一步的调整(对相除之后的结果加1),这意味着Oracle 9中的表扫描的成本会比Oracle 8中大一点点,从而索引会变得更可能被使用一点点.
修正
我们已经看到,优化器有两个内置的假设,而这两个假设又不是很合理.
- 一次单块读的成本与一次多块读的成本一样-(实际上不大可能,特别是运行在没有顺序的文件系统上的时候)
- 一次块访问就是一次物理磁盘读-(那么Buffer Cache是干什么吃的?)
从Oracle 8.1刚发布开始,就有多个参数可供我们以一种相当切合实际的方式来修正这些假设.
Tim Gorman的文章为这些参数提供了一个切实的描述,下面是简单的描述:
- Optimizer_index_cost_adj的值范围为1到10000,默认为100.实际上,这个参数描述的是,相对于一次多块读来讲,一次单块读有多么便宜.如果它的值为30,也就是高速Oracle一次单块读的成本是一次多块读的成本的30%. 从而Oracle就会因此这个参数的值很小而更多的倾向于选择使用索引访问.
- Optimizer_index_caching的值访问为0到100,默认为0.这个参数告诉Oracle,假定索引块将在Buffer Cache中存在百分比.在这个例子中,将这个值设置为接近100的值将助长使用索引而不是表扫描.
关于这些参数的真正美好的事情是,可以将他们设置成”符合实际”的值.
将optimizer_index_caching设置成”buffer cache hit ratio”范围内的一个值(你需要自己决定具体是按照default pool,keep pool还是这两个的某种组合来得到这个数值).
Optimizer_index_cost_adj的值的设置要更加复杂一点.检查v$system_event视图中等待事件”db file scattered read”(多块读取)与”db file sequential read”(单块读取)的有代表性的等待时间.用后者(单块读的等待时间)除以前者(多块读的等待时间)并乘以100.
改进
不要忘了,这两个参数可能需要一天(周)的不同时段进行调整以反映终端用户的工作负载.仅仅取得一组数字,就一直使用下去,是不可行的.
很高兴,在Oracle 9中,情况得到了改善.你可以收集系统统计信息,通常就包含以下四个统计数据:
- 单块读的平均读取时间
- 多块读的平均读取时间
- 实际发生的多块读的平均读取块数
- CPU的理论可用速度
要详细介绍这个特性足够配得上一整篇文章,但是这里我特别强调一点,前三个统计值使得Oracle可以明白对多块读的真实成本(相对单块读来讲).实际上,CPU速度使得Oracle可以得出不适宜的访问机制的CPU成本,比如,读取一个数据块中的每条记录以找出特定的数据值,以及与此相似的行为.
当你将系统升级到Oracle 9时,你首先需要检查的事情就是是否正确使用系统统计信息.单单这个特性就可能大大降低你尝试”优化”的糟糕的SQL的时间.
顺便提一下,尽管系统统计信息带来了惊人的效果,这两个优化器调整参数仍然有效,虽然使用它们的确切地公式在Oracle 8与Oracle 9之间发生了变化.
主题的变种
当然,我选择了一个非常特殊的例子,一个单列非唯一索引上的等值查询,并且表中没有空值,这种情况非常容易处理.(我甚至都没有提及索引的blevel与clustering_factor.)Oracle还有多个不同的方法来处理更加一般的例子.
考虑如下这些我为了方便而忽视的情况:
- 多列索引
- 使用多列索引中的部分列
- 范围扫描
- 唯一索引
- 由非唯一索引代表的唯一约束
- 索引跳跃扫描
- 只查询索引的语句
- 位图索引
- 空值的影响
这个列表还可以不停地列下去.并没有一个简单的公式来告诉你Oracle是如何计算它的成本,只存在一个通用准则,通过它你可以了解这个方法的梗概,以及一组可以应用到不同情形下的不同计算公式.
不管怎样,本文的目的是让你知道有这个通用准则,以及优化器策略中内嵌的两个基本假设.我希望,这篇文章可以帮助你更加深入的理解那些优化器做出来的众所周知的怪事.
进一步阅读
- Tim Gorman: www.evdbt.com. “The Search for Intelligent Life in the Cost Based Optimiser.”
- Wolfgang Breitling: www.centrexcc.com. “Looking under the hood of the CBO.”
闪存表空间 VS 数据库Flash Cache
有2人发表了评论 | 赶紧发表评论吧 | 作者:jametong
本文翻译自Guy Harrison的blog: Flash tablespace vs. DB Flash Cache, 这是他写的关于Flash Cache系列文章的最后一篇,另外还会翻译两篇Kevin Closson写的关于Flash Cache的相关文章.
之前两篇关于Flash Cache的文章如下:原文链接: 闪存表空间 VS 数据库Flash Cache
闪存表空间 VS 数据库Flash Cache
在这篇文章中,我将根据我最近针对使用SSD作为数据文件的存储以及使用Oracle 11GR2数据库Flash Cache所做的测试,给出一份两者的性能对比.
有时,我的整个职业生涯看上去都是在等待旋转磁盘的终结.这项技术是如此古老,能力限制如此明确,如此机械.因此,SSD作为一种数据库存储介质越来越可行(Oracle 11GR2已经直接支持这一点),这个事实令人振奋.
使用SSD作为数据库存储的一部分确实会产生很大的问题,但是,理解闪存SSD的性能特征却是非常重要的,它可以帮助我们确保不会不当地使用它.
SSD有以下两个特征:
- 基于闪存的SSD使用与常见的USB盘相似的闪存技术,这种USB盘已经在小容量移动数据存储领域替代软盘.闪存RAM比较便宜,提供不需要电池备份的持久存储,因此其耗电量也很低.
- 基于DDR RAM的SSD使用本质上与服务器核心内存差别不大的内存模块.这种RAM需要有持久存储(如磁盘或闪存RAM)和内部电池来支撑.在发生电力故障的时候,电池可以提供足够的电力来保证可以将RAM内存中的内容写到持久存储.
DDR SSD非常昂贵(以及$$/GB这个级别),以致于目前无法作为专业的数据库设备使用.但是,基于闪存的SSD磁盘越来越称为机械磁盘的一个可行的替代选项.
读,写以及擦写操作
闪存盘存储是按照页(一般为4K)以及块(一般为256K)来组织的.对于读操作来讲,闪存盘可以从单个页(page)快速返回结果.往一个页中写数据要慢很多(可能要慢10倍).然而,只有在块中刚好有一个空闲的页的往页写才能达到这个速度.如果我们需要往整个块写数据,必须先清除块内的内容才可以.维基百科关于SSD的条目给出了下面这个关于查找/写以及擦写的时间:

当一个闪存SSD盘渐渐填满数据时,需要清除操作的块级别的写操作的比例逐渐增加,闪存SSD的写性能也相应下降.
TRIM API函数
高端的闪存SSD支持一种叫做TRIM的API,这个功能使得OS可以主动提前清除整个块,从而写操作可以在只有一个页级别的IO内完成.大部分高端的SSD盘还支持一种防磨损算法,这种算法可以在设备上移动热点页以避免块级别出现故障的风险.闪存盘在块变得不可靠之前只支持一定次数的擦写操作,加入磁盘可以自动将热点页在物理存储上移动时,这个缺陷就可以得到缓解.
MLC vs SLC
廉价的闪存盘一般都使用MLC(Multi-Level-Cell)技术,它可以实现在一个单元中存储两位的数据,而使用SLC时一个单元中只能保存一位数据.MLC的效果是以牺牲性能的代价来提高数据密度,特别是写性能.从数据丢失的角度讲,MLC也是更加不可靠的.如果你关心写性能,那么或许你应该避免使用基于MLC的SSD.
通常,如果你想要一个高性能的闪存SSD的话(如果它不是高性能的,干嘛还要它呢?),你就应该选择基于SLC的闪存SSD,并且是支持TRIM API以及有着好的防磨损能力的SSD.在我的测试中,我使用一个Intel X-25 E 32GB的SSD盘.它大概需要600澳元(大概534美元).
读写速度差异的问题
假设大部分数据库都是读比写多,我们还需要担心闪存SSD在查找时间与写时间方面的差异吗?毫无疑问答案是YES.对于一个Oracle数据库来讲,当通过Buffer Cache处理事务活动时,一个设备的读能力与往这个设备的写能力之间有很大的不匹配会非常有害.
这个问题与Buffer Cache中的数据的缓存有关.如果往Buffer Cache中放入数据块比从里面写出简单很多,那么Buffer Cache就很可能会被脏块填满,从而出现free buffer waits等待.下图展示了free buffer waits是如何出现的:

如果使用的是廉价的闪存盘,那么写速度就会比读速度慢更多,最终free buffer waits等待将成为事务活动高峰时期的限制因素.
Oracle数据库Flash Cache
Oracle的数据库Flash Cache提供了另外一种利用闪存SSD的途径. 它不是将整个数据文件放到闪存上,而是将闪存作为二级缓存使用.Flash Cache可以非常大从而加快经常被访问的数据块的读速度.但是,如果闪存盘非常繁忙的话,Oracle就只是尽量少写缓存.这样,我们就可以得到闪存来优化读操作的好处,而不用承担多少写操作带来的损失.
我在前一篇文章中总结了Flash Cache的处理算法,下面是我在那篇文章中使用的图表,它概括了当数据库使用Flash Cache时一个数据块的生命周期.
这个架构的关键点是,只有在DBWR没有超负荷时,它才会往Flash Cache中写入数据块.当DBWR逐渐变得繁忙时,往Flash Cache中的写操作将被忽略(这将会降低Flash Cache的效率),它可以防止Buffer Cache被脏块填满,从而导致free buffer waits等待事件的出现.
闪存盘的读性能
让我们来看在实际操作中它是如何表现的.下面来看当我们针对如下情况执行500,000次随机索引读取时的性能对比:
- 1. 一个在机械磁盘上的表,不使用Flash Cache
- 2. 一个在机械磁盘上的表,使用Flash Cache
- 3. 一个直接存储在闪存盘上的表
这个机械磁盘是一块希捷7200.7 80GB Barracuda磁盘,同时这个SSD盘是一块Intel X-25 E 32GB盘(一块非常高端的SLC盘).在这两种情况下,表空间都是创建在裸设备上从而避免文件系统缓存的影响,并且重做日志(Redo Log)以及其他表空间都放置在一个独立的磁盘上.
下面是相关的读性能:

如你所料,将表直接放置在闪存上是最快的,因为这里只有读IO,并且每个读IO都是针对这个非常快的闪存SSD.注意,Flash Cache也同样带来了很大的好处,数据库文件的IO数量下降了,当然从Flash Cache读取的速度是非常快的.
更新性能
下面,我将测量基于主键的更新操作的性能.这样一个更新操作的性能是由读性能(读取这个数据块到缓存中)与DBWR性能共同决定的.如果DBWR写出脏块的速度不是足够快的话,就会出现free buffer waits与write complete waits等待事件了.
我最初预计,对闪存表空间的测试肯定会遇到大量的free buffer waits,因为理论上讲闪存的写操作要比闪存的读操作慢很多很多. 我认为使用Flash Cache将会避免这个问题,并且能够提供更好的性能. 然而,结果非常出人意外.

我使用不同的负荷重复进行上面的测试,但是每一次结果都非常相似.然而,使用Flash Cache还是要比不使用Flash Cache来得好,将表直接存储到闪存储存上性能就更好了.X-25 E SSD盘支持一个远远超出我预期的写频率(大概2000次写操作/秒).Intel 宣称(现在我相信了)他们拥有精密的算法可以有效避免通常与闪存SSD存储介质有关的写损失.
注意,实际上大部分的free buffer waits等待事件的产生都是由Flash Cache的配置导致的.Flash Cache使得Oracle的逻辑读的速度更快,但是由于物理写仍然会面对相对更慢的机械旋转磁盘,Buffer Cache常常会被脏块所填满.
或许随着时间的流逝,当所有的块都被至少写过一次,清除操作变得更加普遍时,X-25的性能会出现下降.然而,如果TRIM API能够正常工作,那么理论上这种性能恶化应该可以避免.注意,并不是所有环境都支持TRIM API,并不是所有的SSD都支持,旧版本的Windows操作可能也不支持.
Write complete waits: flash cache
在上一篇文章中,我注意到,当DBWR繁忙的时候会跳过而不写Flash Cache,这一点应该可以避免Flash Cache的活动不会成为导致free buffer waits的直接原因.然而,我观察到,在特定的负荷下会遇到大量的”write complete waits: flash cache“等待事件.例如,下面的输出结果显示大约有75%的实耗时间(elapsed time)消耗在这个等待事件上.

当一个会话想要修改一个数据,但同时DBWR又正在将这个数据块写到数据文件的时候会出现write complete waits等待事件.Flash Cache相对应的等待事件的出现也是由于类似的原因,但这是不是写数据块到数据文件,而将由DBWR将它写到Flash Cache.当一个特定的数据块被非常频繁的修改的时候,这种现象就会出现;在这种情况下,数据块在被DBWR写完之前被修改的机会相当高.
结论
X-25 E闪存SSD盘给我留下了非常深刻的印象.如果它可以长时间的保持优异的写吞吐量的话,相比于传统的机械旋转磁盘以及11GR2的数据库Flash Cache,它提供的吞吐量有惊人的优势.不过,还有几句忠告:
- 我没有长时间对这个SSD盘做压力测试.有些闪存盘在磨损后显示出更长写延迟.理论上,Intel X-25 E的TRIM功能应该可以避免这一点,但我并没有在这些测试中验证这一点.
- 在经过长时间的使用后,闪存盘可能会成为一种不可靠的存储介质.当然,任何磁盘都会损坏,但是闪存盘比机械磁盘更加容易损坏(虽然在损坏之前它可能可以做更多的工作).
- 在我的测试中,我的表很小,足以完全存放在闪存存储上.如果做不到这一点,使用数据库Flash Cache让我们在无法将整个数据库都放到SSD上的时候,也可以利用闪存盘.
现在,我更加热衷于使用闪存盘了.只要你购买一个高性能的SSD盘(考虑SLC & TRIM),你就可以考虑将整个表空间或者数据库都存放在这些盘上.如果担负不起所有的数据都存放在高端的闪存盘上,还可以考虑使用11GR2的数据库Flash Cache来获得显著的性能提升.
数据库Flash Cache(II)
目前还没有人发表评论 | 赶紧发表评论吧 | 作者:jametong
本文翻译自Guy Harrison的blog: More on the database flash cache, 这是他写的关于Flash Cache系列文章的第二篇, 后面还有一篇对比测试Flash Database与Flash Cache的文章, 我也将翻译出来放到此处, 另外还会翻译两篇Kevin Closson写的关于Flash Cache的相关文章.
原文地址: 数据库Flash Cache(II)
数据库Flash Cache(II)
我非常期待我的高性能Flash SSD(一个Intel X-25E),但同时,我已经在一套便宜的硬件设备上(我的前一篇文章中对此做了说明)做了很多数据库Flash Cache的测试. 有时,在较差的硬件上测试新特性也非常有用,因为你可以观察到一些在高速运行的环境下不会发生的现象.
最初,我天真地认为数据块会被Oracle的服务进程拷贝到Flash Cache中.比如,当我从磁盘读取的时候,同时将数据块放到Buffer Cache与Flash Cache中.然而,通过观察,实际情形可能是, 当数据块将要被刷出Buffer Cache的时候, 由DBWR进程将此数据块从Buffer Cache移入到Flash Cache中.
当然,这是一种更好的处理方式.DBWR可以异步的将数据块写入Flash Cache中,从而用户会话可以获得好处,在数据块写入Flash Cache中的时候不需要进行等待(比从磁盘读取耗费更少的时间).
因此,一个数据块可能有一个如下图的生命周期:
- Oracle服务进程从磁盘读取文件的一个数据块,并将其放到Buffer Cache中
- 如果一个会话在稍后需要访问这个数据块,并且这个数据块仍然在Buffer Cache中,就可以直接从Buffer Cache中读取这个数据块
- 在这个数据块离开Buffer Cache之前,DBWR将它写入Flash Cache(如果DBWR不是太忙的话)
- 如果一个会话稍后需要访问这个数据块,并且它还在Flash Cache中的话,就可以从Flash Cache中读取这个数据块(有可能会将这个数据块放回到Buffer Cache中)
- 如果这个数据块被修改了,DBWR进程最终会将这个数据块写回磁盘.(问题:Flash Cache中那些没有被修改过的数据块会发生什么呢?)
DBWR 与 Flash Cache
从闪存上读取的速度是非常快的,但是往闪存上写就要慢很多了.因此,为了避免性能问题,DBWR进程应该:
- 1. 不要去写闪存, 除非不得不写,并且
- 2. 如果有其他更重要的活动的话,一点都不要写闪存.
对于第一点,在数据块将要被刷出Buffer Cache之前,DBWR进程不要填充Flash Cache.也就是说,DBWR不会为了防止数据块将要被刷出Buffer Cache去写这个数据块,而只是这个数据块确实(或许是可能性很高的时候)将要被刷出Buffer Cache的时候才会将其写入Flash Cache.在数据块被写入Buffer Cache的时候,我们没有发现对Flash Cache的写操作, 同时有其他数据块被刷出Buffer Cache的情况除外.
第二点,当DBWR忙于将脏块写入到磁盘的时候,不会去写Flash Cache,也不会推迟将要被刷出Buffer Cache的数据块的刷出时间.DBWR进程必须尽可能块地清除Buffer Cache中的脏块,否则”free buffer waits”等待事件就会阻止新的数据块被读入Buffer Cache中.如果数据块将要被刷出Buffer Cache,而同时DBWR进程又很忙,这些数据块将不会被写入Flash Cache,统计信息项’flash cache insert skip: DBWR overloaded’将出现增长.
度量DB Flash Cache的效率
下面的查询报告了DBWR由于太忙或其他原因而跳过写Flash Cache的次数:
- 我认为我理解”DBWR overloaded”统计项的意思,DBWR忙于写脏块到磁盘而没有足够的处理能力来将干净的数据块写入Flash Cache.
- “flash cache insert skip: exists”统计项也比较容易理解.如果我们从Flash Cache将一个数据块读回Buffer Cache,它还会继续保留在Flash Cache中.当它再次在Buffer Cache中被刷出的时候就不再需要写入Flash Cache了.
- “not current”统计项可能表示,DBWR明白磁盘或者Buffer Cache中还有这个数据块的一个更新的拷贝,因此拒绝写一个版本到Flash Cache上.
- “not useful”统计项,我无法理解…
我认为这里最大的启示是:
在一个DBWR非常繁忙的系统上,DB Flash Cache可能会非常无效率.
要测量出使用Flash Cache带来的好处,我们可以计算由于使用Flash Cache而被避免掉的物理读的次数,以及乘上每种读操作耗费的平均时间.下面这个查询尝试做这个对比,虽然仅仅针对单块读操作:
结论
DB Flash Cache的架构使得我们可以对与闪存SSD相关的相对糟糕的写性能可以显得稍微放松一点.如果我们将数据文件放到闪存SSD上,我们可能会有遭遇”free buffer waits”瓶颈的风险: DBWR可能会由于尝试往低速(从写延迟的角度上看的话)的闪存盘上写数据块而中止,相对于磁盘来讲,我们可能实际上遭遇了更糟糕的性能,至少对于有非常高的写频率的应用来讲.相应地,几乎不应该出现实际的负面因素,可能出现的更糟糕的情况是,DBWR太忙,以致于不会去填充Flash Cache,而使得Flash Cache变得更加无效.
DB Flash Cache的效率依赖于以下两个主要因素:
- 1. DBWR是否有足够的空间事件来写Flash Cache?
- 2. DBWR需要花费多长事件类写闪存设备?
第一个问题的答案依赖于DBWR当前的活动效率,可能也相应地依赖于你当期的磁盘阵列有多少可用的写带宽.但是,如果你的数据库已经遭遇或者将要遭遇”free buffer waits”,那么DB Flash Cache可能无法正常发挥作用,因为DBWR可能永远不会写Flash Cache.
第二个问题的答案依赖于闪存SSD的品质.通常你会选择使用拥有最小写延迟的闪存盘.那通常也意味着支持TRIM API调用,并且是使用SLC(Single Level Cell)的闪存.
Android OS手机连接802.1X网络的方法
目前还没有人发表评论 | 赶紧发表评论吧 | 作者:vogts
不少Google的G1,G2手机用户无法连接802.1X无线网络,我查阅了下网上资料,说是在设计的时候UI。所以无法出现输入USERID,PASSWORD的登录框的缘故。但是可以通过修改配置文件实现。
进入终端,su到root。然后修改 /data/misc/wifi/wpa_supplicant.conf 文件。
在配置文件的末端增加一段:
network={
ssid=”xxx”
proto=RSN
key_mgmt=WPA-EAP
eap=PEAP
identity=”xxxx”
password=”xxxx”
phase1=”peapver=0″
phase2=”auth=MSCHAPV2″
priority=203
}
把XXXX替换成你的变量即可。然后开启无线网络,那么就OK了。
btw:
谢谢sky友情提供手机,给我参考。谢谢!
针对stored outline做得几个测试
目前还没有人发表评论 | 赶紧发表评论吧 | 作者:jametong
主要的测试代码以及测试方法都依赖于之前翻译的两篇Jonathan Lewis的关于stored outlines的两篇文章:
Oracle 8i/9i中的执行计划稳定性
在Oracle 9中伪造存储概要
此文的原文为 针对stored outline做得几个测试
1. 测试场景
- os : Red Hat Enterprise Linux AS release 4 (Nahant Update 6)
- Db Version: Oracle9i Enterprise Edition Release 9.2.0.8.0 - 64bit Production
2. 初始化环境.
sys@test>@tmp sys@test>create user james identified by james default tablespace users temporary tablespace temp; User created. Elapsed: 00:00:00.00 sys@test>grant create session,create table,create procedure,create any outline,alter session to james; Grant succeeded. Elapsed: 00:00:00.01 sys@test>alter user james quota unlimited on users; User altered. Elapsed: 00:00:00.00 sys@test> sys@test>@conn james/james sys@test>SET TERMOUT OFF james@test> james@test>set feedback off james@test>alter session set cursor_sharing = exact 2 / Elapsed: 00:00:00.00 james@test>alter session set nls_date_format = 'yyyy-mm-dd hh24:mi:ss' 2 / Elapsed: 00:00:00.00 james@test>SET TERMOUT ON james@test>set feedback on james@test>set timing on james@test> james@test>create table so_demo ( 2 n1 number, 3 n2 number, 4 v1 varchar2(10) 5 ); Table created. Elapsed: 00:00:00.05 james@test> james@test>insert into so_demo values (1,1,'One'); 1 row created. Elapsed: 00:00:00.00 james@test> james@test>create index sd_i1 on so_demo(n1); Index created. Elapsed: 00:00:00.01 james@test>create index sd_i2 on so_demo(n2); Index created. Elapsed: 00:00:00.00 james@test> james@test>analyze table so_demo compute statistics; Table analyzed. Elapsed: 00:00:00.01 james@test> james@test>!cat > c_proc.sql <create or replace procedure get_value ( 2 i_n1 in number, 3 i_n2 in number, 4 io_v1 out varchar2 5 ) 6 as 7 begin 8 select v1 9 into io_v1 10 from so_demo 11 where n1 = i_n1 12 and n2 = i_n2 13 ; 14 end; 15 / Procedure created. Elapsed: 00:00:00.02 james@test>_END_ SP2-0042: unknown command "_END_" - rest of line ignored. james@test>!wrap iname=c_proc.sql PL/SQL Wrapper: Release 9.2.0.8.0- 64bit Production on Tue Feb 09 23:29:26 2010 Copyright (c) Oracle Corporation 1993, 2001. All Rights Reserved. Processing c_proc.sql to c_proc.plb james@test>@c_proc.plb james@test>
3. 得到当前此SQL的存储概要.
james@test>var m_value varchar2(10); james@test>alter session set create_stored_outlines = demo; Session altered. Elapsed: 00:00:00.00 james@test>exec get_value(1, 1, :m_value); PL/SQL procedure successfully completed. Elapsed: 00:00:00.03 james@test>alter session set create_stored_outlines = false; Session altered. Elapsed: 00:00:00.00 james@test>col category format a20 james@test>select name, category, used, sql_text from user_outlines where category = 'DEMO'; NAME CATEGORY USED SQL_TEXT ------------------------------ -------------------- --------- -------------------------------------------------------------------------------- SYS_OUTLINE_100209233527397 DEMO UNUSED SELECT V1 FROM SO_DEMO WHERE N1 = :B2 AND N2 = :B1 1 row selected. Elapsed: 00:00:00.01 james@test>col hint format a30 james@test>select name, stage, hint from user_outline_hints where name = 'SYS_OUTLINE_100209233527397'; NAME STAGE HINT ------------------------------ ---------- ------------------------------ SYS_OUTLINE_100209233527397 3 NO_EXPAND SYS_OUTLINE_100209233527397 3 ORDERED SYS_OUTLINE_100209233527397 3 NO_FACT(SO_DEMO) SYS_OUTLINE_100209233527397 3 INDEX(SO_DEMO SD_I1) SYS_OUTLINE_100209233527397 2 NOREWRITE SYS_OUTLINE_100209233527397 1 NOREWRITE 6 rows selected. Elapsed: 00:00:00.00 james@test>
4. 第一种改变存储概要的方法, 也即通过直接修改存储概要表来调整.
4.1 生成新的用来伪造的存储概要
james@test>create or replace outline so_fix
2 for category demo on
3 select /*+ index(so_demo, sd_i2) */ v1
4 from so_demo
5 where n1 = 1
6 and n2 = 2;
Outline created.
Elapsed: 00:00:00.01
james@test>
james@test>select name, category, used, sql_text from user_outlines where name = 'SO_FIX';
NAME CATEGORY USED SQL_TEXT
------------------------------ -------------------- --------- --------------------------------------------------------------------------------
SO_FIX DEMO UNUSED select /*+ index(so_demo, sd_i2) */ v1
from so_demo
where n1 = 1
and n2 = 2
1 row selected.
Elapsed: 00:00:00.00
james@test>select name, stage, hint from user_outline_hints where name = 'SO_FIX';
NAME STAGE HINT
------------------------------ ---------- ------------------------------
SO_FIX 3 NO_EXPAND
SO_FIX 3 ORDERED
SO_FIX 3 NO_FACT(SO_DEMO)
SO_FIX 3 INDEX(SO_DEMO SD_I2)
SO_FIX 2 NOREWRITE
SO_FIX 1 NOREWRITE
6 rows selected.
Elapsed: 00:00:00.01
4.2 进入outln Schema Crack系统的outln相关表, 调换两个存储概要的实际内容.
outln@test>@tmp
outln@test>update outln.ol$hints
2 set ol_name =
3 decode(
4 ol_name,
5 'SO_FIX','SYS_OUTLINE_100209233527397',
6 'SYS_OUTLINE_020503165427311','SO_FIX'
7 )
8 where ol_name in ('SYS_OUTLINE_100209233527397','SO_FIX');
12 rows updated.
Elapsed: 00:00:00.00
outln@test>
outln@test>update outln.ol$ ol1
2 set hintcount = (
3 select hintcount
4 from ol$ ol2
5 where ol2.ol_name in ('SYS_OUTLINE_100209233527397',' SO_FIX')
6 and ol2.ol_name != ol1.ol_name
7 )
8 where
9 ol1.ol_name in ('SYS_OUTLINE_100209233527397','SO_FIX');
2 rows updated.
Elapsed: 00:00:00.00
outln@test>commit;
Commit complete.
Elapsed: 00:00:00.00
outln@test>
4.3 打开使用stored outlines的参数, 测试是否确实使用了存储概要, 并附上10046 Trace得到的信息.
james@test>@tmp james@test>var m_value varchar2(10); james@test>alter session set use_stored_outlines = demo; Session altered. Elapsed: 00:00:00.00 james@test>alter session set events '10046 trace name context forever,level 12'; Session altered. Elapsed: 00:00:00.00 james@test>exec get_value(1, 1, :m_value); PL/SQL procedure successfully completed. Elapsed: 00:00:00.03 james@test>alter session set events '10046 trace name context off'; Session altered. Elapsed: 00:00:00.00 james@test>alter session set use_stored_outlines = false; Session altered. Elapsed: 00:00:00.00 james@test>col spid new_value spid james@test>select spid from v$process where addr = ( 2 select paddr from v$session a,v$mystat b where a.sid = b.sid and rownum
sys@test>@plan
Enter value for hash_value: 3638091068
SQL_TEXT
----------------------------------------------------------------
SELECT V1 FROM SO_DEMO WHERE N1 = :B2 AND N2 = :B1
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | |
|* 1 | TABLE ACCESS BY INDEX ROWID| SO_DEMO | 1 | 7 | 3 (34)|
|* 2 | INDEX RANGE SCAN | SD_I2 | 1 | | 2 (50)|
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("SO_DEMO"."N1"=:B2)
2 - access("SO_DEMO"."N2"=:B1)
sys@test>
此时, 可以发现, 调整过后的存储概要已经发生作用了, 没有使用最初的索引SD_I1而是使用了我们指定的索引SD_I2.
5. 使用在另一个Schema创建View的方式来替换存储概要.
5.1 首先清除掉前面测试使用的存储概要, 并重新生成存储概要.
james@test>var m_value varchar2(10); james@test>alter session set create_stored_outlines = demo; Session altered. Elapsed: 00:00:00.01 james@test>exec get_value(1, 1, :m_value); PL/SQL procedure successfully completed. Elapsed: 00:00:00.05 james@test>alter session set create_stored_outlines = false; Session altered. Elapsed: 00:00:00.00 james@test> james@test>col category format a15 james@test>select name, category, used, sql_text from user_outlines where category = 'DEMO'; NAME CATEGORY USED SQL_TEXT ------------------------------ --------------- --------- -------------------------------------------------------------------------------- SYS_OUTLINE_100210003729028 DEMO UNUSED SELECT V1 FROM SO_DEMO WHERE N1 = :B2 AND N2 = :B1 1 row selected. Elapsed: 00:00:00.01 james@test>col hint format a30 james@test>l 1* select name, stage, hint from user_outline_hints where name = 'SYS_OUTLINE_100210003729028' james@test>/ NAME STAGE HINT ------------------------------ ---------- ------------------------------ SYS_OUTLINE_100210003729028 3 NO_EXPAND SYS_OUTLINE_100210003729028 3 ORDERED SYS_OUTLINE_100210003729028 3 NO_FACT(SO_DEMO) SYS_OUTLINE_100210003729028 3 INDEX(SO_DEMO SD_I1) ******************** SYS_OUTLINE_100210003729028 2 NOREWRITE SYS_OUTLINE_100210003729028 1 NOREWRITE 6 rows selected.
5.2 登录一个新的用户, 创建一个View并重新编译这个存储概要.
james2@test>create or replace view so_demo as 2 select /*+ index(so_demo,sd_i2) */ 3 * 4 from james.so_demo; View created. Elapsed: 00:00:00.07 james2@test>alter outline sys_outline_100210003729028 rebuild; Outline altered. Elapsed: 00:00:00.01 james2@test>@conn james/james Elapsed: 00:00:00.00 Elapsed: 00:00:00.00 james@test>col category format a15 james@test>select name, category, used, sql_text from user_outlines where category = 'DEMO'; NAME CATEGORY USED SQL_TEXT ------------------------------ --------------- --------- -------------------------------------------------------------------------------- SYS_OUTLINE_100210003729028 DEMO UNUSED SELECT V1 FROM SO_DEMO WHERE N1 = :B2 AND N2 = :B1 1 row selected. Elapsed: 00:00:00.01 james@test>col hint format a30 james@test>select name, stage, hint from user_outline_hints where name = 'SYS_OUTLINE_100210003729028' 2 / NAME STAGE HINT ------------------------------ ---------- ------------------------------ SYS_OUTLINE_100210003729028 3 NO_EXPAND SYS_OUTLINE_100210003729028 3 ORDERED SYS_OUTLINE_100210003729028 3 NO_FACT(SO_DEMO) SYS_OUTLINE_100210003729028 3 INDEX(SO_DEMO SD_I2) ******************** SYS_OUTLINE_100210003729028 2 NOREWRITE SYS_OUTLINE_100210003729028 1 NOREWRITE SYS_OUTLINE_100210003729028 1 NOREWRITE 7 rows selected. Elapsed: 00:00:00.00
关注使用星号标识的两行, 你将发现存储概要确实已经通过这种方式替换掉了
6. 使用在新Schema中创建一个类似的新表来实现改变存储概要的方法.
6.1. 通过在原Schema重新编译outline使其回到最初状态.
james@test>alter outline SYS_OUTLINE_100210003729028 rebuild; Outline altered. Elapsed: 00:00:00.01 james@test>select name, stage, hint from user_outline_hints where name = 'SYS_OUTLINE_100210003729028'; NAME STAGE HINT ------------------------------ ---------- ------------------------------ SYS_OUTLINE_100210003729028 3 NO_EXPAND SYS_OUTLINE_100210003729028 3 ORDERED SYS_OUTLINE_100210003729028 3 NO_FACT(SO_DEMO) SYS_OUTLINE_100210003729028 3 INDEX(SO_DEMO SD_I1) ***************** SYS_OUTLINE_100210003729028 2 NOREWRITE SYS_OUTLINE_100210003729028 1 NOREWRITE 6 rows selected. Elapsed: 00:00:00.00 james@test>
6.2. 在新的Schema创建对应表,以及索引SD_I2. 并重新编译存储概要.
james2@test>l 1 create table so_demo ( 2 n1 number, 3 n2 number, 4 v1 varchar2(10) 5* ) james2@test>/ Table created. Elapsed: 00:00:00.02 james2@test>create index sd_i2 on so_demo(n2); Index created. Elapsed: 00:00:00.08 james2@test>alter outline sys_outline_100210003729028 rebuild; Outline altered. Elapsed: 00:00:00.00 james@test>col hint format a30 james@test>select name, stage, hint from user_outline_hints where name = 'SYS_OUTLINE_100210003729028'; NAME STAGE HINT ------------------------------ ---------- ------------------------------ SYS_OUTLINE_100210003729028 3 NO_EXPAND SYS_OUTLINE_100210003729028 3 ORDERED SYS_OUTLINE_100210003729028 3 NO_FACT(SO_DEMO) SYS_OUTLINE_100210003729028 3 INDEX(SO_DEMO SD_I2) ********************* SYS_OUTLINE_100210003729028 2 NOREWRITE SYS_OUTLINE_100210003729028 1 NOREWRITE
这个时候, 你将发现存储概要也改成我们最初想要的结果了, 也即用索引SD_I2替换掉了SD_I1.
使用Oracle 11GR2 数据库Flash Cache
有1人发表了评论 | 赶紧发表评论吧 | 作者:jametong
本文翻译自Guy Harrison的blog: Using the Oracle 11GR2 database flash cache, 这是他写的关于Flash Cache系列文章的第一篇, 后面还有两篇, 我也将陆续翻译出来放到此处, 另外还会翻译两篇Kevin Closson写的关于Flash Cache的相关文章.
使用Oracle 11GR2 数据库Flash Cache
Oracle最近发布了一个补丁程序,使得你可以在Oracle Enterprise Linux中使用数据库Flash Cache,即使你并没有使用Exadata存储.这个补丁的名字有点隐晦:
- 8974084:META BUG FOR FLASH CACHE 11.2PL BUGS TO BACKPORT TO 11.2.0.1 OEL
只要安装好这个补丁,你就可以使用任何已存在的flash 设备作为数据库的Flash Cache.下面是我在一个非常旧的服务器与一个非常便宜的usb flash设备上做的初步尝试.相对于更优质的硬件来讲, 测试结果并不具有代表性,但是我认为,它仍然是很有趣的.
安装与配置
如果你也像我一样想在一个USB flash设备上做试验,那么也必须先挂载这个设备.在我的机器上,我创建了一个目录”/mnt/usbflash”,接着在/etc/fstab文件新增了一个如下的条目:
/dev/sda1 /mnt/usbflash vfat noauto,users,rw,umask=0 0 0
在你的系统中,你可能需要将”/dev/sda1″改成其他的设备,这依赖于你如何配置磁盘.然后就可以通过输入”mount /dev/sda1″来挂载这个闪存盘(flash drive).
一旦挂载完毕,就可以通过设置系统书db_flash_cache_files与db_flash_cache_size来配置flash cache了. 如下是我的相关设置:

注意, 参数DB_FLASH_CACHE_FILE的值必须是一个存储在闪存盘上的文件,而不是这个闪存盘的挂载点本身.
一旦这些参数设置完毕,flash cache就会被激活,并且将充当buffer cache的二级缓存. 当从主缓存移出一个block的时候,它将被移到flash cache中,从而在再次读回这个block的时候不需要产生一次访问磁盘的物理读.
监控
有多种方法来检查是否使用到了flash cache. 首先,v$sysstat视图包含多个新的统计项来展示有多少数据块被添加到flash cache中,以及在flash cache中命中的次数(从这儿下载脚本):

还有一些新的等待事件来显示往flash cache中添加条目以及从flash cache中读取条目引起的等待. 从下图可以看到,’db flash’等待的次数超过’ db file sequential read’的等待次数,虽然从flash cache读取的速度远远快于从磁盘的读取速度(但是从flash cache读取的次数也远远超过从磁盘读取的次数):

那么,请记住,我不能不为这个测试选择了最糟糕的硬件-一个老旧的俩CPU intel主机以及一个便宜的拇指磁盘.即使这样,还是发现了引人注目的问题-相对于整个处理时间来讲写开销非常严重.虽然相对于db file sequential reads来讲从flash cache中读取的速度可以节约很多时间,维护flash cache的开销也可能会非常高,因为大部分基于闪存的SSD(Solid State Disk)都有相对严重的写瓶颈.
所有基于闪存的固态盘(SSD,Solid State Disk)都有写性能方面的问题.然而,便宜的MLC(Multi Level Cell)闪存的写速度差不多相当于更加昂贵的SLC(Single Level Cell)的1/3.在闪存盘比较新时,空闲空间可以以单页(一般为4k)递增的方式来写.然而,当闪存盘越来越旧时,写操作需要先清除一个完整的128页的块,从而就会慢很多了.我的便宜的USB盘是一个旧盘,并且是MLC的,所以它的写性能是非常差的.但是,即使是最好的基于闪存的SSD,它的写速度也比读速度要慢很多,因此,有些时候使用闪存盘反而会导致数据库运行的更慢. 因此监控就显得非常重要了.
下面是几个与此相关的其他v$systat统计项:

可以通过查看V$BH视图来查看cache中的内容. 保存在flash cache中的buffer有诸如’flashcur’一类的状态,使得我们可以统计每个对象有多少buffer在主缓存中,有多少buffer在flash cache中(脚本来自这里):

在这个例子中,TXN_DATA表有85,833个块在flash cache中,有28,753个块在主buffer cache中.
结论
能让flash cache工作,我非常高兴,特别是在如此蹩脚的硬件上. 很高兴Oracle对Non-Exadata硬件开放这项技术.
我将很快在搭建一套更好的环境,从而我可以理解它在相当好的商业SSD闪存盘上是如何工作的.
我坚信, 市场需要的理想的存储体系应该包含至少两层—一层来解决海量存储,另一层解决快速检索. 但是,我们也应该保持谨慎,因为flash的写弊端可能导致我们在RAID5上遇到的类似的性能问题.
在Oracle 9中伪造存储概要
目前还没有人发表评论 | 赶紧发表评论吧 | 作者:jametong
译者注: 本文翻译自Jonathan Lewis的文章Faking Stored Outlines in Oracle 9, 可以从此处下载原文的word版本: Stored Outlines in Oracle 9.
本文与前一篇Oracle 8i/9i中的执行计划稳定性是Jonathan Lewis先生写的关于stored outline具体使用以及其中可能涉及到的风险系列文章,也是我所见到的关于stored outline介绍的最详细的文档了. 关于stored outline还有以下相关资料可以对照阅读下:
Oracle Outlines - aka Plan Stability By Kerry Osborne
Plan stability in 10g - using existing cursors to create Stored Outlines and SQL profiles By Randolf Geist
Stored Outlines and Plan Stability By Tim Hall
Tuning Third-party Vendor Oracle Systems :Tuning when you can’t touch the code By Mark Ault
在Oracle 9中伪造存储概要
在前面的文章中,我讨论到存储概要,并且描述了一种通过滥用系统来生成你所需要存储概要的方法.我同时也指出,在Oracle 9中使用这种方法存在一些风险,因为存储在数据库中的细节信息已经变得非常复杂.在接下来的文章中,我将介绍一种合法的操作存储概要的方法,这种方法可以应用在Oracle 8与Oracle 9中.这篇文章的细节都是基于实验得出的,实验环境是Oracle 8.1.7.0与Oracle 9.2.0.1的默认安装环境.
回顾
当你知道如何通过给一段DML语句添加提示就可以让它运行的快很多,但是你却没有访问源代码并将提示放到适当位置的途径, 你会怎么做?
在上一篇文章中,我展示了你可以如何用存储概要(也被称为执行计划稳定性)来驱使数据库引擎为你做这种工作.
一个存储概要由两个组件组成(宽泛地讲)-一个你希望控制的SQL语句,一组每当Oracle发现这条SQL被优化都将在它上面应用的提示.这两个组件都被保存在一个被称为outln的数据库schema中.
我们可以使用一组如图-1中类似的查询语句来检查保存在其中的SQL语句,以及附着在这条SQL语句上的提示.
select name, used, sql_text
from user_outlines
where category = 'DEFAULT'
;
select stage, node, hint
from user_outline_hints
where name = '{one of the names}'
;
Figure 1 Examining stored outlines.
在前面的文章中,我介绍了这样一种想法来欺骗系统, 使用合法的方法创建一个存储概要, 接着,使用一个文本相似的但已经添加过提示的语句来创建一个存储概要,最后,使用一组SQL语句来交换这两个存储概要的实际结果来修复存储概要.
当时,我曾提到这种方法对Oracle 8来讲或许是安全的,但是由于在新版本中引入的变化, 在Oracle 9中可能会导致问题.
这篇文章将对这些变化进行考查, 介绍一种合法的方法来得到你想要的一组存储到outln中的提示,用来解决你的那些问题语句.
相关变化
如果你登录到outln schema(在Oracle 9中它默认是锁住的)查看可用的表清单,你将发现Oracle 9比Oracle 8多出来一张表. 这些表为:
| ol$ | SQL语句 |
| ol$hints | 提示表 |
| ol$nodes | 查询块 |
第三张表是一张新表,被用来将提示列表与这条SQL语句(一份内部重写的版本)的多个不同查询块.你还将发现,提示列表(ol$hints)也被加强了,其中还包括文本长度与偏移量的细节信息.
图2为这三张表的详细描述,用星号标注了Oracle 9中出现的新字段.
ol$ OL_NAME VARCHAR2(30) SQL_TEXT LONG TEXTLEN NUMBER SIGNATURE RAW(16) HASH_VALUE NUMBER HASH_VALUE2 NUMBER *** CATEGORY VARCHAR2(30) VERSION VARCHAR2(64) CREATOR VARCHAR2(30) TIMESTAMP DATE FLAGS NUMBER HINTCOUNT NUMBER SPARE1 NUMBER *** SPARE2 VARCHAR2(1000) *** Ol$hints OL_NAME VARCHAR2(30) HINT# NUMBER CATEGORY VARCHAR2(30) HINT_TYPE NUMBER HINT_TEXT VARCHAR2(512) STAGE# NUMBER NODE# NUMBER TABLE_NAME VARCHAR2(30) TABLE_TIN NUMBER TABLE_POS NUMBER REF_ID NUMBER *** USER_TABLE_NAME VARCHAR2(64) *** COST FLOAT(126) *** CARDINALITY FLOAT(126) *** BYTES FLOAT(126) *** HINT_TEXTOFF NUMBER *** HINT_TEXTLEN NUMBER *** JOIN_PRED VARCHAR2(2000) *** SPARE1 NUMBER *** SPARE2 NUMBER *** ol$nodes (completely new in 9) OL_NAME VARCHAR2(30) CATEGORY VARCHAR2(30) NODE_ID NUMBER PARENT_ID NUMBER NODE_TYPE NUMBER NODE_TEXTLEN NUMBER NODE_TEXTOFF NUMBER
Figure 2 The outln tables.
你可能很快会注意到多处细节-有大量信息被基于这些表的视图排除在外了.视图user_outline_hints的视图定义完全没有改变,尽管表ol$hints上新增加了10个字段.实际上,这个视图在Oracle 8的时候就极度不足,因为它遗漏了相当有用的hint#字段.
你还会注意到,Oracle 9现在有两个hash_value字段.如果你在Oracle 8与Oracle 9中对同样的SQL语句创建存储概要,你将发现它们拥有同样的hash_value,但是Oracle 9中对应的hash_value可能完全不同.
你可以也会发现,Oracle 9中的signature(签名)字段的值与Oracle 8中的值是不同的. 这是由于Oracle这两个版本之间策略上的最主要的调整就是为了提高存储概要的重复利用.在Oracle 8中,只有在你的SQL语句与存储的SQL语句完全匹配(包含空格符/大小写以及换行符)的时候才可以使用.到Oracle 9之后,这个限制放宽了,只要在去除掉重复的”空字符”并且将文本都转换成同样的大小写之后SQL语句能够匹配就可以使用存储概要了.例如,下面的两条SQL语句将使用同一个存储概要.
select * from t1 where id = 5; SELECT * FROM T1 WHERE ID = 5;
策略上的这个调整导致了第一次创建这个执行计划的SQL语句的签名的调整;如果你的数据库从Oracle 8升级到Oracle 9,就必须更新存储概要或者必须确认它们不再被使用.(事实上,别名为dbms_outln包outln_pkg包含一个特别的存储过程update_signatures来处理这个问题.
不过,关于Oracle 9中这些表的最意义重大的事情却是对查询语句中涉及到的文本与对象的极度详细描述.创建图-3中显示的例子,并在继续阅读之前详细查看ol$hints表中的内容.
drop table t1;
create table t1
nologging
as
select
rownum id,
rownum n1,
object_name,
rpad('x',500) padding
from
all_objects
where
rownum <= 100
;
alter table t1
add constraint t1_pk primary key (id);
create index t1_i1 on t1(n1);
analyze table t1 compute statistics;
create or replace outline demo_1 on
select * from t1
where id = 5
and n1 = 10
;
Figure 3 Sample code.
这个例子立足于一个简单的小表,包含两组相近的列,其中一个列为逐渐(从而也创建了索引),另外包含一个简单的非唯一索引.我们为一个典型的查询创建一个存储概要来查看我们可以如何对待它.
如果针对由这个例子创建的存储概要demo_1运行图-1中的示例查询,我们将发现这个查询将附带6个提示.
STAGE NODE HINT 3 1 NO_EXPAND 3 1 ORDERED 3 1 NO_FACT(T1) 3 1 INDEX(T1 T1_PK) 2 1 NOREWRITE 1 1 NOREWRITE
不出意外,其中的第四行显示我们将使用主键索引来访问这张表.如果我们实际上想要Oracle使用这个非唯一索引T1_I1访问表,我们该对存储概要做什么呢?理论上讲,我们可以调整这个存储概要以使得
3 1 INDEX(T1 T1_PK)
变成
3 1 INDEX(T1 T1_I1)
新特性
我们可以做的第一件事是查看包dbms_outln_edit.这个包在Oracle 9中引入,正如它的名字提示的那样,它的目标是编辑存储概要,这看上去令人充满希望.
然而,查看包的方法列表,检查文档手册,我们发现这个包只包含如下几个”编辑相关”的方法.
CREATE_EDIT_TABLES DROP_EDIT_TABLES CHANGE_JOIN_POS
前两个方法允许我们创建或删除outln用户拥有的表的本地拷贝.第三个方法允许我们交换一个存储概要计划中的表连接顺序. 哪怕仅仅是帮助我们修改一个简单的提示的方法也是没有的.目前,这个包看上去实际上一无是处-但是它们注定会越来越完善.
当然B方案就是去侵入它了!如果我们登录到outln用户,并自己诊察ol$hints表(也就是支撑视图user_outline_hints的表)的内容,我们可以尝试下面的这个更新操作:
update ol$hints set hint_text = 'INDEX(T1 T1_I1)' where ol_name = 'demo_1' and hint# = 4 ;
登录回到我们的测试Schema,清空共享池,并且打开存储概要:
connect test_user/test alter system flush shared_pool; alter session set use_stored_outlines = true;
实际上,我们将发现侵入的存储概要确实如你所愿了.但是这是一个让人不爽的解决方案,
因为我们一直会给一个关于”更改数据字典表”的严厉的警告.
旧方法(1)
接着,我们的目标就是寻找一种迂回但又看似无害的方法来改变存储概要表的内容,并且不需要直接的侵入存储概要表.
从前(在Oracle 9以前),我们有多种实现办法,它们都是基于这样一个事实,存储概要的效果仅仅取决于进来的SQL语句的文本,而完全不关心对对象类型或者对象的所有者.
将表替换成添加过提示的视图是一种有效的方法.(我相信,这种方法最初是由Tom Kyte在它的《Expert One on One: Oracle》这本书中介绍的).
连接到另外一个拥有表T1的访问权限的Schema,按照下面的定义创建一个添加过提示的视图,视图与表的名称保持一致.
Create or replace view t1 as Select /*+ index(t1,t1_i1) */ * from test_user.t1;
一旦视图创建完成,就在这个schema下使用下面的这个命令”重编译”这个已存在的存储概要.
alter outline demo_1 rebuild;
注意:必须拥有权限alter any outline才可以执行这个命令.
如果登录回到原来的schema,清空缓存(flush shared pool),并且启用存储概要,我们将会发现原来的查询语句现在如愿以偿的使用上了索引T1_I1.
3 1 INDEX(T1 T1_I1)
这样为什么可行?因为存储概要并不属于任何一个schema. 当我们在另外一个schema中重编译这个称为demo_1的存储概要的时候,名称T1应用到了一个本地的包含提示的视图上了,因此Oracle将这个提示包装进了真实的执行计划中,从而也进入了这个存储概要.通过查看视图user_outline_hints,将会发现关键的那一行已经变成了
3 1 INDEX(T1 T1_I1)
很不幸,我们还将注意到它现在包含3行如下形式的提示:
2 1 NOREWRITE 1 2 NOREWRITE 1 1 NOREWRITE
而原来我们只有两行:
2 1 NOREWRITE 1 1 NOREWRITE
我们引入了一个新的提示,也就是”Stage 1,Node 2″.我不敢说我确切的知道这是什么意思,但是它一定与这样一个事实有关,为了在另外一个Schema解析优化这个查询,Oracle执行了一个额外的步骤来将视图引用转换成基础表的引用.
虽然目前这不会导致存储概要无法正确使用(或者如同它在这个简单的例子中这样),谁又能说Oracle在将来的版本又会有多挑剔呢.
旧方法(2)
因为视图引入了一个可能在将来版本变成错误的异常,我们不得不更加挑剔. 让我们试试下面的这种方法:
Create a new schema. Create table T1 in that schema. Create ONLY the index T1_I1. Rebuild the outline in that schema
如果比较存储概要重建前后user_outline_hints的详细内容(必须重新登录到原来的Schema来做这件事),我们将发现除了我们想要改变的那一行,它们是完全一样的.重新登录回原来的Schema,通过清空共享池以及打开存储概要做一个常规检查,我们将会发现修改后的存储概要已经被使用了.
然而,还有一个潜在的威胁,不过这一次更加隐蔽.再回去看图-2中出现在Oracle 9中的新字段的定义-你认为字段user_table_name中保存的值将会是什么啊?它应该是有限制的表名称,例如:
{User_name}.{table_name}
在我们的例子中,这将告诉Oracle表T1实际上是一个属于新的Schema的表,而不是原来的Schema下面的表.即使Oracle确实在使用这个存储概要,这个表里的信息也充分说明Oracle是在错误的对象上面应用这个存储概要.
另外,它现在现在有效,但是为什么有这个信息在这儿呢-可能是为了将来的版本增强做准备呢.
可靠的赌注
看来,要生成存储概要,而又不面临将来的风险就只有一种方法了,那就是尽可能的真实.
在这个示例中,你需要删除主键索引,生成执行计划,然后替换掉主键.
当然,你可能不想在生产环境做这件事,即使你在生产环境做了,存储概要也有可能选择走全表扫描(而不是走你想要的那个索引).
底线是你必须至少在另一个数据库中有一个这个Schema的空闲拷贝,接着需要非常小心的操作这个拷贝以得到需要的存储概要.一旦得到这个存储概要,你就可以从一个数据库导出它并将其导入另外一个数据库.
例如:在这个空闲的数据库上,删除主键以避免PK唯一扫描就是可行的.如果Oracle并没有自动的采用另外一个索引,你可以对系统说各种谎言,诸如:
- 将optimizer_mode改成first_rows_1
- 构造数据使得列N1上的数据是唯一的.(不过,不要将其改成唯一索引,这样生成出来的存储概要将是unique scan而不是range scan了).
- 使用dbms_stats来使这个索引获得一个难以置信的clustering_factor.
- 调整参数optimiser_index_caching来告诉系统,这个索引已经完全被缓存.
- 调整optimiser_index_cost_adj来告诉系统,多块读要比单块读要慢100倍.
- 使用dbms_stats修改aux_stats$表来达到上一条同样的宣称效果,并且添加这样一个事实,一次多块读的典型大小为2个块.
- 重建这个索引以包含where从句中的所有字段.
给定存储概要表中的内容,假使表的所有者不变,对象类型不变以及不改变索引的唯一度,几乎任何事情都可以做. 如果你可以构造一个数据集与环境来生成一份与生产系统没有内部不一致的存储概要,那么你就可以以任何方式来欺骗系统.
结论
相对于Oracle 8来讲,在Oracle 9中进入存储概要的信息变更更加精细了.之前可以非常容易也很明显无风险的”调整”存储概要的方法,现在还仍然可以工作,但是Oracle 9中收集的巨量的附加信息表明,之前的那种方法现在可能会给将来留下隐患.
虽然Oracle 9中引入了一个编辑存储概要的包,但它当前还只是局限在交换表的连接顺序.除了使用第二套系统来调整索引(通过改变环境参数以及人造的统计信息)外, 看似不存在安全的干预存储概要的方法.
参考文献
Oracle 9i Release 2: Database Performance Tuning Guide and Reference - Chapter 7.
Oracle 9I Release 2: Supplied PL/SQL Packages and Types Reference - Chapters 41 - 42


