<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	>

<channel>
	<title>Alibaba DBA Team</title>
	<atom:link href="http://www.alidba.net/index.php/feed" rel="self" type="application/rss+xml" />
	<link>http://www.alidba.net</link>
	<description>这里记录着阿里巴巴数据库团队成员的点点滴滴</description>
	<pubDate>Thu, 11 Mar 2010 15:55:43 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.7</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>使用数据库存图片</title>
		<link>http://www.alidba.net/index.php/archives/400</link>
		<comments>http://www.alidba.net/index.php/archives/400#comments</comments>
		<pubDate>Thu, 11 Mar 2010 15:55:43 +0000</pubDate>
		<dc:creator>八神</dc:creator>
		
		<category><![CDATA[大话技术]]></category>

		<category><![CDATA[图片]]></category>

		<guid isPermaLink="false">http://www.alidba.net/?p=400</guid>
		<description><![CDATA[图片是网站上很重要的资源，用户发布的产品图片，用户的logo，AV男，兽兽女，犀利哥等等等等，一个稍具规模的网站，图片的数量可能是千万级，这种资源的特点就是文件小，数量大，每个文件在几字节到几K不等，所以针对图片的访问，基本是非常离散的IO，考验的是系统的磁盘并发和CPU处理能力
一般网站，图片都是存放专门的图片服务器，可集中也可以分布式，比如有条件的可以购买昂贵的NAS存储，由主机拖着，以NFS或者HTTP的方式，供前面的应用访问，或者使用多台廉价PC，图片分布打散到每个机器，前台应用解决图片存取和访问策略，以便充分利用所有资源，在应用与图片服务器中间还可能加一层cache，来缓冲前台的访问压力
上面所说的方法，弊端是有很多的
1.文件系统一定是要使用的，为了管理数千万的图片，必须进行目录分层，因为一个目录下不可能存放太多的文件，前期的目录规划要考虑后期的扩展，还有图片分布均匀，这样做下来，往往目录的深度会达到5层甚至7层
2.数据迁移，备份怎么做？高级的NAS存储，有自己的卷复制，但这个粒度太粗，如果要细分到底层或中间层，会有点力不从心，对于这种大数据量的小文件拷贝，PC也是非常吃力
3.每一个图片的访问，基本会做5-7次的目录跳转，转换到磁盘，也可能有10次物理IO了，在并发量上来的时候，磁盘会是个瓶颈，当然，分布式是个方向
这里想到了另外一种方法，利用数据库来存放图片，存储的方式就是现在最流行的key-value
create table mypic
(
key number not null,
pic blob
);
create index ind_picid on mypic(key);
key是图片名称，事先最好统一规划一下，使用number数字来命名，并针对key建立索引
VALUE就是图片内容，用blob来保存
访问一个图片的方式就是：
获取图片名称  数据key
走key上的索引，定位到记录表记录
根据表记录，定位到图片的blob，并读取
这个方法的优点：
1.单个图片的访问路径缩短，数字索引比较小，分区做的好的话可能小到两层，从索引到表，到blob段，大概是4-5跳
并且KEY是区分度非常高的数据类型，在索引每层的横向检索中，不会超过1个数据块，而文件目录结构中，子目录繁多，
要遍历这层的inode后，才知道具体跳转的下层位置
2.备份方式比较灵活，可以基于整个数据库，或者单独的表，数据的迁移，删除，都是基于表级别的，比较直观，方便
3.可以在数据库层面，考虑图片的水平拆分，垂直拆分，分表，分库，都不错
4.数据库的复制技术可以派上用场，读写分离
这里数据库完全是当成一个KEY-VALUE的存储在用，我们可以考虑将一些廉价的PC堆在一起，做成一个picdb的群集，
大致画了一个草图，当然在WEB与picdb间应该还有层cache的,这个方法是YY出来的，可能很多地方不成熟，但SY强身，YY强国，没有想法，哪来的动力？欢迎各位专业人士拍砖

]]></description>
			<content:encoded><![CDATA[<p>图片是网站上很重要的资源，用户发布的产品图片，用户的logo，AV男，兽兽女，犀利哥等等等等，一个稍具规模的网站，图片的数量可能是千万级，这种资源的特点就是文件小，数量大，每个文件在几字节到几K不等，所以针对图片的访问，基本是非常离散的IO，考验的是系统的磁盘并发和CPU处理能力</p>
<p>一般网站，图片都是存放专门的图片服务器，可集中也可以分布式，比如有条件的可以购买昂贵的NAS存储，由主机拖着，以NFS或者HTTP的方式，供前面的应用访问，或者使用多台廉价PC，图片分布打散到每个机器，前台应用解决图片存取和访问策略，以便充分利用所有资源，在应用与图片服务器中间还可能加一层cache，来缓冲前台的访问压力</p>
<p>上面所说的方法，弊端是有很多的<br />
1.文件系统一定是要使用的，为了管理数千万的图片，必须进行目录分层，因为一个目录下不可能存放太多的文件，前期的目录规划要考虑后期的扩展，还有图片分布均匀，这样做下来，往往目录的深度会达到5层甚至7层<br />
2.数据迁移，备份怎么做？高级的NAS存储，有自己的卷复制，但这个粒度太粗，如果要细分到底层或中间层，会有点力不从心，对于这种大数据量的小文件拷贝，PC也是非常吃力<br />
3.每一个图片的访问，基本会做5-7次的目录跳转，转换到磁盘，也可能有10次物理IO了，在并发量上来的时候，磁盘会是个瓶颈，当然，分布式是个方向</p>
<p>这里想到了另外一种方法，利用数据库来存放图片，存储的方式就是现在最流行的key-value</p>
<p>create table mypic<br />
(<br />
key number not null,<br />
pic blob<br />
);</p>
<p>create index ind_picid on mypic(key);</p>
<p>key是图片名称，事先最好统一规划一下，使用number数字来命名，并针对key建立索引<br />
VALUE就是图片内容，用blob来保存<br />
访问一个图片的方式就是：<br />
获取图片名称  数据key<br />
走key上的索引，定位到记录表记录<br />
根据表记录，定位到图片的blob，并读取</p>
<p>这个方法的优点：<br />
1.单个图片的访问路径缩短，数字索引比较小，分区做的好的话可能小到两层，从索引到表，到blob段，大概是4-5跳<br />
并且KEY是区分度非常高的数据类型，在索引每层的横向检索中，不会超过1个数据块，而文件目录结构中，子目录繁多，<br />
要遍历这层的inode后，才知道具体跳转的下层位置<br />
2.备份方式比较灵活，可以基于整个数据库，或者单独的表，数据的迁移，删除，都是基于表级别的，比较直观，方便<br />
3.可以在数据库层面，考虑图片的水平拆分，垂直拆分，分表，分库，都不错<br />
4.数据库的复制技术可以派上用场，读写分离</p>
<p>这里数据库完全是当成一个KEY-VALUE的存储在用，我们可以考虑将一些廉价的PC堆在一起，做成一个picdb的群集，<br />
大致画了一个草图，当然在WEB与picdb间应该还有层cache的,这个方法是YY出来的，可能很多地方不成熟，但SY强身，YY强国，没有想法，哪来的动力？欢迎各位专业人士拍砖</p>
<p><img class="alignnone size-full wp-image-404" src="http://www.alidba.net/wp-content/uploads/2010/03/ppp1.jpg" alt="ppp1" width="748" height="653" /></p>
]]></content:encoded>
			<wfw:commentRss>http://www.alidba.net/index.php/archives/400/feed</wfw:commentRss>
		</item>
		<item>
		<title>不平衡的索引?</title>
		<link>http://www.alidba.net/index.php/archives/394</link>
		<comments>http://www.alidba.net/index.php/archives/394#comments</comments>
		<pubDate>Tue, 02 Mar 2010 06:49:12 +0000</pubDate>
		<dc:creator>jametong</dc:creator>
		
		<category><![CDATA[大话技术]]></category>

		<category><![CDATA[b-tree]]></category>

		<category><![CDATA[balanced tree index]]></category>

		<category><![CDATA[index block split]]></category>

		<category><![CDATA[index split]]></category>

		<category><![CDATA[jonathan lewis]]></category>

		<category><![CDATA[treedump]]></category>

		<guid isPermaLink="false">http://www.alidba.net/?p=394</guid>
		<description><![CDATA[本文翻译自Jonathan Lewis早年写在dbazine上的文章unbalanced indexes? 本文的word版本可以到此处下载
原文参见: 不平衡的索引?
不平衡的索引?
by Jonathan Lewis
网络上有多篇介绍Oracle索引实现机制的文章,都提及需要经常重建索引.在这些文章中的某处,总是会出现这样一段简短的描述,索引会如何变的不平衡,以及可能导致的后果.很不幸,它们好像忽视了这样一个事实,Oracle使用的B-tree机制是一种&#8221;平衡B-tree&#8221;索引,也就是说,索引无法变得不平衡.
&#8220;平衡&#8221;到底意味着什么?
既然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: [...]]]></description>
			<content:encoded><![CDATA[<blockquote><p>本文翻译自<a href="http://jonathanlewis.wordpress.com/">Jonathan Lewis</a>早年写在dbazine上的文章<a href="http://www.dbazine.com/oracle/or-articles/jlewis13">unbalanced indexes?</a> 本文的word版本可以到<a href="http://www.jlcomp.demon.co.uk/13_unbalanced_indexes.doc">此处下载</a></p>
<p>原文参见: <a href="http://www.dbthink.com/?p=193">不平衡的索引?</a></p></blockquote>
<p><strong>不平衡的索引?</strong><br />
<em>by Jonathan Lewis</em></p>
<p>网络上有多篇介绍Oracle索引实现机制的文章,都提及需要经常重建索引.在这些文章中的某处,总是会出现这样一段简短的描述,索引会如何变的不平衡,以及可能导致的后果.很不幸,它们好像忽视了这样一个事实,Oracle使用的B-tree机制是一种&#8221;平衡B-tree&#8221;索引,也就是说,索引无法变得不平衡.</p>
<p><strong>&#8220;平衡&#8221;到底意味着什么?</strong></p>
<p>既然Oracle的索引使用的是平衡B-tree,为什么还有如此多的人相信他们的索引会变得不平衡呢?<br />
另外,平衡B-tree到底又是什么呢?<br />
第二个问题的答案可能能够帮助我们得到第一个问题的答案.<br />
从技术角度讲,平衡B-tree的显要特性是,在任意时间点,任何叶子节点(leaf block)到根节点(root block)的距离都相等,平衡是指从顶部到底部的平衡.<br />
就Oracle来说,执行一个treedump命令就可以很容易发现这一点,如图-1所示:</p>
<pre>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';</pre>
<p><em>图-1: 一次索引树转储涉及到的步骤</em></p>
<p>首先,需要找到你想要转储(dump)索引或者索引分区(index partition)的object_id;接着,将object_id作为level的参数来调用treedump事件. 如果检查这个索引树(tree dump)转储生成的跟踪文件,你将发现类似于图-2中所示的结果.</p>
<pre>branch: 0x14001aa 20971946 (.. level 2)
  branch: 0x14003ef 20972527 (.. level 1)
    leaf: 0x14001ab 20971947 (..)
    leaf: 0x14001ac 20971948 (..)</pre>
<p><em>图-2: 从索引树转储结果中提取出的一段内容</em></p>
<p>这个跟踪文件以递归降序的方式展示索引的分支块(branch block,根块只是分支块的一个特例)与叶子块.注意,转储内容的第一个块(根块)记录了一个高度(level),并且它下面的每一个分支块也都记录了一个高度,但是叶子块没有记录高度.<br />
根块的高度就是索引的blevel(执行analyze index命令之后会记录在视图dba_indexes中).索引的高度(height,执行命令validate index后会记录在视图index_stats中)就是blevel + 1.<br />
每一个叶子块到根块的距离就正好有这么多步.索引总是平衡的.<br />
那么为什么有那么多人相信Oracle会允许索引变得不平衡呢?</p>
<p><strong>我应负的责任</strong></p>
<p>此时,我必须承认我有罪,仅仅在一年前(2002年5月),我也重复了一个关于块分裂(index block split)的众所周知但是却完全错误的描述.虽然我知悉(在我的书(2000年12月,Practical Oracle 8i)中对此做了说明) treedump的细节,但我还是这样做了.<br />
我猜想,这种错误的观念最初产生于Oracle5(很多年以前)手册的说明,其大意是,因为&#8221;没有一个叶子块到根块的距离比任何其他叶子块到根块的距离远.&#8221;, 所以Oracle索引是平衡的.将这个表述与一个过于简化的块分裂的图结合到一起,瞧,一个几乎牢不可破的神话就诞生了.<br />
图-3和图-4描绘了一个非常常见,但是完全错误的关于叶子块如何分裂的概念.<br />
<img src="http://lh4.ggpht.com/_Tj8VUwhrzbg/S4yrRrGhyaI/AAAAAAAAAII/Z2Q3VZtInnY/jlewis13-1.gif"/><br />
<em>图-3: 叶子块将要分裂前的索引结构</em><br />
<img src="http://lh4.ggpht.com/_Tj8VUwhrzbg/S4yrRvhpPaI/AAAAAAAAAIM/4ZdSMJN-_pQ/jlewis13-2.gif"/><br />
<em>图-4 这不是叶子块分裂发生的方式</em><br />
有这样一个流传广泛的说法,叶子块分裂到两个全新的块,这两个块分享它的所有数据;接着, Oracle在原来的叶子块的位置插入一个新的分支块来持有指向这两个块的指针.因此,在这样一个错误的视角下,这个索引的右边就会比左边更深.(经常会有人说,创建在基于序列的字段上的索引会带来最大的问题,因为,从这个理论来推导,最右边的叶子块到根块的距离会越来越远,没分裂一次,就会降低一层.)<br />
事实上,这个工作Oracle做得更加精致前瞻并有效得多. 图-5所示是一个复杂的叶子块分裂的结果.<br />
<img src="http://lh4.ggpht.com/_Tj8VUwhrzbg/S4yrRuRproI/AAAAAAAAAIQ/nFcWkGa-qJg/jlewis13-3.gif"/><br />
<em>图-5: 一次递归分裂后的索引结构图</em><br />
由于这个叶子块分裂成了两个块,Oracle会尝试往当前指向这个叶子块的分支块中插入一个额外的指针.<br />
但是,如果这个分支块也满了,Oracle会继续将这个分支块也分裂成两个块,并且在两个分支块之间分配现存块里的指向叶子块的指针,并且(递归地) 在这个分支块的上一级的分支块中插入一个指向这个新的分支块的新的分支指针.<br />
如果在这个过程中,Oracle抵达了根块,然而这个根块也满了,那么根块也必须分裂.在这种情况下,Oracle将创建一个新的根块来持有这两个分支指针.(事实上,Oracle处理根块分裂的方式与处理普通的分支块的分裂有一点细微的差异,以确保无论根块上发生了多少次的分裂,总是可以在物理段的同一个位置找到根块.)<br />
注意,这个递归的分裂操作这样沿着索引树不断攀升意味着,无论何时,索引总能保持平衡.</p>
<p><strong>为什么这个神话如此牢固?</strong></p>
<p>为什么这个关于不平衡的索引的神话能够长盛不衰,是否存在一些原因呢?我这答案是确实存在一些原因.<br />
要切记,当我们讨论B-tree的时候,单词&#8221;balanced&#8221;的定义有非常严格的含义.然而,这个单词还有一种完全不同的解释.<br />
例如,你将如何描述图-6中的这个索引,其中根块指向六个叶子块,但是其中一个叶子块是空的,有三个块几乎是空的,还有两个块塞的很满.(注意,从根块到叶子块的这些额外的说明是为了强调索引填充分布的多么不均匀;实际上,根块到每个叶子块都只有一个指针.)<br />
<img src="http://lh4.ggpht.com/_Tj8VUwhrzbg/S4yrRqip67I/AAAAAAAAAIU/Ihz54GfR97Q/jlewis13-4.gif"/><br />
<em>图-6 &#8220;不平衡&#8221;的一种非技术理解</em></p>
<p>看到这种图案的索引,一个&#8221;人&#8221;的真实反应都会人为它&#8221;不平衡&#8221;.很明显,索引的右手边要比左手边&#8221;重&#8221;.很不幸,当技术表达意味着完全不同的东西的时候,这种非正式的人为表达应该更加恰当.<br />
或许正是这种技术表达与非正式的人为表达之间的冲突导致了这种混淆.<br />
在这种非正式的意义上,在基于序列值的字段上索引很容易就会变得&#8221;不平衡&#8221;,特别是它们被用来表征/处理先进先出(FIFO)队列机制的时候.然而,即使它们(在非正式语境中)是不平衡的,它们(在技术上)仍然是平衡的B-tree.<br />
(推动使用类似于&#8221;扭曲&#8221;或者&#8221;分布不均匀&#8221;作为术语来描述这种类型的索引,或许会是个好主意.)<br />
有时,仅仅几篇草率地使用术语的文章或报告就可能构建一个神话,在这个例子中,就是一个导致众多DBA浪费无数小时的时间去做不必要的索引重建的神话.<br />
记住,你下一次认为Oracle表现愚蠢或者低效的时候,很可能问题是出在一个古老的误解上面,而不是Oracle软件本身的问题.</p>
<p><strong>告警提醒</strong></p>
<p>如果你还想进一步地研究,treedump选项还有一系列的问题需要注意.对于大部分的Oracle版本,它看似对索引段中的每个块都生成一行输出,这样可能会非常昂贵并且速度缓慢,因为它需要按顺序访问索引中的每一个数据块.然而,在Oracle 9.0中,跟踪文件看似会对每一个块做一个整块转储,这样会使得转储文件非常巨大,转储速度也会非常缓慢.<br />
第二个问题是所有版本都一致的.如果这个索引是在定义主键约束或者唯一键约束时生成的,Oracle就会设置ind$表的flags字段的第13位,而这将导致treedump程序崩溃并报出错误&#8221;invalid value.&#8221;分区索引的分区段不会产生这个问题,但是对于所有其他类型的主键索引与唯一索引(包含非分区索引组织表,IOT),这都很恼人.先创建索引,再基于这个索引创建约束通常是个好主意, 这样处理可以避免除索引组织表外的其他所有条件下的问题. 在紧急情况下,你可以修改ind$表来清除这一位,但是很明显,需要先取得Oracle支持的认可.</p>
<p><strong>结论</strong></p>
<p>当谈到平衡B-tree索引的时候,术语&#8221;平衡&#8221;指的是从顶部到底部,而不是从左到右.<br />
Oracle确实实现了一个版本的&#8221;平衡B-tree索引&#8221;,因此在任何时候,索引中的所有叶子块到根块的距离都是完全相同的,如果最近对这个索引作过分析的话,可以从视图user_indexes的字段blevel找到它,如果刚刚对这个索引执行过validate index的话,可以从视图index_stats的height字段(等价于blevel+1)得到.<br />
当听到你应该经常重建索引,因为&#8221;这些索引已经变得不平衡&#8221;时,要抵制这种理由.因为它不是一个靠得住的理由.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.alidba.net/index.php/archives/394/feed</wfw:commentRss>
		</item>
		<item>
		<title>为什么Oracle不使用我的索引?!</title>
		<link>http://www.alidba.net/index.php/archives/392</link>
		<comments>http://www.alidba.net/index.php/archives/392#comments</comments>
		<pubDate>Tue, 02 Mar 2010 06:48:00 +0000</pubDate>
		<dc:creator>jametong</dc:creator>
		
		<category><![CDATA[大话技术]]></category>

		<category><![CDATA[cbo]]></category>

		<category><![CDATA[cost based optimizer]]></category>

		<category><![CDATA[index]]></category>

		<category><![CDATA[jonathan lewis]]></category>

		<category><![CDATA[system statistics]]></category>

		<guid isPermaLink="false">http://www.alidba.net/?p=392</guid>
		<description><![CDATA[本文翻译自Jonathan Lewis发表在DBAZine上的文章:Why Isn&#8217;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 &#60;= 3000;
create table t2 as
select
    mod(rownum,200) n1,
    mod(rownum,200) n2,
    rpad('x',215) v1
    from all_objects
    where rownum [...]]]></description>
			<content:encoded><![CDATA[<blockquote><p>本文翻译自Jonathan Lewis发表在DBAZine上的文章:<a href="http://www.dbazine.com/oracle/or-articles/jlewis12">Why Isn&#8217;t Oracle Using My Index?!</a>,可以从<a href="http://www.jlcomp.demon.co.uk/12_using_index.doc">此处下载</a>本文的Word版本. </p>
<p>原文参见: <a href="http://www.dbthink.com/?p=177">为什么Oracle不使用我的索引?!</a>
</p></blockquote>
<p><strong></strong><strong>为什么Oracle不使用我的索引?!</strong><br />
<em>by <a href="http://jonathanlewis.wordpress.com/">Jonathan Lewis</a></em><br />
标题的这个问题可能是在Metalink论坛与Usenet新闻组出现的最频繁的问题了.这篇文章使用一个测试用例(可以在你自己的系统来重现的)来演示基于成本的优化器的基本工作原理.在看完这篇文章之后,当再次遇到这个令人讨厌的问题时,你应该就可以自信的解答了.<br />
由于在安装Oracle的时候存在大量的选项,因此当某人执行一条你口授的脚本时,通常很难精确的预测即将出现什么结果. 当时我想要尝试一下,希望你的数据库选择了一个相对普通的安装选项,并且最常用的关键的参数是取得默认值. 这个例子是在Oracle 8.1.7下创建并测试的,参数db_block_size被设置成最常用的值(8k),参数db_file_multiblock_read_count也设置了一个很常用的值(8).在Oracle 9.2下跑图-1中的这个脚本(创建了一组表,在表上添加索引并分些表与索引),结果可能出现部分差异.</p>
<pre>
create table t1 as
select 	trunc((rownum-1)/15)	n1,  trunc((rownum-1)/15)	n2, rpad('x', 215)		v1
from all_objects
where rownum &lt;= 3000;
create table t2 as
select
    mod(rownum,200) n1,
    mod(rownum,200) n2,
    rpad('x',215) v1
    from all_objects
    where rownum &lt;= 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;
</pre>
<p><em>图 1: 测试的数据集</em><br />
在你准备好数据之后,你可能认为这两组数据是一样的,尤其是,在两个数据集中字段N1有的值范围相同(从0-199),并且每个值都出现了15次.你可能会这样检查数据:</p>
<pre>
select n1, count(*)
from t1
group by n1;
</pre>
<p>查询表T2也会证实你的上述观点.<br />
如果你接着执行下面的查询语句:</p>
<pre>
select * from t1 where n1 = 45;
select * from t2 where n1 = 45;
</pre>
<p>你将发现每个查询语句都返回了15条记录.然而如果你执行</p>
<pre>
set autotrace traceonly explain
</pre>
<p>语句,你将发现这两个查询语句拥有两种不同的执行路径.针对表T1的查询使用了索引,而针对表T2的查询则做了一个全表扫描.<br />
因此,在拥有完全相同的数据的情况下,同一个查询语句戏剧性地出现了两个不同的执行路径.</p>
<p><strong>索引到底怎么了?</strong></p>
<p>注意: 如果你曾经听到如下的关于使用索引的&#8221;魔法&#8221;准则,例如,&#8221;在数据少于23%/10%/2%(随机取一个数字)的时候,Oracle将使用索引,&#8221;那么,此时你将怀疑他们的准确性.例如,在这个例子中,Oracle对于一个在3000记录中取15条的查询使用了全表扫描,仅仅0.5%的数据.<br />
要调查诸如此例的问题,只有一个手段(我经常首先尝试使用的):添加一些索引以使得Oracle选择我们认为它应该选择的执行路径,并检查这能否给我们部分提示.<br />
在这个例子中,添加一个简单的提示:<br />
/*+ index(t2, t2_i1) */<br />
就足够让Oracle从选择全表扫描切换到选择使用索引访问.图-2展示了这三种路径的成本(简化为C=nnn).</p>
<pre>
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)
</pre>
<p><em>图 2: 不同的查询以及它们的成本</em><br />
因此,为什么在T2相关的查询中Oracle没有选择索引作为默认的执行路径?很简单,正如执行计划所展示,选择执行全表扫描的成本要低于使用索引的成本.</p>
<p><strong>为什么使用全表扫描成本更低?</strong></p>
<p>当然,这仅仅是在重复问题.为什么全表扫描的成本会低于使用索引的成本呢?<br />
通过深入调查这个问题,你将揭开了基于成本的优化器的关键机制(也是致命的错误假设).<br />
让我们通过运行下面这个查询来开始我们的考查:</p>
<pre>
select
     table_name,
     blevel,
	   avg_data_blocks_per_key,
	   avg_leaf_blocks_per_key,
	   clustering_factor
from	user_indexes;
</pre>
<p>下表是对应的输出结果:</p>
<table>
<tr>
<th>&nbsp;</th>
<th>T1</th>
<th>T2</th>
</tr>
<tr>
</tr>
<tr>
<td>Blevel</td>
<td>1</td>
<td>	1</td>
</tr>
<tr>
<td>Data block / key</td>
<td>1</td>
<td>	1</td>
</tr>
<tr>
<td>Leaf block / key</td>
<td>1</td>
<td>	15</td>
</tr>
<tr>
<td>Clustering factor</td>
<td>96</td>
<td>	3000</td>
</tr>
</table>
<p>请特别关注&#8221;data block per key&#8221;的值. 如果你执行一个完全基于这个索引的键值的等值校验的查询语句,这个值就是Oracle认为必须访问的不同数据块的数量.<br />
因此,我们的查询语句的成本是从哪儿来得呢?就Oracle而言,如果输入的键值为45,当从表T1获取数据时,我们就可以访问一个索引叶子块以及一个表块(两个块),因此成本为2.<br />
如果对表T2执行同样的查询,我们就必须访问一个索引叶子块以及15个表块(总共16个块),因此成本为16.<br />
很明显,根据这种观点,表T1的索引比表T2的索引更加理想.不过,这就带来了两个未解决的问题:<br />
全表扫描的成本来自何处,为什么两张表之间的avg_data_blocks_per_key的数值差别如此大?<br />
第二个问题的答案很简单.回头再看看表T1的定义,它使用trunc()函数来生成N1的值,使用&#8221;rownum -1&#8243;除以15并截成整数.</p>
<pre>
     Trunc(675/15) = 45
     Trunc(676/15) = 45
           …
     Trunc(689/15) = 45
</pre>
<p>所有值为45的记录实际上都是一条接着一条连续(可能所有的都能够保存到一个数据块中)的出现的.<br />
表T2使用mod()函数来生成N1的值,使用对rownum取200的模的方式.</p>
<pre>
      mod(45,200) = 45
      mod(245,200) = 45
            …
      mod(2845,200) = 45
</pre>
<p>值为45的记录要隔200才会在表中出现一次(可能导致每一个相关数据块中都不会超过1条记录).<br />
通过对表的分析,Oracle可以得到我们表中的数据分布的完美的描述.从而优化器才确切的明白,对于我们的查询,Oracle将需要访问多少个数据块,在这个简单的例子中,这个查询的成本就是需要访问的数据块的数量.</p>
<p><strong>但是为什么是全表扫描呢?</strong></p>
<p>我们看到,对于同样的执行路径,对表T2进行索引访问的成本要远远高于对表T1的索引访问成本,但是为什么会选择使用全表扫描呢?<br />
这个问题将让我们发现Oracle做的两个过于简单甚至不恰当的假设.<br />
第一个假设是,每个块访问都视为一次物理磁盘读,第二个假设是,多块读的速度与单块读一样.<br />
因此,这些假设将对我们的这个实验产生什么影响?<br />
如果使用下面的查询语句查询user_tables表:</p>
<pre>
select
      table_name,
      blocks
from user_tables;
</pre>
<p>你将发现这两张表每个都是占用96个数据块.<br />
在文章开头,我说过这个测试用例运行在一个db_file_multiblock_read_count的值为8的版本为8的Oracle系统系统上.<br />
粗略地讲,Oracle认为它可以通过12(96/8)次磁盘读请求来读出所有的96个数据块.<br />
由于通过索引访问表需要16个块(等于物理读)请求, 从Oracle的可悲的受骗的视角看的话,选择全表扫描显得更清晰也更快捷.<br />
瞧!如果你要访问的数据适当地散布在表上,即使只有很小比例的数据也会选择使用全表扫描,在数据块非常多(也就是表很大)而返回的记录数很少的时候,这个问题还会被放大.</p>
<p><strong>校正</strong></p>
<p>实际上,可能你已经发现,我计算出来的扫描读次数为12,而执行计划中报告的成本是15.一种轻微的简化版本认为表扫描(或者索引快速全扫描)的成本为<br />
<em>     &#8216;number of blocks&#8217; /<br />
     db_file_multiblock_read_count.</em><br />
Oracle使用一种&#8221;调整后&#8221;的多块读的值来做这种计算(然而,在扫描开始之后,它仍然尽力使用这个真实的请求值来扫描).<br />
为了方面查询,下表对比了几个真实值与调整后的值:</p>
<table>
<tr>
<th>Actual</th>
<th>Adjusted</th>
</tr>
<tr>
<td>4</td>
<td>3.175</td>
</tr>
<tr>
<td>8</td>
<td>6.589</td>
</tr>
<tr>
<td>16</td>
<td>10.398</td>
</tr>
<tr>
<td>32</td>
<td>16.409</td>
</tr>
<tr>
<td>64</td>
<td>25.895</td>
</tr>
<tr>
<td>128</td>
<td>40.865</td>
</tr>
</table>
<p>你还将发现,当你为这个参数提供一个不切实际的很大的值后,Oracle可以为你提供保护来避免发生错误.<br />
顺便提一下,Oracle 9中有一点小小的改变,表扫描的成本还会做进一步的调整(对相除之后的结果加1),这意味着Oracle 9中的表扫描的成本会比Oracle 8中大一点点,从而索引会变得更可能被使用一点点.</p>
<p><strong>修正</strong></p>
<p>我们已经看到,优化器有两个内置的假设,而这两个假设又不是很合理.</p>
<ul>
<li>一次单块读的成本与一次多块读的成本一样-(实际上不大可能,特别是运行在没有顺序的文件系统上的时候)</li>
<li>一次块访问就是一次物理磁盘读-(那么Buffer Cache是干什么吃的?)</li>
</ul>
<p>从Oracle 8.1刚发布开始,就有多个参数可供我们以一种相当切合实际的方式来修正这些假设.<br />
Tim Gorman的文章为这些参数提供了一个切实的描述,下面是简单的描述:</p>
<ul>
<li>Optimizer_index_cost_adj的值范围为1到10000,默认为100.实际上,这个参数描述的是,相对于一次多块读来讲,一次单块读有多么便宜.如果它的值为30,也就是高速Oracle一次单块读的成本是一次多块读的成本的30%. 从而Oracle就会因此这个参数的值很小而更多的倾向于选择使用索引访问.</li>
<li>Optimizer_index_caching的值访问为0到100,默认为0.这个参数告诉Oracle,假定索引块将在Buffer Cache中存在百分比.在这个例子中,将这个值设置为接近100的值将助长使用索引而不是表扫描.</li>
</ul>
<p>关于这些参数的真正美好的事情是,可以将他们设置成&#8221;符合实际&#8221;的值.<br />
将optimizer_index_caching设置成&#8221;buffer cache hit ratio&#8221;范围内的一个值(你需要自己决定具体是按照default pool,keep pool还是这两个的某种组合来得到这个数值).<br />
Optimizer_index_cost_adj的值的设置要更加复杂一点.检查v$system_event视图中等待事件&#8221;db file scattered read&#8221;(多块读取)与&#8221;db file sequential read&#8221;(单块读取)的有代表性的等待时间.用后者(单块读的等待时间)除以前者(多块读的等待时间)并乘以100.</p>
<p><strong>改进</strong></p>
<p>不要忘了,这两个参数可能需要一天(周)的不同时段进行调整以反映终端用户的工作负载.仅仅取得一组数字,就一直使用下去,是不可行的.<br />
很高兴,在Oracle 9中,情况得到了改善.你可以收集系统统计信息,通常就包含以下四个统计数据:</p>
<ul>
<li>单块读的平均读取时间</li>
<li>多块读的平均读取时间</li>
<li>实际发生的多块读的平均读取块数</li>
<li>CPU的理论可用速度</li>
</ul>
<p>要详细介绍这个特性足够配得上一整篇文章,但是这里我特别强调一点,前三个统计值使得Oracle可以明白对多块读的真实成本(相对单块读来讲).实际上,CPU速度使得Oracle可以得出不适宜的访问机制的CPU成本,比如,读取一个数据块中的每条记录以找出特定的数据值,以及与此相似的行为.<br />
当你将系统升级到Oracle 9时,你首先需要检查的事情就是是否正确使用系统统计信息.单单这个特性就可能大大降低你尝试&#8221;优化&#8221;的糟糕的SQL的时间.<br />
顺便提一下,尽管系统统计信息带来了惊人的效果,这两个优化器调整参数仍然有效,虽然使用它们的确切地公式在Oracle 8与Oracle 9之间发生了变化.</p>
<p><strong>主题的变种</strong></p>
<p>当然,我选择了一个非常特殊的例子,一个单列非唯一索引上的等值查询,并且表中没有空值,这种情况非常容易处理.(我甚至都没有提及索引的blevel与clustering_factor.)Oracle还有多个不同的方法来处理更加一般的例子.<br />
考虑如下这些我为了方便而忽视的情况:</p>
<ul>
<li>多列索引</li>
<li>使用多列索引中的部分列</li>
<li>范围扫描</li>
<li>唯一索引</li>
<li>由非唯一索引代表的唯一约束</li>
<li>索引跳跃扫描</li>
<li>只查询索引的语句</li>
<li>位图索引</li>
<li>空值的影响</li>
</ul>
<p>这个列表还可以不停地列下去.并没有一个简单的公式来告诉你Oracle是如何计算它的成本,只存在一个通用准则,通过它你可以了解这个方法的梗概,以及一组可以应用到不同情形下的不同计算公式.<br />
不管怎样,本文的目的是让你知道有这个通用准则,以及优化器策略中内嵌的两个基本假设.我希望,这篇文章可以帮助你更加深入的理解那些优化器做出来的众所周知的怪事.</p>
<p><strong>进一步阅读</strong></p>
<ul>
<li><em>Tim Gorman: <a href="www.evdbt.com">www.evdbt.com</a>. &#8220;The Search for Intelligent Life in the Cost Based Optimiser.&#8221;</em></li>
<li><em>Wolfgang Breitling: <a href="www.centrexcc.com">www.centrexcc.com</a>. &#8220;Looking under the hood of the CBO.&#8221;</em></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.alidba.net/index.php/archives/392/feed</wfw:commentRss>
		</item>
		<item>
		<title>闪存表空间 VS 数据库Flash Cache</title>
		<link>http://www.alidba.net/index.php/archives/390</link>
		<comments>http://www.alidba.net/index.php/archives/390#comments</comments>
		<pubDate>Tue, 23 Feb 2010 08:25:50 +0000</pubDate>
		<dc:creator>jametong</dc:creator>
		
		<category><![CDATA[大话技术]]></category>

		<category><![CDATA[Flash Cache]]></category>

		<category><![CDATA[flash tablespace]]></category>

		<category><![CDATA[free buffer waits]]></category>

		<category><![CDATA[mlc]]></category>

		<category><![CDATA[slc]]></category>

		<category><![CDATA[ssd]]></category>

		<guid isPermaLink="false">http://www.alidba.net/?p=390</guid>
		<description><![CDATA[本文翻译自Guy Harrison的blog: Flash tablespace vs. DB Flash Cache, 这是他写的关于Flash Cache系列文章的最后一篇,另外还会翻译两篇Kevin Closson写的关于Flash Cache的相关文章.
之前两篇关于Flash Cache的文章如下:

数据库Flash Cache(II)
使用Oracle 11GR2 数据库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 [...]]]></description>
			<content:encoded><![CDATA[<blockquote><p>本文翻译自<a href="http://guyharrison.squarespace.com/">Guy Harrison</a>的blog: <a href="http://guyharrison.squarespace.com/blog/2010/1/24/flash-tablespace-vs-db-flash-cache.html">Flash tablespace vs. DB Flash Cache</a>, 这是他写的关于Flash Cache系列文章的最后一篇,另外还会翻译两篇<a href="http://kevinclosson.wordpress.com/">Kevin Closson</a>写的关于Flash Cache的相关文章.<br />
之前两篇关于Flash Cache的文章如下:</p>
<ul>
<li><a href="http://www.dbthink.com/?p=135">数据库Flash Cache(II)</a></li>
<li><a href="http://www.dbthink.com/?p=100">使用Oracle 11GR2 数据库Flash Cache</a></li>
</ul>
<p>原文链接: <a href="http://www.dbthink.com/?p=169">闪存表空间 VS 数据库Flash Cache</a>
</p></blockquote>
<p><strong>闪存表空间 VS 数据库Flash Cache</strong></p>
<p>在这篇文章中,我将根据我最近针对使用SSD作为数据文件的存储以及使用Oracle 11GR2数据库Flash Cache所做的测试,给出一份两者的性能对比.<br />
有时,我的整个职业生涯看上去都是在等待旋转磁盘的终结.这项技术是如此古老,能力限制如此明确,如此机械.因此,SSD作为一种数据库存储介质越来越可行(Oracle 11GR2已经直接支持这一点),这个事实令人振奋.<br />
使用SSD作为数据库存储的一部分确实会产生很大的问题,但是,理解闪存SSD的性能特征却是非常重要的,它可以帮助我们确保不会不当地使用它.<br />
SSD有以下两个特征:</p>
<ul>
<li>基于闪存的SSD使用与常见的USB盘相似的闪存技术,这种USB盘已经在小容量移动数据存储领域替代软盘.闪存RAM比较便宜,提供不需要电池备份的持久存储,因此其耗电量也很低.</li>
<li>基于DDR RAM的SSD使用本质上与服务器核心内存差别不大的内存模块.这种RAM需要有持久存储(如磁盘或闪存RAM)和内部电池来支撑.在发生电力故障的时候,电池可以提供足够的电力来保证可以将RAM内存中的内容写到持久存储.</li>
</ul>
<p>DDR SSD非常昂贵(以及$$/GB这个级别),以致于目前无法作为专业的数据库设备使用.但是,基于闪存的SSD磁盘越来越称为机械磁盘的一个可行的替代选项.</p>
<p><strong>读,写以及擦写操作</strong></p>
<p>闪存盘存储是按照页(一般为4K)以及块(一般为256K)来组织的.对于读操作来讲,闪存盘可以从单个页(page)快速返回结果.往一个页中写数据要慢很多(可能要慢10倍).然而,只有在块中刚好有一个空闲的页的往页写才能达到这个速度.如果我们需要往整个块写数据,必须先清除块内的内容才可以.维基百科关于SSD的条目给出了下面这个关于查找/写以及擦写的时间:<br />
<img src="http://lh6.ggpht.com/_Tj8VUwhrzbg/S4N-f8mDHbI/AAAAAAAAAHQ/vLB3bMbJcq8/seekwriteerase.png"/><br />
当一个闪存SSD盘渐渐填满数据时,需要清除操作的块级别的写操作的比例逐渐增加,闪存SSD的写性能也相应下降.</p>
<p><strong>TRIM API函数</strong></p>
<p>高端的闪存SSD支持一种叫做TRIM的API,这个功能使得OS可以主动提前清除整个块,从而写操作可以在只有一个页级别的IO内完成.大部分高端的SSD盘还支持一种防磨损算法,这种算法可以在设备上移动热点页以避免块级别出现故障的风险.闪存盘在块变得不可靠之前只支持一定次数的擦写操作,加入磁盘可以自动将热点页在物理存储上移动时,这个缺陷就可以得到缓解.</p>
<p><strong>MLC vs SLC </strong></p>
<p>廉价的闪存盘一般都使用MLC(Multi-Level-Cell)技术,它可以实现在一个单元中存储两位的数据,而使用SLC时一个单元中只能保存一位数据.MLC的效果是以牺牲性能的代价来提高数据密度,特别是写性能.从数据丢失的角度讲,MLC也是更加不可靠的.如果你关心写性能,那么或许你应该避免使用基于MLC的SSD.<br />
通常,如果你想要一个高性能的闪存SSD的话(如果它不是高性能的,干嘛还要它呢?),你就应该选择基于SLC的闪存SSD,并且是支持TRIM API以及有着好的防磨损能力的SSD.在我的测试中,我使用一个Intel X-25 E 32GB的SSD盘.它大概需要600澳元(大概534美元).</p>
<p>读写速度差异的问题</p>
<p>假设大部分数据库都是读比写多,我们还需要担心闪存SSD在查找时间与写时间方面的差异吗?毫无疑问答案是YES.对于一个Oracle数据库来讲,当通过Buffer Cache处理事务活动时,一个设备的读能力与往这个设备的写能力之间有很大的不匹配会非常有害.<br />
这个问题与Buffer Cache中的数据的缓存有关.如果往Buffer Cache中放入数据块比从里面写出简单很多,那么Buffer Cache就很可能会被脏块填满,从而出现<em>free buffer waits</em>等待.下图展示了f<em>ree buffer waits</em>是如何出现的:<br />
<img src="http://lh3.ggpht.com/_Tj8VUwhrzbg/S4N9JxDcqjI/AAAAAAAAAGg/-7FCXPcn92Y/Free%20buffer%20waits.png"/><br />
如果使用的是廉价的闪存盘,那么写速度就会比读速度慢更多,最终free buffer waits等待将成为事务活动高峰时期的限制因素.</p>
<p><strong>Oracle数据库Flash Cache</strong></p>
<p>Oracle的数据库Flash Cache提供了另外一种利用闪存SSD的途径. 它不是将整个数据文件放到闪存上,而是将闪存作为二级缓存使用.Flash Cache可以非常大从而加快经常被访问的数据块的读速度.但是,如果闪存盘非常繁忙的话,Oracle就只是尽量少写缓存.这样,我们就可以得到闪存来优化读操作的好处,而不用承担多少写操作带来的损失.<br />
我在前一篇文章中总结了Flash Cache的处理算法,下面是我在那篇文章中使用的图表,它概括了当数据库使用Flash Cache时一个数据块的生命周期.<br />
<img src="http://lh6.ggpht.com/_Tj8VUwhrzbg/S3-DdmpEW7I/AAAAAAAAAEE/NLsiAqVjYRI/db%20flash%20cache%20lifecycle.PNG"/><br />
这个架构的关键点是,只有在DBWR没有超负荷时,它才会往Flash Cache中写入数据块.当DBWR逐渐变得繁忙时,往Flash Cache中的写操作将被忽略(这将会降低Flash Cache的效率),它可以防止Buffer Cache被脏块填满,从而导致free buffer waits等待事件的出现.</p>
<p><strong>闪存盘的读性能</strong></p>
<p>让我们来看在实际操作中它是如何表现的.下面来看当我们针对如下情况执行500,000次随机索引读取时的性能对比:</p>
<ul>
<li>1.	一个在机械磁盘上的表,不使用Flash Cache</li>
<li>2.	一个在机械磁盘上的表,使用Flash Cache</li>
<li>3.	一个直接存储在闪存盘上的表</li>
</ul>
<p>这个机械磁盘是一块希捷7200.7 80GB Barracuda磁盘,同时这个SSD盘是一块Intel X-25 E 32GB盘(一块非常高端的SLC盘).在这两种情况下,表空间都是创建在裸设备上从而避免文件系统缓存的影响,并且重做日志(Redo Log)以及其他表空间都放置在一个独立的磁盘上.<br />
下面是相关的读性能:<br />
<img src="http://lh6.ggpht.com/_Tj8VUwhrzbg/S4N-f_7Dz5I/AAAAAAAAAHM/Rqd8h0o9aCE/flashSSDreads.png"/><br />
如你所料,将表直接放置在闪存上是最快的,因为这里只有读IO,并且每个读IO都是针对这个非常快的闪存SSD.注意,Flash Cache也同样带来了很大的好处,数据库文件的IO数量下降了,当然从Flash Cache读取的速度是非常快的.</p>
<p><strong>更新性能</strong></p>
<p>下面,我将测量基于主键的更新操作的性能.这样一个更新操作的性能是由读性能(读取这个数据块到缓存中)与DBWR性能共同决定的.如果DBWR写出脏块的速度不是足够快的话,就会出现<em>free buffer waits</em>与<em>write complete waits</em>等待事件了.<br />
我最初预计,对闪存表空间的测试肯定会遇到大量的free buffer waits,因为理论上讲闪存的写操作要比闪存的读操作慢很多很多. 我认为使用Flash Cache将会避免这个问题,并且能够提供更好的性能. 然而,结果非常出人意外.<br />
<img src="http://lh3.ggpht.com/_Tj8VUwhrzbg/S4N9mf6SmDI/AAAAAAAAAG4/0pVcCGQrKSw/update%20performance.png"/><br />
我使用不同的负荷重复进行上面的测试,但是每一次结果都非常相似.然而,使用Flash Cache还是要比不使用Flash Cache来得好,将表直接存储到闪存储存上性能就更好了.X-25 E SSD盘支持一个远远超出我预期的写频率(大概2000次写操作/秒).Intel 宣称(现在我相信了)他们拥有精密的算法可以有效避免通常与闪存SSD存储介质有关的写损失.<br />
注意,实际上大部分的<em>free buffer waits</em>等待事件的产生都是由Flash Cache的配置导致的.Flash Cache使得Oracle的逻辑读的速度更快,但是由于物理写仍然会面对相对更慢的机械旋转磁盘,Buffer Cache常常会被脏块所填满.<br />
或许随着时间的流逝,当所有的块都被至少写过一次,清除操作变得更加普遍时,X-25的性能会出现下降.然而,如果TRIM API能够正常工作,那么理论上这种性能恶化应该可以避免.注意,并不是所有环境都支持TRIM API,并不是所有的SSD都支持,旧版本的Windows操作可能也不支持.</p>
<p><strong>Write complete waits: flash cache</strong></p>
<p>在上一篇文章中,我注意到,当DBWR繁忙的时候会跳过而不写Flash Cache,这一点应该可以避免Flash Cache的活动不会成为导致<em>free buffer  waits</em>的直接原因.然而,我观察到,在特定的负荷下会遇到大量的&#8221;<em>write complete waits: flash cache</em>&#8220;等待事件.例如,下面的输出结果显示大约有75%的实耗时间(elapsed time)消耗在这个等待事件上.<br />
<img src="http://lh5.ggpht.com/_Tj8VUwhrzbg/S4N9KDzpHXI/AAAAAAAAAGo/vZX6mtcRvBg/s512/write%20complete%20waits.png"/><br />
当一个会话想要修改一个数据,但同时DBWR又正在将这个数据块写到数据文件的时候会出现<em>write complete waits</em>等待事件.Flash Cache相对应的等待事件的出现也是由于类似的原因,但这是不是写数据块到数据文件,而将由DBWR将它写到Flash Cache.当一个特定的数据块被非常频繁的修改的时候,这种现象就会出现;在这种情况下,数据块在被DBWR写完之前被修改的机会相当高.</p>
<p><strong>结论</strong></p>
<p>X-25 E闪存SSD盘给我留下了非常深刻的印象.如果它可以长时间的保持优异的写吞吐量的话,相比于传统的机械旋转磁盘以及11GR2的数据库Flash Cache,它提供的吞吐量有惊人的优势.不过,还有几句忠告:</p>
<ul>
<li>我没有长时间对这个SSD盘做压力测试.有些闪存盘在磨损后显示出更长写延迟.理论上,Intel X-25 E的TRIM功能应该可以避免这一点,但我并没有在这些测试中验证这一点.</li>
<li>在经过长时间的使用后,闪存盘可能会成为一种不可靠的存储介质.当然,任何磁盘都会损坏,但是闪存盘比机械磁盘更加容易损坏(虽然在损坏之前它可能可以做更多的工作).</li>
<li>在我的测试中,我的表很小,足以完全存放在闪存存储上.如果做不到这一点,使用数据库Flash Cache让我们在无法将整个数据库都放到SSD上的时候,也可以利用闪存盘.</li>
<p>现在,我更加热衷于使用闪存盘了.只要你购买一个高性能的SSD盘(考虑SLC &amp; TRIM),你就可以考虑将整个表空间或者数据库都存放在这些盘上.如果担负不起所有的数据都存放在高端的闪存盘上,还可以考虑使用11GR2的数据库Flash Cache来获得显著的性能提升.
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.alidba.net/index.php/archives/390/feed</wfw:commentRss>
		</item>
		<item>
		<title>数据库Flash Cache(II)</title>
		<link>http://www.alidba.net/index.php/archives/388</link>
		<comments>http://www.alidba.net/index.php/archives/388#comments</comments>
		<pubDate>Tue, 23 Feb 2010 08:24:12 +0000</pubDate>
		<dc:creator>jametong</dc:creator>
		
		<category><![CDATA[大话技术]]></category>

		<category><![CDATA[Flash Cache]]></category>

		<category><![CDATA[free buffer waits]]></category>

		<category><![CDATA[mlc]]></category>

		<category><![CDATA[slc]]></category>

		<category><![CDATA[ssd]]></category>

		<guid isPermaLink="false">http://www.alidba.net/?p=388</guid>
		<description><![CDATA[本文翻译自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 [...]]]></description>
			<content:encoded><![CDATA[<blockquote><p>本文翻译自<a href="http://guyharrison.squarespace.com/">Guy Harrison</a>的blog: <a href="http://guyharrison.squarespace.com/blog/2009/11/30/more-on-the-database-flash-cache.html">More on the database flash cache</a>, 这是他写的关于Flash Cache系列文章的第二篇, 后面还有一篇对比测试Flash Database与Flash Cache的文章, 我也将翻译出来放到此处, 另外还会翻译两篇Kevin Closson写的关于Flash Cache的相关文章.</p>
<p>原文地址: <a href="http://www.dbthink.com/?p=135">数据库Flash Cache(II)</a>
 </p></blockquote>
<p><strong>数据库Flash Cache(II)</strong></p>
<p>我非常期待我的高性能Flash SSD(一个Intel X-25E),但同时,我已经在一套便宜的硬件设备上(我的<a href="http://www.dbthink.com/?p=100">前一篇文章</a>中对此做了说明)做了很多数据库Flash Cache的测试. 有时,在较差的硬件上测试新特性也非常有用,因为你可以观察到一些在高速运行的环境下不会发生的现象.<br />
最初,我天真地认为数据块会被Oracle的服务进程拷贝到Flash Cache中.比如,当我从磁盘读取的时候,同时将数据块放到Buffer Cache与Flash Cache中.然而,通过观察,实际情形可能是, 当数据块将要被刷出Buffer Cache的时候, 由DBWR进程将此数据块从Buffer Cache移入到Flash Cache中.<br />
当然,这是一种更好的处理方式.DBWR可以异步的将数据块写入Flash Cache中,从而用户会话可以获得好处,在数据块写入Flash Cache中的时候不需要进行等待(比从磁盘读取耗费更少的时间).<br />
因此,一个数据块可能有一个如下图的生命周期:<br />
<img /></p>
<ul>
<li>Oracle服务进程从磁盘读取文件的一个数据块,并将其放到Buffer Cache中</li>
<li>如果一个会话在稍后需要访问这个数据块,并且这个数据块仍然在Buffer Cache中,就可以直接从Buffer Cache中读取这个数据块</li>
<li>在这个数据块离开Buffer Cache之前,DBWR将它写入Flash Cache(如果DBWR不是太忙的话)</li>
<li>如果一个会话稍后需要访问这个数据块,并且它还在Flash Cache中的话,就可以从Flash Cache中读取这个数据块(有可能会将这个数据块放回到Buffer Cache中)</li>
<li>如果这个数据块被修改了,DBWR进程最终会将这个数据块写回磁盘.(问题:Flash Cache中那些没有被修改过的数据块会发生什么呢?)</li>
</ul>
<p><strong>DBWR 与 Flash Cache</strong></p>
<p>从闪存上读取的速度是非常快的,但是往闪存上写就要慢很多了.因此,为了避免性能问题,DBWR进程应该:</p>
<ul>
<li>1. 不要去写闪存, 除非不得不写,并且</li>
<li>2. 如果有其他更重要的活动的话,一点都不要写闪存.</li>
</ul>
<p>对于第一点,在数据块将要被刷出Buffer Cache之前,DBWR进程不要填充Flash Cache.也就是说,DBWR不会为了防止数据块将要被刷出Buffer Cache去写这个数据块,而只是这个数据块确实(或许是可能性很高的时候)将要被刷出Buffer Cache的时候才会将其写入Flash Cache.在数据块被写入Buffer Cache的时候,我们没有发现对Flash Cache的写操作, 同时有其他数据块被刷出Buffer Cache的情况除外.<br />
第二点,当DBWR忙于将脏块写入到磁盘的时候,不会去写Flash Cache,也不会推迟将要被刷出Buffer Cache的数据块的刷出时间.DBWR进程必须尽可能块地清除Buffer Cache中的脏块,否则&#8221;free buffer waits&#8221;等待事件就会阻止新的数据块被读入Buffer Cache中.如果数据块将要被刷出Buffer Cache,而同时DBWR进程又很忙,这些数据块将不会被写入Flash Cache,统计信息项&#8217;flash cache insert skip: DBWR overloaded&#8217;将出现增长.</p>
<p><strong>度量DB Flash Cache的效率</strong></p>
<p>下面的<a href="http://guyharrison.squarespace.com/storage/flash_insert_stats.sql">查询</a>报告了DBWR由于太忙或其他原因而跳过写Flash Cache的次数:</p>
<p><img /></p>
<ul>
<li>我认为我理解&#8221;DBWR overloaded&#8221;统计项的意思,DBWR忙于写脏块到磁盘而没有足够的处理能力来将干净的数据块写入Flash Cache.</li>
<li>&#8220;flash cache insert skip: exists&#8221;统计项也比较容易理解.如果我们从Flash Cache将一个数据块读回Buffer Cache,它还会继续保留在Flash Cache中.当它再次在Buffer Cache中被刷出的时候就不再需要写入Flash Cache了.</li>
<li>&#8220;not current&#8221;统计项可能表示,DBWR明白磁盘或者Buffer Cache中还有这个数据块的一个更新的拷贝,因此拒绝写一个版本到Flash Cache上.</li>
<li>&#8220;not useful&#8221;统计项,我无法理解…</li>
</ul>
<p>我认为这里最大的启示是:<br />
<strong><em>在一个DBWR非常繁忙的系统上,DB Flash Cache可能会非常无效率.</em></strong><br />
要测量出使用Flash Cache带来的好处,我们可以计算由于使用Flash Cache而被避免掉的物理读的次数,以及乘上每种读操作耗费的平均时间.下面<a href="http://guyharrison.squarespace.com/storage/flash_time_savings.sql">这个查询</a>尝试做这个对比,虽然仅仅针对单块读操作:</p>
<p><img /> </p>
<p><strong>结论</strong></p>
<p>DB Flash Cache的架构使得我们可以对与闪存SSD相关的相对糟糕的写性能可以显得稍微放松一点.如果我们将数据文件放到闪存SSD上,我们可能会有遭遇&#8221;free buffer waits&#8221;瓶颈的风险: DBWR可能会由于尝试往低速(从写延迟的角度上看的话)的闪存盘上写数据块而中止,相对于磁盘来讲,我们可能实际上遭遇了更糟糕的性能,至少对于有非常高的写频率的应用来讲.相应地,几乎不应该出现实际的负面因素,可能出现的更糟糕的情况是,DBWR太忙,以致于不会去填充Flash Cache,而使得Flash Cache变得更加无效.<br />
DB Flash Cache的效率依赖于以下两个主要因素:</p>
<ul>
<li>1. DBWR是否有足够的空间事件来写Flash Cache?</li>
<li>2. DBWR需要花费多长事件类写闪存设备?</li>
</ul>
<p>第一个问题的答案依赖于DBWR当前的活动效率,可能也相应地依赖于你当期的磁盘阵列有多少可用的写带宽.但是,如果你的数据库已经遭遇或者将要遭遇&#8221;free buffer waits&#8221;,那么DB Flash Cache可能无法正常发挥作用,因为DBWR可能永远不会写Flash Cache.<br />
第二个问题的答案依赖于闪存SSD的品质.通常你会选择使用拥有最小写延迟的闪存盘.那通常也意味着支持TRIM API调用,并且是使用SLC(Single Level Cell)的闪存.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.alidba.net/index.php/archives/388/feed</wfw:commentRss>
		</item>
		<item>
		<title>Android OS手机连接802.1X网络的方法</title>
		<link>http://www.alidba.net/index.php/archives/386</link>
		<comments>http://www.alidba.net/index.php/archives/386#comments</comments>
		<pubDate>Mon, 22 Feb 2010 02:03:41 +0000</pubDate>
		<dc:creator>vogts</dc:creator>
		
		<category><![CDATA[大话技术]]></category>

		<guid isPermaLink="false">http://www.alidba.net/?p=386</guid>
		<description><![CDATA[android google g1 g2 g3 802.1x peap wpa-eap]]></description>
			<content:encoded><![CDATA[<p>不少Google的G1,G2手机用户无法连接802.1X无线网络，我查阅了下网上资料，说是在设计的时候UI。所以无法出现输入USERID，PASSWORD的登录框的缘故。但是可以通过修改配置文件实现。</p>
<p>进入终端,su到root。然后修改 /data/misc/wifi/wpa_supplicant.conf  文件。</p>
<p>在配置文件的末端增加一段：</p>
<p>network={<br />
ssid=&#8221;xxx&#8221;<br />
proto=RSN<br />
key_mgmt=WPA-EAP<br />
eap=PEAP<br />
identity=&#8221;xxxx&#8221;<br />
password=&#8221;xxxx&#8221;<br />
phase1=&#8221;peapver=0&#8243;<br />
phase2=&#8221;auth=MSCHAPV2&#8243;<br />
priority=203<br />
}</p>
<p>把XXXX替换成你的变量即可。然后开启无线网络，那么就OK了。</p>
<p>btw:</p>
<p>谢谢sky友情提供手机，给我参考。谢谢！</p>
]]></content:encoded>
			<wfw:commentRss>http://www.alidba.net/index.php/archives/386/feed</wfw:commentRss>
		</item>
		<item>
		<title>针对stored outline做得几个测试</title>
		<link>http://www.alidba.net/index.php/archives/382</link>
		<comments>http://www.alidba.net/index.php/archives/382#comments</comments>
		<pubDate>Wed, 10 Feb 2010 09:23:03 +0000</pubDate>
		<dc:creator>jametong</dc:creator>
		
		<category><![CDATA[大话技术]]></category>

		<category><![CDATA[plan stability]]></category>

		<category><![CDATA[stored outlines]]></category>

		<guid isPermaLink="false">http://www.alidba.net/?p=382</guid>
		<description><![CDATA[主要的测试代码以及测试方法都依赖于之前翻译的两篇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&#62;@tmp
sys@test&#62;create user james identified by james default tablespace users temporary tablespace temp;

User created.

Elapsed: 00:00:00.00
sys@test&#62;grant create session,create table,create procedure,create any outline,alter session to james;

Grant succeeded.

Elapsed: 00:00:00.01
sys@test&#62;alter user james [...]]]></description>
			<content:encoded><![CDATA[<blockquote><p>主要的测试代码以及测试方法都依赖于之前翻译的两篇<a href="http://jonathanlewis.wordpress.com/">Jonathan Lewis</a>的关于stored outlines的两篇文章:<br />
<a href="http://www.dbthink.com/?p=76">Oracle 8i/9i中的执行计划稳定性</a><br />
<a href="http://www.dbthink.com/?p=83">在Oracle 9中伪造存储概要</a><br />
此文的原文为 <a href="http://www.dbthink.com/?p=114">针对stored outline做得几个测试</a>
</p>
</blockquote>
<p>1. 测试场景</p>
<ul>
<li>os : Red Hat Enterprise Linux AS release 4 (Nahant Update 6)</li>
<li>Db Version: Oracle9i Enterprise Edition Release 9.2.0.8.0 - 64bit Production</li>
</ul>
<p>2. 初始化环境.</p>
<pre>
sys@test&gt;@tmp
sys@test&gt;create user james identified by james default tablespace users temporary tablespace temp;

User created.

Elapsed: 00:00:00.00
sys@test&gt;grant create session,create table,create procedure,create any outline,alter session to james;

Grant succeeded.

Elapsed: 00:00:00.01
sys@test&gt;alter user james quota unlimited on users;

User altered.

Elapsed: 00:00:00.00
sys@test&gt;
sys@test&gt;@conn james/james
sys@test&gt;SET TERMOUT OFF
james@test&gt;
james@test&gt;set feedback off
james@test&gt;alter session set cursor_sharing = exact
  2  /
Elapsed: 00:00:00.00
james@test&gt;alter session set nls_date_format = 'yyyy-mm-dd hh24:mi:ss'
  2  /
Elapsed: 00:00:00.00
james@test&gt;SET TERMOUT ON
james@test&gt;set feedback on
james@test&gt;set timing on
james@test&gt;
james@test&gt;create table so_demo (
  2          n1      number,
  3          n2      number,
  4          v1      varchar2(10)
  5  );

Table created.

Elapsed: 00:00:00.05
james@test&gt;
james@test&gt;insert into so_demo values (1,1,'One');

1 row created.

Elapsed: 00:00:00.00
james@test&gt;
james@test&gt;create index sd_i1 on so_demo(n1);

Index created.

Elapsed: 00:00:00.01
james@test&gt;create index sd_i2 on so_demo(n2);

Index created.

Elapsed: 00:00:00.00
james@test&gt;
james@test&gt;analyze table so_demo compute statistics;

Table analyzed.

Elapsed: 00:00:00.01
james@test&gt;
james@test&gt;!cat &gt; c_proc.sql &lt;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&gt;_END_
SP2-0042: unknown command "_END_" - rest of line ignored.
james@test&gt;!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&gt;@c_proc.plb
james@test&gt;
</pre>
<p>3. 得到当前此SQL的存储概要. </p>
<pre>
james@test&gt;var m_value varchar2(10);
james@test&gt;alter session set create_stored_outlines = demo;

Session altered.

Elapsed: 00:00:00.00
james@test&gt;exec get_value(1, 1, :m_value);

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.03
james@test&gt;alter session set create_stored_outlines = false;

Session altered.

Elapsed: 00:00:00.00
james@test&gt;col category format a20
james@test&gt;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&gt;col hint format a30
james@test&gt;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&gt;
</pre>
<p>4. 第一种改变存储概要的方法, 也即通过直接修改存储概要表来调整.<br />
4.1 生成新的用来伪造的存储概要</p>
<pre>
james@test&gt;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&gt;
james@test&gt;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&gt;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
</pre>
<p>4.2 进入outln Schema Crack系统的outln相关表, 调换两个存储概要的实际内容.</p>
<pre>
outln@test&gt;@tmp
outln@test&gt;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&gt;
outln@test&gt;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&gt;commit;

Commit complete.

Elapsed: 00:00:00.00
outln@test&gt;
</pre>
<p>4.3 打开使用stored outlines的参数, 测试是否确实使用了存储概要, 并附上10046 Trace得到的信息.</p>
<pre>
james@test&gt;@tmp
james@test&gt;var m_value varchar2(10);
james@test&gt;alter session set use_stored_outlines = demo;

Session altered.

Elapsed: 00:00:00.00
james@test&gt;alter session set events '10046 trace name context forever,level 12';

Session altered.

Elapsed: 00:00:00.00
james@test&gt;exec get_value(1, 1, :m_value);

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.03
james@test&gt;alter session set events '10046 trace name context off';

Session altered.

Elapsed: 00:00:00.00
james@test&gt;alter session set use_stored_outlines = false;

Session altered.

Elapsed: 00:00:00.00
james@test&gt;col spid new_value spid
james@test&gt;select spid from v$process where addr = (
  2    select paddr from v$session a,v$mystat b where a.sid = b.sid and rownum
</pre>
<pre>
sys@test&gt;@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&gt;
</pre>
<p><em>此时, 可以发现, 调整过后的存储概要已经发生作用了, 没有使用最初的索引SD_I1而是使用了我们指定的索引SD_I2.</em></p>
<p>5. 使用在另一个Schema创建View的方式来替换存储概要.<br />
5.1 首先清除掉前面测试使用的存储概要, 并重新生成存储概要.</p>
<pre>
james@test&gt;var m_value varchar2(10);
james@test&gt;alter session set create_stored_outlines = demo;

Session altered.

Elapsed: 00:00:00.01
james@test&gt;exec get_value(1, 1, :m_value);

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.05
james@test&gt;alter session set create_stored_outlines = false;

Session altered.

Elapsed: 00:00:00.00
james@test&gt;
james@test&gt;col category format a15
james@test&gt;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&gt;col hint format a30
james@test&gt;l
  1* select name, stage, hint from user_outline_hints where name = 'SYS_OUTLINE_100210003729028'
james@test&gt;/

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.
</pre>
<p>5.2 登录一个新的用户, 创建一个View并重新编译这个存储概要.</p>
<pre>
james2@test&gt;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&gt;alter outline sys_outline_100210003729028 rebuild;

Outline altered.

Elapsed: 00:00:00.01
james2@test&gt;@conn james/james
Elapsed: 00:00:00.00
Elapsed: 00:00:00.00
james@test&gt;col category format a15
james@test&gt;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&gt;col hint format a30
james@test&gt;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
</pre>
<p>关注使用星号标识的两行, 你将发现存储概要确实已经通过这种方式替换掉了</p>
<p>6. 使用在新Schema中创建一个类似的新表来实现改变存储概要的方法.<br />
6.1. 通过在原Schema重新编译outline使其回到最初状态.</p>
<pre>
james@test&gt;alter outline SYS_OUTLINE_100210003729028 rebuild; 

Outline altered.

Elapsed: 00:00:00.01
james@test&gt;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&gt;
</pre>
<p>6.2. 在新的Schema创建对应表,以及索引SD_I2. 并重新编译存储概要.</p>
<pre>
james2@test&gt;l
  1  create table so_demo (
  2     n1      number,
  3     n2      number,
  4     v1      varchar2(10)
  5* )
james2@test&gt;/

Table created.

Elapsed: 00:00:00.02
james2@test&gt;create index sd_i2 on so_demo(n2);

Index created.

Elapsed: 00:00:00.08
james2@test&gt;alter outline sys_outline_100210003729028 rebuild;

Outline altered.

Elapsed: 00:00:00.00
james@test&gt;col hint format a30

james@test&gt;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
</pre>
<p>这个时候, 你将发现存储概要也改成我们最初想要的结果了, 也即用索引SD_I2替换掉了SD_I1.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.alidba.net/index.php/archives/382/feed</wfw:commentRss>
		</item>
		<item>
		<title>使用Oracle 11GR2 数据库Flash Cache</title>
		<link>http://www.alidba.net/index.php/archives/376</link>
		<comments>http://www.alidba.net/index.php/archives/376#comments</comments>
		<pubDate>Wed, 10 Feb 2010 05:24:18 +0000</pubDate>
		<dc:creator>jametong</dc:creator>
		
		<category><![CDATA[大话技术]]></category>

		<category><![CDATA[11g New Feature]]></category>

		<category><![CDATA[Flash Cache]]></category>

		<guid isPermaLink="false">http://www.alidba.net/?p=376</guid>
		<description><![CDATA[本文翻译自Guy Harrison的blog: Using the Oracle 11GR2 database flash cache, 这是他写的关于Flash Cache系列文章的第一篇, 后面还有两篇, 我也将陆续翻译出来放到此处, 另外还会翻译两篇Kevin Closson写的关于Flash Cache的相关文章.
原文参见 使用Oracle 11GR2 数据库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设备上做试验,那么也必须先挂载这个设备.在我的机器上,我创建了一个目录&#8221;/mnt/usbflash&#8221;,接着在/etc/fstab文件新增了一个如下的条目:
/dev/sda1 /mnt/usbflash  vfat noauto,users,rw,umask=0 0 0
在你的系统中,你可能需要将&#8221;/dev/sda1&#8243;改成其他的设备,这依赖于你如何配置磁盘.然后就可以通过输入&#8221;mount /dev/sda1&#8243;来挂载这个闪存盘(flash drive).
一旦挂载完毕,就可以通过设置系统书db_flash_cache_files与db_flash_cache_size来配置flash cache了. 如下是我的相关设置:

注意, 参数DB_FLASH_CACHE_FILE的值必须是一个存储在闪存盘上的文件,而不是这个闪存盘的挂载点本身.
一旦这些参数设置完毕,flash cache就会被激活,并且将充当buffer cache的二级缓存. [...]]]></description>
			<content:encoded><![CDATA[<blockquote><p>本文翻译自<a href="http://guyharrison.squarespace.com/">Guy Harrison</a>的blog: <a href="http://guyharrison.squarespace.com/blog/2009/11/24/using-the-oracle-11gr2-database-flash-cache.html">Using the Oracle 11GR2 database flash cache</a>, 这是他写的关于Flash Cache系列文章的第一篇, 后面还有两篇, 我也将陆续翻译出来放到此处, 另外还会翻译两篇<a href="http://kevinclosson.wordpress.com/">Kevin Closson</a>写的关于Flash Cache的相关文章.</p>
<p>原文参见 <a href="http://www.dbthink.com/?p=100">使用Oracle 11GR2 数据库Flash Cache</a>
</p>
</blockquote>
<p><strong>使用Oracle 11GR2 数据库Flash Cache</strong><br />
Oracle最近发布了一个补丁程序,使得你可以在Oracle Enterprise Linux中使用数据库Flash Cache,即使你并没有使用Exadata存储.这个补丁的名字有点隐晦:</p>
<ul>
<li><em>8974084:META BUG FOR FLASH CACHE 11.2PL BUGS TO BACKPORT TO 11.2.0.1 OEL</em></li>
</ul>
<p>只要安装好这个补丁,你就可以使用任何已存在的flash 设备作为数据库的Flash Cache.下面是我在一个非常旧的服务器与一个非常便宜的usb flash设备上做的初步尝试.相对于更优质的硬件来讲, 测试结果并不具有代表性,但是我认为,它仍然是很有趣的.</p>
<p><strong>安装与配置<br />
</strong><br />
如果你也像我一样想在一个USB flash设备上做试验,那么也必须先挂载这个设备.在我的机器上,我创建了一个目录&#8221;/mnt/usbflash&#8221;,接着在/etc/fstab文件新增了一个如下的条目:</p>
<p><code>/dev/sda1 /mnt/usbflash  vfat noauto,users,rw,umask=0 0 0</code></p>
<p>在你的系统中,你可能需要将&#8221;/dev/sda1&#8243;改成其他的设备,这依赖于你如何配置磁盘.然后就可以通过输入&#8221;mount /dev/sda1&#8243;来挂载这个闪存盘(flash drive).<br />
一旦挂载完毕,就可以通过设置系统书db_flash_cache_files与db_flash_cache_size来配置flash cache了. 如下是我的相关设置:</p>
<p><img src="http://lh6.ggpht.com/_Tj8VUwhrzbg/S3I7Cf72fjI/AAAAAAAAABg/JGdZx17KaVU/parameters.png" alt="Parameters About FlashCache" /></p>
<p>注意, 参数DB_FLASH_CACHE_FILE的值必须是一个存储在闪存盘上的文件,而不是这个闪存盘的挂载点本身.<br />
一旦这些参数设置完毕,flash cache就会被激活,并且将充当buffer cache的二级缓存. 当从主缓存移出一个block的时候,它将被移到flash cache中,从而在再次读回这个block的时候不需要产生一次访问磁盘的物理读.<br />
<strong><br />
监控</strong></p>
<p>有多种方法来检查是否使用到了flash cache. 首先,v$sysstat视图包含多个新的统计项来展示有多少数据块被添加到flash cache中,以及在flash cache中命中的次数(从这儿下载脚本):</p>
<p><img src="http://lh3.ggpht.com/_Tj8VUwhrzbg/S3I7CpC-TzI/AAAAAAAAABk/M8W_vr-yQXE/flashstats1.png" alt="Flash Cache Statistics" /></p>
<p>还有一些新的等待事件来显示往flash cache中添加条目以及从flash cache中读取条目引起的等待. 从下图可以看到,&#8217;db flash&#8217;等待的次数超过&#8217; db file sequential read&#8217;的等待次数,虽然从flash cache读取的速度远远快于从磁盘的读取速度(但是从flash cache读取的次数也远远超过从磁盘读取的次数):</p>
<p><img src="http://lh5.ggpht.com/_Tj8VUwhrzbg/S3I7C_AToWI/AAAAAAAAABw/QP6u5pc0Bsc/wait_events.png" alt="Wait Event" /></p>
<p>那么,请记住,我不能不为这个测试选择了最糟糕的硬件-一个老旧的俩CPU intel主机以及一个便宜的拇指磁盘.即使这样,还是发现了引人注目的问题-相对于整个处理时间来讲写开销非常严重.虽然相对于db file sequential reads来讲从flash cache中读取的速度可以节约很多时间,维护flash cache的开销也可能会非常高,因为大部分基于闪存的SSD(Solid State Disk)都有相对严重的写瓶颈.<br />
所有基于闪存的固态盘(SSD,Solid State Disk)都有写性能方面的问题.然而,便宜的MLC(Multi Level Cell)闪存的写速度差不多相当于更加昂贵的SLC(Single Level Cell)的1/3.在闪存盘比较新时,空闲空间可以以单页(一般为4k)递增的方式来写.然而,当闪存盘越来越旧时,写操作需要先清除一个完整的128页的块,从而就会慢很多了.我的便宜的USB盘是一个旧盘,并且是MLC的,所以它的写性能是非常差的.但是,即使是最好的基于闪存的SSD,它的写速度也比读速度要慢很多,因此,有些时候使用闪存盘反而会导致数据库运行的更慢. 因此监控就显得非常重要了.<br />
下面是几个与此相关的其他v$systat统计项:</p>
<p><img src="http://lh6.ggpht.com/_Tj8VUwhrzbg/S3I7CqfJbvI/AAAAAAAAABo/DJfJH1gT9ms/flashstats2.png" alt="Flash Statistics2" /></p>
<p>可以通过查看V$BH视图来查看cache中的内容. 保存在flash cache中的buffer有诸如&#8217;flashcur&#8217;一类的状态,使得我们可以统计每个对象有多少buffer在主缓存中,有多少buffer在flash cache中(脚本来自这里):</p>
<p><img src="http://lh6.ggpht.com/_Tj8VUwhrzbg/S3I7CycYOoI/AAAAAAAAABs/NmRz3iUEy1o/cache%20contents.png" alt="Cache Contents" /></p>
<p>在这个例子中,TXN_DATA表有85,833个块在flash cache中,有28,753个块在主buffer cache中.<br />
<strong><br />
结论</strong></p>
<p>能让flash cache工作,我非常高兴,特别是在如此蹩脚的硬件上. 很高兴Oracle对Non-Exadata硬件开放这项技术.<br />
我将很快在搭建一套更好的环境,从而我可以理解它在相当好的商业SSD闪存盘上是如何工作的.<br />
我坚信, 市场需要的理想的存储体系应该包含至少两层&#8212;一层来解决海量存储,另一层解决快速检索. 但是,我们也应该保持谨慎,因为flash的写弊端可能导致我们在RAID5上遇到的类似的性能问题.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.alidba.net/index.php/archives/376/feed</wfw:commentRss>
		</item>
		<item>
		<title>在Oracle 9中伪造存储概要</title>
		<link>http://www.alidba.net/index.php/archives/374</link>
		<comments>http://www.alidba.net/index.php/archives/374#comments</comments>
		<pubDate>Thu, 04 Feb 2010 08:05:20 +0000</pubDate>
		<dc:creator>jametong</dc:creator>
		
		<category><![CDATA[大话技术]]></category>

		<category><![CDATA[plan stability]]></category>

		<category><![CDATA[stored outline]]></category>

		<guid isPermaLink="false">http://www.alidba.net/?p=374</guid>
		<description><![CDATA[译者注: 本文翻译自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 [...]]]></description>
			<content:encoded><![CDATA[<blockquote><p>译者注: 本文翻译自<a href="http://jonathanlewis.wordpress.com/">Jonathan Lewis</a>的文章<a href="http://www.dbazine.com/oracle/or-articles/jlewis5">Faking Stored Outlines in Oracle 9</a>, 可以从此处下载原文的word版本: <a href="http://www.jlcomp.demon.co.uk/05_outlines9.doc">Stored Outlines in Oracle 9</a>.<br />
本文与前一篇<a href="http://www.dbthink.com/?p=76">Oracle 8i/9i中的执行计划稳定性</a>是Jonathan Lewis先生写的关于stored outline具体使用以及其中可能涉及到的风险系列文章,也是我所见到的关于stored outline介绍的最详细的文档了. 关于stored outline还有以下相关资料可以对照阅读下:<br />
<a href="http://kerryosborne.oracle-guy.com/2008/12/oracle-outlines-aka-plan-stability/">Oracle Outlines - aka Plan Stability</a> By <em>Kerry Osborne</em><br />
<a href="http://oracle-randolf.blogspot.com/2009/03/plan-stability-in-10g-using-existing.html">Plan stability in 10g - using existing cursors to create Stored Outlines and SQL profiles</a> By <em>Randolf Geist</em><br />
<a href="http://www.oracle-base.com/articles/misc/Outlines.php">Stored Outlines and Plan Stability</a> By <em>Tim Hall</em><br />
<a href="http://www.rampant-books.com/ebook_vendor_tune.htm">Tuning Third-party Vendor Oracle Systems <em>:Tuning when you can&#8217;t touch the code</em></a> By <em>Mark Ault</em></p>
<p>原文为<a href="http://www.dbthink.com/?p=83">在Oracle 9中伪造存储概要</a>
</p></blockquote>
<p><strong></strong><strong>在Oracle 9中伪造存储概要</strong></p>
<p><em>在前面的文章中,我讨论到存储概要,并且描述了一种通过滥用系统来生成你所需要存储概要的方法.我同时也指出,在Oracle 9中使用这种方法存在一些风险,因为存储在数据库中的细节信息已经变得非常复杂.在接下来的文章中,我将介绍一种合法的操作存储概要的方法,这种方法可以应用在Oracle 8与Oracle 9中.这篇文章的细节都是基于实验得出的,实验环境是Oracle 8.1.7.0与Oracle 9.2.0.1的默认安装环境.<br />
</em><br />
<strong>回顾</strong></p>
<p>当你知道如何通过给一段DML语句添加提示就可以让它运行的快很多,但是你却没有访问源代码并将提示放到适当位置的途径, 你会怎么做?</p>
<p>在上一篇文章中,我展示了你可以如何用存储概要(也被称为执行计划稳定性)来驱使数据库引擎为你做这种工作.<br />
一个存储概要由两个组件组成(宽泛地讲)-一个你希望控制的SQL语句,一组每当Oracle发现这条SQL被优化都将在它上面应用的提示.这两个组件都被保存在一个被称为outln的数据库schema中.</p>
<p>我们可以使用一组如图-1中类似的查询语句来检查保存在其中的SQL语句,以及附着在这条SQL语句上的提示.</p>
<pre>
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}'
;
</pre>
<p><strong>Figure 1  Examining stored outlines.</strong><br />
在前面的文章中,我介绍了这样一种想法来欺骗系统, 使用合法的方法创建一个存储概要, 接着,使用一个文本相似的但已经添加过提示的语句来创建一个存储概要,最后,使用一组SQL语句来交换这两个存储概要的实际结果来修复存储概要.</p>
<p>当时,我曾提到这种方法对Oracle 8来讲或许是安全的,但是由于在新版本中引入的变化, 在Oracle 9中可能会导致问题.<br />
这篇文章将对这些变化进行考查, 介绍一种合法的方法来得到你想要的一组存储到outln中的提示,用来解决你的那些问题语句.</p>
<p><strong>相关变化</strong></p>
<p>如果你登录到outln schema(在Oracle 9中它默认是锁住的)查看可用的表清单,你将发现Oracle 9比Oracle 8多出来一张表. 这些表为:</p>
<table>
<tr>
<td>ol$</td>
<td>SQL语句</td>
</tr>
<tr>
<td>ol$hints</td>
<td>提示表</td>
</tr>
<tr>
<td>ol$nodes</td>
<td>查询块</td>
</tr>
</table>
<p>第三张表是一张新表,被用来将提示列表与这条SQL语句(一份内部重写的版本)的多个不同查询块.你还将发现,提示列表(ol$hints)也被加强了,其中还包括文本长度与偏移量的细节信息.<br />
图2为这三张表的详细描述,用星号标注了Oracle 9中出现的新字段.</p>
<pre>
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
</pre>
<p><strong>Figure 2  The outln tables.</strong><br />
你可能很快会注意到多处细节-有大量信息被基于这些表的视图排除在外了.视图user_outline_hints的视图定义完全没有改变,尽管表ol$hints上新增加了10个字段.实际上,这个视图在Oracle 8的时候就极度不足,因为它遗漏了相当有用的hint#字段.<br />
你还会注意到,Oracle 9现在有两个hash_value字段.如果你在Oracle 8与Oracle 9中对同样的SQL语句创建存储概要,你将发现它们拥有同样的hash_value,但是Oracle 9中对应的hash_value可能完全不同.<br />
你可以也会发现,Oracle 9中的signature(签名)字段的值与Oracle 8中的值是不同的. 这是由于Oracle这两个版本之间策略上的最主要的调整就是为了提高存储概要的重复利用.在Oracle 8中,只有在你的SQL语句与存储的SQL语句完全匹配(包含空格符/大小写以及换行符)的时候才可以使用.到Oracle 9之后,这个限制放宽了,只要在去除掉重复的&#8221;空字符&#8221;并且将文本都转换成同样的大小写之后SQL语句能够匹配就可以使用存储概要了.例如,下面的两条SQL语句将使用同一个存储概要.</p>
<pre>
select * from t1 where id = 5;

SELECT  *
FROM	T1
WHERE   ID = 5;
</pre>
<p>策略上的这个调整导致了第一次创建这个执行计划的SQL语句的签名的调整;如果你的数据库从Oracle 8升级到Oracle 9,就必须更新存储概要或者必须确认它们不再被使用.(事实上,别名为dbms_outln包outln_pkg包含一个特别的存储过程update_signatures来处理这个问题.<br />
不过,关于Oracle 9中这些表的最意义重大的事情却是对查询语句中涉及到的文本与对象的极度详细描述.创建图-3中显示的例子,并在继续阅读之前详细查看ol$hints表中的内容.</p>
<pre>
drop table t1;

create table t1
nologging
as
select
	rownum		id,
	rownum		n1,
	object_name,
	rpad('x',500)	padding
from
	all_objects
where
	rownum &lt;= 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
;
</pre>
<p><strong>Figure 3 Sample code.</strong><br />
这个例子立足于一个简单的小表,包含两组相近的列,其中一个列为逐渐(从而也创建了索引),另外包含一个简单的非唯一索引.我们为一个典型的查询创建一个存储概要来查看我们可以如何对待它.<br />
如果针对由这个例子创建的存储概要demo_1运行图-1中的示例查询,我们将发现这个查询将附带6个提示.</p>
<pre>
  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
</pre>
<p>不出意外,其中的第四行显示我们将使用主键索引来访问这张表.如果我们实际上想要Oracle使用这个非唯一索引T1_I1访问表,我们该对存储概要做什么呢?理论上讲,我们可以调整这个存储概要以使得</p>
<pre>	    3     1  INDEX(T1 T1_PK)</pre>
<p>变成</p>
<pre>	    3     1  INDEX(T1 T1_I1)</pre>
<p><strong>新特性</strong></p>
<p>我们可以做的第一件事是查看包dbms_outln_edit.这个包在Oracle 9中引入,正如它的名字提示的那样,它的目标是编辑存储概要,这看上去令人充满希望.<br />
然而,查看包的方法列表,检查文档手册,我们发现这个包只包含如下几个&#8221;编辑相关&#8221;的方法.</p>
<pre>
	CREATE_EDIT_TABLES
	DROP_EDIT_TABLES
	CHANGE_JOIN_POS
</pre>
<p>前两个方法允许我们创建或删除outln用户拥有的表的本地拷贝.第三个方法允许我们交换一个存储概要计划中的表连接顺序. 哪怕仅仅是帮助我们修改一个简单的提示的方法也是没有的.目前,这个包看上去实际上一无是处-但是它们注定会越来越完善.<br />
当然B方案就是去侵入它了!如果我们登录到outln用户,并自己诊察ol$hints表(也就是支撑视图user_outline_hints的表)的内容,我们可以尝试下面的这个更新操作:</p>
<pre>
update ol$hints
set
	hint_text = 'INDEX(T1 T1_I1)'
where
	ol_name = 'demo_1'
and hint# = 4
;
</pre>
<p>登录回到我们的测试Schema,清空共享池,并且打开存储概要:</p>
<pre>
connect test_user/test
alter system flush shared_pool;
alter session set use_stored_outlines = true;
</pre>
<p>实际上,我们将发现侵入的存储概要确实如你所愿了.但是这是一个让人不爽的解决方案,<br />
因为我们一直会给一个关于&#8221;更改数据字典表&#8221;的严厉的警告.</p>
<p><strong>旧方法(1)</strong></p>
<p>接着,我们的目标就是寻找一种迂回但又看似无害的方法来改变存储概要表的内容,并且不需要直接的侵入存储概要表.<br />
从前(在Oracle 9以前),我们有多种实现办法,它们都是基于这样一个事实,存储概要的效果仅仅取决于进来的SQL语句的文本,而完全不关心对对象类型或者对象的所有者.<br />
将表替换成添加过提示的视图是一种有效的方法.(我相信,这种方法最初是由Tom Kyte在它的《Expert One on One: Oracle》这本书中介绍的).<br />
连接到另外一个拥有表T1的访问权限的Schema，按照下面的定义创建一个添加过提示的视图,视图与表的名称保持一致.</p>
<pre>

Create or replace view t1 as
Select /*+ index(t1,t1_i1) */
	*
from test_user.t1;
</pre>
<p>一旦视图创建完成,就在这个schema下使用下面的这个命令&#8221;重编译&#8221;这个已存在的存储概要.</p>
<pre>
	alter outline demo_1 rebuild;
</pre>
<p><strong>注意</strong>:必须拥有权限alter any outline才可以执行这个命令.<br />
如果登录回到原来的schema,清空缓存(flush shared pool),并且启用存储概要,我们将会发现原来的查询语句现在如愿以偿的使用上了索引T1_I1.</p>
<pre>
	    3      1  INDEX(T1 T1_I1)
</pre>
<p>这样为什么可行?因为存储概要并不属于任何一个schema. 当我们在另外一个schema中重编译这个称为demo_1的存储概要的时候,名称T1应用到了一个本地的包含提示的视图上了,因此Oracle将这个提示包装进了真实的执行计划中,从而也进入了这个存储概要.通过查看视图user_outline_hints,将会发现关键的那一行已经变成了<br />
	    3      1  INDEX(T1 T1_I1)</p>
<p>很不幸,我们还将注意到它现在包含3行如下形式的提示:</p>
<pre>
	    2      1  NOREWRITE
	    1      2  NOREWRITE
	    1      1  NOREWRITE
</pre>
<p>而原来我们只有两行:</p>
<pre>
	    2     1  NOREWRITE
	    1     1  NOREWRITE
</pre>
<p>我们引入了一个新的提示,也就是&#8221;Stage 1,Node 2&#8243;.我不敢说我确切的知道这是什么意思,但是它一定与这样一个事实有关,为了在另外一个Schema解析优化这个查询,Oracle执行了一个额外的步骤来将视图引用转换成基础表的引用.<br />
虽然目前这不会导致存储概要无法正确使用(或者如同它在这个简单的例子中这样),谁又能说Oracle在将来的版本又会有多挑剔呢.</p>
<p><strong>旧方法(2)</strong></p>
<p>因为视图引入了一个可能在将来版本变成错误的异常,我们不得不更加挑剔. 让我们试试下面的这种方法:</p>
<pre>
Create a new schema.
Create table T1 in that schema.
Create ONLY the index T1_I1.
Rebuild the outline in that schema
</pre>
<p>如果比较存储概要重建前后user_outline_hints的详细内容(必须重新登录到原来的Schema来做这件事),我们将发现除了我们想要改变的那一行,它们是完全一样的.重新登录回原来的Schema,通过清空共享池以及打开存储概要做一个常规检查,我们将会发现修改后的存储概要已经被使用了.</p>
<p>然而,还有一个潜在的威胁,不过这一次更加隐蔽.再回去看图-2中出现在Oracle 9中的新字段的定义-你认为字段user_table_name中保存的值将会是什么啊?它应该是有限制的表名称,例如:<br />
	<em>{User_name}.{table_name}</em></p>
<p>在我们的例子中,这将告诉Oracle表T1实际上是一个属于新的Schema的表,而不是原来的Schema下面的表.即使Oracle确实在使用这个存储概要,这个表里的信息也充分说明Oracle是在错误的对象上面应用这个存储概要.<br />
另外,它现在现在有效,但是为什么有这个信息在这儿呢-可能是为了将来的版本增强做准备呢.</p>
<p><strong>可靠的赌注</strong></p>
<p>看来,要生成存储概要,而又不面临将来的风险就只有一种方法了,那就是尽可能的真实.</p>
<p>在这个示例中,你需要删除主键索引,生成执行计划,然后替换掉主键.</p>
<p>当然,你可能不想在生产环境做这件事,即使你在生产环境做了,存储概要也有可能选择走全表扫描(而不是走你想要的那个索引).<br />
底线是你必须至少在另一个数据库中有一个这个Schema的空闲拷贝,接着需要非常小心的操作这个拷贝以得到需要的存储概要.一旦得到这个存储概要,你就可以从一个数据库导出它并将其导入另外一个数据库.<br />
例如:在这个空闲的数据库上,删除主键以避免PK唯一扫描就是可行的.如果Oracle并没有自动的采用另外一个索引,你可以对系统说各种谎言,诸如:</p>
<ul>
<li>将optimizer_mode改成first_rows_1</li>
<li>构造数据使得列N1上的数据是唯一的.(不过,不要将其改成唯一索引,这样生成出来的存储概要将是unique scan而不是range scan了).</li>
<li>使用dbms_stats来使这个索引获得一个难以置信的clustering_factor.</li>
<li>调整参数optimiser_index_caching来告诉系统,这个索引已经完全被缓存.</li>
<li>调整optimiser_index_cost_adj来告诉系统,多块读要比单块读要慢100倍.</li>
<li>使用dbms_stats修改aux_stats$表来达到上一条同样的宣称效果,并且添加这样一个事实,一次多块读的典型大小为2个块.</li>
<li>重建这个索引以包含where从句中的所有字段.</li>
</ul>
<p>给定存储概要表中的内容,假使表的所有者不变,对象类型不变以及不改变索引的唯一度,几乎任何事情都可以做. 如果你可以构造一个数据集与环境来生成一份与生产系统没有内部不一致的存储概要,那么你就可以以任何方式来欺骗系统.</p>
<p><strong>结论</strong><br />
相对于Oracle 8来讲,在Oracle 9中进入存储概要的信息变更更加精细了.之前可以非常容易也很明显无风险的&#8221;调整&#8221;存储概要的方法,现在还仍然可以工作,但是Oracle 9中收集的巨量的附加信息表明,之前的那种方法现在可能会给将来留下隐患.</p>
<p>虽然Oracle 9中引入了一个编辑存储概要的包,但它当前还只是局限在交换表的连接顺序.除了使用第二套系统来调整索引(通过改变环境参数以及人造的统计信息)外, 看似不存在安全的干预存储概要的方法.</p>
<p><strong>参考文献</strong><br />
<em>Oracle 9i Release 2: Database Performance Tuning Guide and Reference - Chapter 7.  </em><br />
<em>Oracle 9I Release 2: Supplied PL/SQL Packages and Types Reference - Chapters 41 - 42  </em></p>
]]></content:encoded>
			<wfw:commentRss>http://www.alidba.net/index.php/archives/374/feed</wfw:commentRss>
		</item>
		<item>
		<title>Oracle 8i/9i中的执行计划稳定性</title>
		<link>http://www.alidba.net/index.php/archives/367</link>
		<comments>http://www.alidba.net/index.php/archives/367#comments</comments>
		<pubDate>Mon, 01 Feb 2010 08:57:35 +0000</pubDate>
		<dc:creator>jametong</dc:creator>
		
		<category><![CDATA[大话技术]]></category>

		<category><![CDATA[3rd party application]]></category>

		<category><![CDATA[plan stability]]></category>

		<category><![CDATA[stored outlines]]></category>

		<guid isPermaLink="false">http://www.alidba.net/?p=367</guid>
		<description><![CDATA[本文翻译自Jonathan Lewis的文章. Plan Stability in Oracle 8i/9i, 可以到此下载这篇文章的word版本. Stored Outlines in Oracle 8
Oracle 8i/9i中的执行计划稳定性
找出如何使用&#8221;存储概要&#8221;来提高应用的性能的方法,即使你无法修改源代码,调整索引或者胡乱摆弄配置..
by Jonathan Lewis (jonathan@jlcomp.demon.co.uk)
工具箱: 为了测试需要,这篇文章将仅仅涉及可在一个SQL*Plus会话中运行的简单SQL与PL/SQL代码. 读者将需要拥有一些特别的权限(典型的终端用户一般没有这些权限),另外,还需要熟悉一些基本SQL的技能. 本文从Oracle8i开始讲起, 接着讨论Oracle9i, 在9i部分将提供多个针对存储概要的生成与处理的改进.
针对黑盒的后门
如果你是这样一个DBA,在你负责维护的Oracle数据库上运行的是一个第三方应用程序, 你一定曾经遇到这样的困惑, 库缓存(Library Cache)中有一些运行的异常缓慢并且代价高昂的SQL,但是只能在源代码中添加提示才能解决问题.
从Oracle8.1开始,你不再需要重写SQL以添加提示了,可以在不调整代码的情况下使得提示生效.这个特性就是存储概要,也即执行计划稳定性,它的原理也很简单:存储信息到数据库中,告诉数据库&#8221;如果发现一条SQL语句与XXX长得像,就将这些提示放到如下这些位置&#8221;.
实际上,这将给你如下三种可能的好处.首先,可以优化那部分代价昂贵的语句.其次,如果存在一些Oracle需要花费很长时间进行优化(而不是执行)的其他语句,可以利用它来节约时间并降低优化阶段的争用.最后,它为了提供了一个使用新的cusror_sharing参数的机会同时又不用付出失去最优执行路径的损失.
在Oracle8中让它起作用有几个问题需要处理(在Oracle9中大部分哦都去掉了), 但是,通常都可以很容易的利用这个特性.
背景/概述
为了展示如何善用存储概要,我们将从一个包含无法修改源代码的存储过程开始, 显然, 这个存储过程会包含一个运行异常无效的SQL.
我们将看到,我们可以如何捕捉SQL语句以及它在数据库中的当前执行路径的细节,发现一些提示来提高这个SQL的性能,然后, 并且使得这条语句在将来的任何时候运行的时候都使用我们指定的提示.
在下面的展示中, 我们将创建一个用户,在这个用户的Schema中 一张表,并且创建一个存储过程来访问这张表-但是仅仅为了有趣-我们将在这个存储过程上使用工具wrap, 以致我们无法对这段代码做逆向工程.
这个演示将默认存储概要框架已经在数据库创建是自动安装了.
初步配置
创建一个用户并使其拥有如下权限:create session,create table,create procedure,create any outline,以及alter session. 以此用户登录并运行以下脚本来创建表.

create table so_demo (
	n1	number,
	n2	number,
	v1	varchar2(10)
)
;

insert into so_demo values (1,1,'One');
create index sd_i1 on so_demo(n1);
create index sd_i2 on so_demo(n2);
analyze table [...]]]></description>
			<content:encoded><![CDATA[<p>本文翻译自Jonathan Lewis的文章. <a href="http://www.dbazine.com/oracle/or-articles/jlewis4">Plan Stability in Oracle 8i/9i</a>, 可以到此下载这篇文章的word版本. <a href="http://www.jlcomp.demon.co.uk/04_outlines.rtf">Stored Outlines in Oracle 8</a><br />
<strong>Oracle 8i/9i中的执行计划稳定性</strong><br />
找出如何使用&#8221;存储概要&#8221;来提高应用的性能的方法,即使你无法修改源代码,调整索引或者胡乱摆弄配置..<br />
by <a href="http://jonathanlewis.wordpress.com/">Jonathan Lewis</a> (jonathan@jlcomp.demon.co.uk)<br />
<strong>工具箱</strong>: 为了测试需要,这篇文章将仅仅涉及可在一个SQL*Plus会话中运行的简单SQL与PL/SQL代码. 读者将需要拥有一些特别的权限(典型的终端用户一般没有这些权限),另外,还需要熟悉一些基本SQL的技能. 本文从Oracle8i开始讲起, 接着讨论Oracle9i, 在9i部分将提供多个针对存储概要的生成与处理的改进.<br />
<strong>针对黑盒的后门</strong><br />
如果你是这样一个DBA,在你负责维护的Oracle数据库上运行的是一个第三方应用程序, 你一定曾经遇到这样的困惑, 库缓存(Library Cache)中有一些运行的异常缓慢并且代价高昂的SQL,但是只能在源代码中添加提示才能解决问题.<br />
从Oracle8.1开始,你不再需要重写SQL以添加提示了,可以在不调整代码的情况下使得提示生效.这个特性就是存储概要,也即执行计划稳定性,它的原理也很简单:存储信息到数据库中,告诉数据库&#8221;如果发现一条SQL语句与XXX长得像,就将这些提示放到如下这些位置&#8221;.<br />
实际上,这将给你如下三种可能的好处.首先,可以优化那部分代价昂贵的语句.其次,如果存在一些Oracle需要花费很长时间进行优化(而不是执行)的其他语句,可以利用它来节约时间并降低优化阶段的争用.最后,它为了提供了一个使用新的cusror_sharing参数的机会同时又不用付出失去最优执行路径的损失.<br />
在Oracle8中让它起作用有几个问题需要处理(在Oracle9中大部分哦都去掉了), 但是,通常都可以很容易的利用这个特性.<br />
<strong>背景/概述</strong><br />
为了展示如何善用存储概要,我们将从一个包含无法修改源代码的存储过程开始, 显然, 这个存储过程会包含一个运行异常无效的SQL.<br />
我们将看到,我们可以如何捕捉SQL语句以及它在数据库中的当前执行路径的细节,发现一些提示来提高这个SQL的性能,然后, 并且使得这条语句在将来的任何时候运行的时候都使用我们指定的提示.<br />
在下面的展示中, 我们将创建一个用户,在这个用户的Schema中 一张表,并且创建一个存储过程来访问这张表-但是仅仅为了有趣-我们将在这个存储过程上使用工具wrap, 以致我们无法对这段代码做逆向工程.<br />
这个演示将默认存储概要框架已经在数据库创建是自动安装了.<br />
<strong>初步配置</strong><br />
创建一个用户并使其拥有如下权限:create session,create table,create procedure,create any outline,以及alter session. 以此用户登录并运行以下脚本来创建表.<br />
<code><br />
create table so_demo (<br />
	n1	number,<br />
	n2	number,<br />
	v1	varchar2(10)<br />
)<br />
;</code><br />
<code><br />
insert into so_demo values (1,1,'One');<br />
create index sd_i1 on so_demo(n1);<br />
create index sd_i2 on so_demo(n2);<br />
analyze table so_demo compute statistics;<br />
</code></p>
<p>接着,需要如下代码来创建存储过程以访问这个表. 创建一个名为c_proc.sql的脚本并包含以下内容:<br />
<code><br />
create or replace procedure get_value (<br />
	i_n1	in	number,<br />
	i_n2	in	number,<br />
	io_v1	out	varchar2<br />
)<br />
as<br />
begin<br />
	select	v1<br />
	into	io_v1<br />
	from	so_demo<br />
	where	n1 = i_n1<br />
	and	n2 = i_n2<br />
	;<br />
end;<br />
/<br />
</code><br />
当然可以直接运行c_proc.sql脚本来创建这个存储过程,但是,为了测试效果,到操作系统中运行如下命令:<br />
<code><br />
wrap iname=c_proc.sql<br />
</code><br />
&gt;返回的信息应该是:<br />
<code><br />
Processing c_proc.sql to c_proc.plb<br />
</code><br />
运行难以理解的脚本c_proc.plb而不是c_proc.sql来创建存储过程, 你将会发现, 在user_source视图中再也找不到目标SQL 语句的任何一点蛛丝马迹.<br />
<strong>这个应用到底想做什么?</strong><br />
现在, 我们已经有了想要假装运行的应用了,或许还可以考虑将sql_trace打开来看看它到底在做什么. 毋须吃惊, 这个SQL语句运行一个全表扫描来得到想要的数据.<br />
在这个小小的测试中, 全表扫描可能是最有效的选择了,但是,我们假定当Oracle使用and-equal选项将我们的单列索引组合起来的执行路径可以获得最佳性能.<br />
使用存储概要来解决这个问题,答案就很简单了. 实际上,有多种方法来实现我们想要达到的目的,因此, 不要将这个例子当作终极策略. Oracle一直在不断推出新特性以使得工作更加轻松,在这儿描述的方法在将来的版本中将无可避免的变得过时.<br />
<strong>你想要应用如何去做?</strong><br />
让Oracle按照我们想要的去做需要分三个阶段:</p>
<li>登录一个新的会话并重新运行这个存储过程,首先告诉Oracle我们需要捕捉它运行的每一条SQL语句,以及这些SQL相关的执行路径的详细信息. 这些&#8221;路径&#8221;存储概要的第一个例子.</li>
<li>为任何有问题的SQL语句创建更好的存储概要,并且将糟糕的存储概要与这个更好的存储概要进行&#8221;交换&#8221;.</li>
<li>登录一个新的会话, 让Oracle在下次遇上匹配的SQL的时候开始使用这个新的存储概要而不是使用常规的优化方法;接着再次运行这个存储过程.</li>
<p>必须不断的停止/开始新的会话以确保pl/sql 没有缓存这个游标. 存储概要仅仅在游标被解析的时候才能被创建与应用, 因此必须确保已经存在的相关游标已经关闭.<br />
因此创建一个会话,并执行以下命令:<br />
<code><br />
alter session set create_stored_outlines = demo;<br />
</code><br />
接着运行一小段匿名块来执行这个存储过程,例如:<br />
<code><br />
declare<br />
	m_value	varchar2(10);<br />
begin<br />
	get_value(1, 1, m_value);<br />
end;<br />
/<br />
</code><br />
然后停止收集执行路径(不然接着要运行的部分SQL也将出现在存储概要的表中,使得使得事情变得更加复杂).<br />
<code><br />
alter session set create_stored_outlines = false;<br />
</code><br />
为了看到我们活动的结果,查询以下视图可以让我们看到Oracle为我们创建并保存的存储概要的细节.<br />
<code><br />
select name, category, used, sql_text<br />
from user_outines<br />
where category = 'DEMO';</code><br />
NAME                           CATEGORY                       USED<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212; &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212; &#8212;&#8212;&#8212;<br />
SQL_TEXT<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br />
SYS_OUTLINE_020503165427311    DEMO                           UNUSED<br />
SELECT V1   FROM SO_DEMO  WHERE N1 = :b1  AND N2 = :b2<br />
&gt;select	name, stage, hint<br />
from	user_outline_hints<br />
where	name = &#8216; SYS_OUTLINE_020503165427311&#8242;;<br />
NAME                                STAGE HINT<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212; &#8212;&#8212;&#8212;- &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br />
SYS_OUTLINE_020503165427311             3 NO_EXPAND<br />
SYS_OUTLINE_020503165427311             3 ORDERED<br />
SYS_OUTLINE_020503165427311             3 NO_FACT(SO_DEMO)<br />
SYS_OUTLINE_020503165427311             3 FULL(SO_DEMO)<br />
SYS_OUTLINE_020503165427311             2 NOREWRITE<br />
SYS_OUTLINE_020503165427311             1 NOREWRITE</p>
<p>我们可以看到,有一个类目为demo有且仅有一个存储概要,研究这个存储概要的sql_text将发现它与我们原来的PL/SQL源中的SQL类似但又不完全一致. 这一点非常重要, 只有在将要运行的sql_text与保存在存储概要中的sql_text非常接近的时候, Oracle才会使用这个存储概要. 实际上, 在Oracle8i中, 这个SQL必须完全匹配, 这也是最初使用存储概要时的一个致命的问题.<br />
从列表中可以看出,存储概要仅仅是一组提示, 这些提示描述Oracle运行特定SQL将要采取的行动. 这个执行计划使用了一个全表扫描操作-哪怕是简单到只是一个全表扫描, Oracle也使用了大量的提示来确保它的执行.<br />
注意,存储概要总是归属于一个类目;在这个例子中是demo类目,这是我们在最初的alter session命令中指定的. 如果我们最初的命令中仅仅指定True而不是demo的话,将会发现存储概要虽在的类目为default.<br />
存储概要也拥有名字, 并且这个名字必须在整个数据库中保持唯一.任何两个存储概要都不能拥有同一个名字, 即使它们是由不同的用户产生的.实际上,存储概要没有属主,它们只有创建者. 如果你创建一个存储概要并且刚刚好与我将要运行的一段SQL语句匹配,接下来Oracle将应用你的这一组提示到我的文本上-即使这些提示在我的Schema中是完全无意义的. (这给我们提供了一种完全不同的伪装存储概要的选项,这将在另外一篇文章中介绍). 你可能已经注意到,当Oracle自动生成存储概要的时候,它的名字有一个简单的格式-包含一个到最近的微秒的时间戳.<br />
接着调优我们的问题SQL, 我们判断, 如果我们能够注入提示/*+ and_equal(so_demo, sd_i1, sd_i2) */,Oracle将使用我们期望的执行路径,因此我们明确的创建如下存储概要:<br />
<code><br />
create or replace outline so_fix<br />
for category demo on<br />
select /*+ and_equal(so_demo, sd_i1, sd_i2) */ v1<br />
from	so_demo<br />
where	n1 = 1<br />
and	n2 = 2;<br />
</code><br />
这个语句创建了一个显示命名为so_fix的存储概要到我们的demo类目.通过指定谓词name = &#8216;SO_FIX&#8217;来查询user_outlines和user_outline_hints,可以看到这个存储概要到底长啥样.<br />
&gt;<code><br />
NAME                           CATEGORY                      USED<br />
------------------------------ ------------------------------ ---------<br />
SQL_TEXT<br />
--------------------------------------------------------------------------------<br />
SO_FIX                         DEMO                           UNUSED<br />
select /*+ and_equal(so_demo, sd_i1, sd_i2) */ v1<br />
from so_demo<br />
where n1 = 1<br />
and n2 = 2</code><br />
&gt;NAME                                STAGE HINT<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212; &#8212;&#8212;&#8212;- &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br />
SO_FIX                                  3 NO_EXPAND<br />
SO_FIX                                  3 ORDERED<br />
SO_FIX                                  3 NO_FACT(SO_DEMO)<br />
SO_FIX                                  3 AND_EQUAL(SO_DEMO SD_I1 SD_I2)<br />
SO_FIX                                  2 NOREWRITE<br />
SO_FIX                                  1 NOREWRITE</p>
<p>请特别注意, FULL(SO_DEMO)所在的行已经被替换成了AND_EQUAL(SO_DEMO SD_I1 SD_I2),这也正是我们想要看到的.<br />
现在,我们必须交换这两个存储概要.我们希望每次Oracle看到原来的文本的时候都使用我们的新的提示列表,我们必须欺骗Oracle(来达到这一点).视图user_outlines与user_outline_hints是由schemaoutln中的两个表(分别为ol$和ol$hints)生成的,我们将不得不直接修改这两张表; 这意味着要使用outln登录到数据库或者使用包含更新这两张表的权限的账户登录.<br />
幸运的是,outln schema里面的这两张表不包含任何引用完整性约束.方便的是,表ol$(outline)与表ol$hints(hint)之间的关系是通过存储概要的名称(存储在列ol_name中)来定义的.因此,通过非常仔细地检查存储概要的名称,我们可以通过在ol$hints表上替换两个存储概要的名称来交换它们的提示,就像下面的SQL这样:<br />
<code><br />
update outln.ol$hints<br />
set ol_name =<br />
	decode(<br />
		ol_name,<br />
			'SO_FIX','SYS_OUTLINE_020503165427311',<br />
			'SYS_OUTLINE_020503165427311','SO_FIX'<br />
	)<br />
where ol_name in ('SYS_OUTLINE_020503165427311','SO_FIX')<br />
;<br />
</code><br />
侵入一个如此接近Oracle内核的内容,特别是在一个手册中给出注释,你可能会感觉到一点的不自在-但是这个更新操作实际上是经过Oracle官方认可的:参见Metalink Note: <a href="https://support.oracle.com/CSP/ui/flash.html#tab=KBHome%28page=KBHome&amp;id=%28%29%29,%28page=KBNavigator&amp;id=%28bmDocDsrc=DOCUMENT&amp;bmDocTitle=How%20to%20specify%20hidden%20hints%20%28Outlines%29%20on%20SQL%20statements%20in%20Oracle%208i&amp;viewingMode=1143&amp;bmDocID=92202.1&amp;from=BOOKMARK&amp;bmDocType=BULLETIN%29%29">92202.1</a> Dated 5th June 2000.然而,这个注释还有一点没有提及, 还需要做第二个更新操作来确保与每个存储概要相关的提示的数量保持一致.如果没有做这件事,你将发现部分存储概要将在export/import的时候被破坏或毁坏.<br />
<code><br />
update outln.ol$ ol1<br />
set hintcount = (<br />
	select	hintcount<br />
	from	ol$ ol2<br />
	where	ol2.ol_name in ('SYS_OUTLINE_020503165427311',' SO_FIX')<br />
	and	ol2.ol_name != ol1.ol_name<br />
	)<br />
where<br />
	ol1.ol_name in ('SYS_OUTLINE_020503165427311','SO_FIX')<br />
;<br />
</code><br />
一旦交换操作完成, 你就可以登录一个新的会话,告诉Oracle开始使用存储概要,重新运行这个存储过程并退出;并再次使用sql_trace来检查Oracle具体如何操作这条SQL语句的.告诉Oracle使用这个(侵入)的存储概要的方法是执行以下命令:<br />
<code><br />
alter session set use_stored_outline = demo;<br />
</code><br />
通过检查跟踪稳健,你将发现这条SQL现在使用的是and_equal路径. (如果你使用tkprof来处理并explain这个跟踪稳健,你将发现输出结果显示了两个相互矛盾的路径. 第一条路径显示实际发生and_equal路径,这是正确的结果;第二条路径可能显示为一个全表扫描,因为存储概要在tkprof在针对跟踪到的SQL语句运行explain plan的时候可能不会被启用).<br />
<strong>从开发环境到生产环境</strong><br />
到目前为止,我们已经创建好一个outline,我们还需要将其迁移到生产环境中去.还有很多关于存储概要的小功能可以帮助实现这一点. 例如,可以重命名这个存储概要,将其从开发环境导出,并导入到生产系统中,检查它是否能在生产环境的一个&#8217;test&#8217;类目中正常工作,接着将其迁移到生产类目.可用的命令如下:<br />
<code><br />
alter outline SYS_OUTLINE_020503165427311 rename to AND_EQUAL_SAMPLE;<br />
alter outline AND_EQUAL_SAMPLE change category to PROD_CAT;<br />
</code><br />
为了处理将存储概要从开发环境导出并导入到生产系统,可以通过利用在exp的参数文件中添加一个where从句来实现,因为,我们可能会有一个如下的导出参数文件:<br />
<code><br />
userid=outln/outln<br />
tables=(ol$, ol$hints, ol$nodes)	# ol$nodes exists in v9 only<br />
file=so.dmp<br />
consistent=y				# very important<br />
rows=yes<br />
query='where ol_name  = ''AND_EQUAL_SAMPLE'''<br />
</code><br />
<strong>Oracle9中的改进</strong><br />
当开始深入使用存储概要的时候, 还有很多细节需要注意,在Oracle8中,在使用存储概要可以做什么以及它如何工作方面有一些使人烦恼的充满限制的特性.幸运的是,很多这种问题在Oracle 9中都已经解决.</p>
<ul>
<li>最琐碎也最明显的缺陷是,在Oracle 8中,只有当存储概要中的SQL语句与将要运行的语句完全匹配的时候才会被使用. 在Oracle 9中,有一个&#8221;标准化&#8221;的操作来缓解这个匹配需求;在进行比较之前,SQL文本会被转换成大写,并去掉其中的空白字符(一般包含空格,换行,回车以及tab 分隔符). 在不同的SQL语句之间存在微弱的差别的情况下,这个功能提高了使用同样的存储概要的机会.</li>
</ul>
<ul>
<li>当存储概要需要处理一个更加复杂的涉及到多个查询块的执行路径时,还有一些问题-在Oracle 9中这些也已经通过引入第三张表存在于outln schema的表ol$nodes解决掉了.它可以帮助Oracle突破ol$hints中的提示列表,并在输入的SQL的合适的子项之间实现交叉引用. 这当然是个好事情. 然而,这将给在两个存储概要之间交换提示这种策略带来一些负面效应, 因为ol$hints这张表还获取了文本长度与偏移量的多方面的细节.当升级到Oracle 9的时,有必要使用另外一种方法来大量生成存储概要,例如,包含经过手工特别处理的数据集的第二schema,或缺失的索引,或包含嵌入正在使用的提示的存储视图来代替文本中引用的表.</li>
</ul>
<ul>
<li>Oracle 9中引入的另外一个特性是,新增了大量的管理存储概要的支持,其中包含一个可以允许直接修改存储概要的包的初始版本.然而更重大的改进是,新增了一个选项,为在生产系统的操作执行计划提供了更好程度的安全性.虽然没有人喜欢在生产环境做实验,但有时生产环境是唯一的场所-包含合适的数据分布以及数据规模-来让你确定一段特定SQL语句的最优执行计划. 在Oracle 9中,可以创建一份outln表的虽有拷贝,并提取&#8221;公共&#8221;存储概要到里面以做&#8221;私有&#8221;的实验,而不会出现不小心将私有的存储概要对最终用户代码可见的风险.就我个人来讲,我会考虑将它作为杀手锏,但我也可以想象有时它可能成为一种必需. 在一个不那么危险的级别上,如果你有一个大规模的UAT或者测试系统,这就是一个可被用来独立测试的特性了.</li>
</ul>
<p><strong>限制</strong><br />
这篇文章给你提供了足够多的信息来开始测试存储概要;但是在你开始将这项技术应用到生产环境之前,必须明白以下几点.</p>
<ul>
<li>首先,在Oracle8i中,outln的用户的默认密码是一个众所周知的密码,并且这个帐户有一些非常危险的权限.你必须修改这个帐户的密码.在oracle 9中,你将发现这个帐户的状态是locked.</li>
</ul>
<ul>
<li>其次,用来保存存储概要信息的表是创建在system表空间中的.对于一个生产系统来讲当你开始创建存储概要的时候你将发现你在system表空间中使用了大量的空间.将这些表迁移到一个属于它们自己的表空间,这是个好主意.很不幸,这其中有一个表包含一个long类型的列,因此你可能不得不使用exp/imp来将这些表移动到一个新的表空间.</li>
</ul>
<ul>
<li>最后,当使用存储概要对于解决致命的性能问题异常有效时,还会涉及到另外的代价.如果有存储概要被激活,针对每一个新解析的SQL语句Oracle都将检查是否存在一个相关的存储概要. 如果系统中有大量的SQL语句没有对应的存储概要,那么就需要权衡从少量的包含存储概要的SQL语句得到的好处是否能够抵消这部分额外代价.不过, 这是一个仅仅在存在更加严重的性能问题的系统才可能出现的问题.</li>
</ul>
<p><strong>结论.</strong><br />
存储概要可能会非常有用.当你无法通过修改源代码或者改变索引策略时,存储概要可能是仅有的可以让一个第三方应用有效运行的方法.<br />
将这个想法推至极限,如果你仍然必须面对将一个系统从基于规则的优化方法到基于成本的优化方法的话,存储概要可能是是成本最低的并且无风险的选项.<br />
如果要最大程度的受益于存储概要,那么Oracle 9提供了多项改进以允许支持更多类型的SQL语句,降低额外开销,并为你提供更大的灵活性来进行测试,操作以及安装存储概要.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.alidba.net/index.php/archives/367/feed</wfw:commentRss>
		</item>
	</channel>
</rss>
