﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-weidagang2046的专栏-文章分类-Database</title><link>http://www.blogjava.net/weidagang2046/category/1067.html</link><description>物格而后知致</description><language>zh-cn</language><lastBuildDate>Fri, 02 Mar 2007 07:33:11 GMT</lastBuildDate><pubDate>Fri, 02 Mar 2007 07:33:11 GMT</pubDate><ttl>60</ttl><item><title>在应用中加入全文检索功能——基于Java的全文索引引擎Lucene简介</title><link>http://www.blogjava.net/weidagang2046/articles/87684.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Thu, 14 Dec 2006 05:05:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/87684.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/87684.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/87684.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/87684.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/87684.html</trackback:ping><description><![CDATA[
		<p>作者： 车东 Email: chedongATbigfoot.com/chedongATchedong.com</p>
		<p>写于：2002/08 最后更新：
<script language="Javascript" src="http://www.chedong.com/referer.js"></script>
 11/29/2006 17:23:30<br /><a href="http://www.chedong.com/tech/comments.php">Feed Back &gt;&gt;</a> (<a href="http://www.linuxforum.net/doc/smartq-grand.html">Read this before you ask question</a>)<a href="http://creativecommons.org/licenses/by-sa/2.5/"><br /><img alt="Creative Commons License" src="http://www.creativecommons.cn/images/public/somerights.gif" border="0" /></a>&lt;广告&gt;</p>
		<p>版权声明：可以任意转载，转载时请务必以超链接形式标明文章原始出处和作者信息及本声明<br /><a href="http://www.chedong.com/tech/lucene.html">http://www.chedong.com/tech/lucene.html</a></p>
		<p>关键词：Lucene java full-text search engine Chinese word segment</p>
		<p>
				<span style="FONT-WEIGHT: bold">
				</span>内容摘要：</p>
		<p>Lucene是一个基于Java的全文索引工具包。</p>
		<ol>
				<li>
						<a href="http://www.chedong.com/tech/lucene.html#intro">基于Java的全文索引引擎Lucene简介：关于作者和Lucene的历史</a>
				</li>
				<li>
						<a href="http://www.chedong.com/tech/lucene.html#compare">全文检索的实现：Luene全文索引和数据库索引的比较</a>
				</li>
				<li>
						<a href="http://www.chedong.com/tech/lucene.html#segment">中文切分词机制简介：基于词库和自动切分词算法的比较</a>
				</li>
				<li>
						<a href="http://www.chedong.com/tech/lucene.html#demo">具体的安装和使用简介：系统结构介绍和演示</a>
				</li>
				<li>
						<a href="http://www.chedong.com/tech/lucene.html#hacking">Hacking Lucene：简化的查询分析器，删除的实现，定制的排序，应用接口的扩展</a>
				</li>
				<li>
						<a href="http://www.chedong.com/tech/lucene.html#learn">从Lucene我们还可以学到什么</a>
				</li>
		</ol>
		<p>
				<a name="intro">
						<b>基于Java的全文索引/检索引擎——Lucene</b>
				</a>
		</p>
		<p>Lucene不是一个完整的全文索引应用，而是是一个用Java写的全文索引引擎工具包，它可以方便的嵌入到各种应用中实现针对应用的全文索引/检索功能。</p>
		<p>Lucene的作者：Lucene的贡献者<a href="http://www.nutch.org/blog/cutting.html">Doug Cutting</a>是一位资深全文索引/检索专家，曾经是V-Twin搜索引擎(Apple的Copland操作系统的成就之一)的主要开发者，后在Excite担任高级系统架构设计师，目前从事于一些INTERNET底层架构的研究。他贡献出的Lucene的目标是为各种中小型应用程序加入全文检索功能。</p>
		<p>Lucene的发展历程：早先发布在作者自己的<a href="http://www.lucene.com/">www.lucene.com</a>，后来发布在<a href="http://sourceforge.net/projects/lucene/">SourceForge</a>，2001年年底成为APACHE基金会jakarta的一个子项目：<a href="http://jakarta.apache.org/lucene/">http://jakarta.apache.org/lucene/</a></p>
		<p>已经有很多Java项目都使用了Lucene作为其后台的全文索引引擎，比较著名的有：</p>
		<ul>
				<li>
						<a href="http://www.jivesoftware.com/">J</a>
						<a href="http://www.jivesoftware.com/">ive</a>：WEB论坛系统； 
</li>
				<li>
						<a href="http://eyebrowse.tigris.org/">Eyebrows</a>：邮件列表HTML归档/浏览/查询系统，本文的主要参考文档“<a href="http://www.javaworld.com/javaworld/jw-09-2000/jw-0915-lucene_p.html">TheLucene search engine: Powerful, flexible, and free</a>”作者就是EyeBrows系统的主要开发者之一，而EyeBrows已经成为目前APACHE项目的主要邮件列表归档系统。 
</li>
				<li>
						<a href="http://xml.apache.org/cocoon/index.html">Cocoon</a>:基于XML的web发布框架，全文检索部分使用了Lucene 
</li>
				<li>
						<p align="left">
								<a href="http://www.eclipse.org/">Eclipse</a>:基于Java的开放开发平台，帮助部分的全文索引使用了Lucene</p>
				</li>
		</ul>
		<p>对于中文用户来说，最关心的问题是其是否支持中文的全文检索。但通过后面对于Lucene的结构的介绍，你会了解到由于Lucene良好架构设计，对中文的支持只需对其语言词法分析接口进行扩展就能实现对中文检索的支持。</p>
		<p>
				<b>
						<a name="compare">全文检索的实现机制</a>
				</b>
		</p>
		<p>Lucene的API接口设计的比较通用，输入输出结构都很像数据库的表==&gt;记录==&gt;字段，所以很多传统的应用的文件、数据库等都可以比较方便的映射到Lucene的存储结构/接口中。总体上看：可以先把<b>Lucene当成一个支持全文索引的数据库系统</b>。</p>
		<p>比较一下Lucene和数据库：</p>
		<table width="100%" border="1">
				<tbody>
						<tr>
								<td align="middle" width="50%">Lucene</td>
								<td align="middle" width="50%">数据库</td>
						</tr>
						<tr>
								<td width="50%">
										<pre>索引数据源：doc(field1,field2...) doc(field1,field2...)<br />                  \  indexer /<br />                 _____________<br />                | Lucene Index|<br />                --------------<br />                 / searcher \<br /> 结果输出：Hits(doc(field1,field2) doc(field1...))</pre>
								</td>
								<td width="50%">
										<pre> 索引数据源：record(field1,field2...) record(field1..)<br />              \  SQL: insert/<br />               _____________<br />              | DB  Index   |<br />               -------------<br />              / SQL: select \<br />结果输出：results(record(field1,field2..) record(field1...))</pre>
								</td>
						</tr>
						<tr>
								<td width="50%">Document：一个需要进行索引的“单元”<br />一个Document由多个字段组成</td>
								<td width="50%">Record：记录，包含多个字段</td>
						</tr>
						<tr>
								<td width="50%">Field：字段</td>
								<td width="50%">Field：字段</td>
						</tr>
						<tr>
								<td width="50%">Hits：查询结果集，由匹配的Document组成</td>
								<td width="50%">RecordSet：查询结果集，由多个Record组成</td>
						</tr>
				</tbody>
		</table>
		<p>
				<b>全文检索 ≠ like "%keyword%"</b>
		</p>
		<p>通常比较厚的书籍后面常常附关键词索引表（比如：北京：12, 34页，上海：3,77页……），它能够帮助读者比较快地找到相关内容的页码。而数据库索引能够大大提高查询的速度原理也是一样，想像一下通过书后面的索引查找的速度要比一页一页地翻内容高多少倍……而索引之所以效率高，另外一个原因是它是排好序的。<b>对于检索系统来说核心是一个排序问题</b>。</p>
		<p align="left">由于数据库索引不是为全文索引设计的，因此，<b>使用like "%keyword%"时，数据库索引是不起作用的</b>，在使用like查询时，搜索过程又变成类似于一页页翻书的遍历过程了，所以对于含有模糊查询的数据库服务来说，LIKE对性能的危害是极大的。如果是需要对多个关键词进行模糊匹配：like"%keyword1%" and like "%keyword2%" ...其效率也就可想而知了。</p>
		<p>所以建立一个高效检索系统的关键是建立一个类似于科技索引一样的反向索引机制，将数据源（比如多篇文章）排序顺序存储的同时，有另外一个排好序的关键词列表，用于存储关键词==&gt;文章映射关系，利用这样的映射关系索引：[关键词==&gt;出现关键词的文章编号，出现次数（甚至包括位置：起始偏移量，结束偏移量），出现频率]，检索过程就是把<b>模糊查询变成多个可以利用索引的精确查询的逻辑组合的过程</b>。从而大大提高了多关键词查询的效率，所以，全文检索问题归结到最后是一个排序问题。</p>
		<p>由此可以看出模糊查询相对数据库的精确查询是一个非常不确定的问题，这也是大部分数据库对全文检索支持有限的原因。Lucene最核心的特征是通过特殊的索引结构实现了传统数据库不擅长的全文索引机制，并提供了扩展接口，以方便针对不同应用的定制。</p>
		<p>可以通过一下表格对比一下数据库的模糊查询：</p>
		<table height="283" width="100%" border="1">
				<tbody>
						<tr>
								<td align="middle" width="9%" height="16">　</td>
								<td align="middle" width="47%" height="16">Lucene全文索引引擎</td>
								<td align="middle" width="40%" height="16">数据库</td>
						</tr>
						<tr>
								<td width="9%" height="48">索引</td>
								<td width="47%" height="48">将数据源中的数据都通过全文索引一一建立反向索引</td>
								<td width="40%" height="48">对于LIKE查询来说，数据传统的索引是根本用不上的。数据需要逐个便利记录进行GREP式的模糊匹配，比有索引的搜索速度要有多个数量级的下降。</td>
						</tr>
						<tr>
								<td width="9%" height="49">匹配效果</td>
								<td width="47%" height="49">通过词元(term)进行匹配，通过语言分析接口的实现，可以实现对中文等非英语的支持。</td>
								<td width="40%" height="49">使用：like "%net%" 会把netherlands也匹配出来，<br />多个关键词的模糊匹配：使用like "%com%net%"：就不能匹配词序颠倒的xxx.net..xxx.com</td>
						</tr>
						<tr>
								<td width="9%" height="32">匹配度</td>
								<td width="47%" height="32">有匹配度算法，将匹配程度（相似度）比较高的结果排在前面。</td>
								<td width="40%" height="32">没有匹配程度的控制：比如有记录中net出现5词和出现1次的，结果是一样的。</td>
						</tr>
						<tr>
								<td width="9%" height="32">结果输出</td>
								<td width="47%" height="32">通过特别的算法，将最匹配度最高的头100条结果输出，结果集是缓冲式的小批量读取的。</td>
								<td width="40%" height="32">返回所有的结果集，在匹配条目非常多的时候（比如上万条）需要大量的内存存放这些临时结果集。</td>
						</tr>
						<tr>
								<td width="9%" height="32">可定制性</td>
								<td width="47%" height="32">通过不同的语言分析接口实现，可以方便的定制出符合应用需要的索引规则（包括对中文的支持）</td>
								<td width="40%" height="32">没有接口或接口复杂，无法定制</td>
						</tr>
						<tr>
								<td width="9%" height="32">结论</td>
								<td width="47%" height="32">高负载的模糊查询应用，需要负责的模糊查询的规则，索引的资料量比较大</td>
								<td width="40%" height="32">使用率低，模糊匹配规则简单或者需要模糊查询的资料量少</td>
						</tr>
				</tbody>
		</table>
		<p>
				<span style="FONT-WEIGHT: bold">全文检索和数据库应用最大的不同在于：让</span>
				<span style="FONT-WEIGHT: bold">最相关的</span>
				<span style="FONT-WEIGHT: bold">头100条结果满足98%以上用户的需求<br /></span>
				<br />Lucene的创新之处：</p>
		<p>大部分的搜索（数据库）引擎都是用B树结构来维护索引，索引的更新会导致大量的IO操作，Lucene在实现中，对此稍微有所改进：不是维护一个索引文件，而是在扩展索引的时候不断创建新的索引文件，然后定期的把这些新的小索引文件合并到原先的大索引中（针对不同的更新策略，批次的大小可以调整），这样在不影响检索的效率的前提下，提高了索引的效率。</p>
		<p>Lucene和其他一些全文检索系统/应用的比较：</p>
		<table width="100%" border="1">
				<tbody>
						<tr>
								<td align="middle" width="18%">　</td>
								<td align="middle" width="45%">Lucene</td>
								<td align="middle" width="37%">其他开源全文检索系统</td>
						</tr>
						<tr>
								<td width="18%">增量索引和批量索引</td>
								<td width="45%">可以进行增量的索引(Append)，可以对于大量数据进行批量索引，并且接口设计用于优化批量索引和小批量的增量索引。</td>
								<td width="37%">很多系统只支持批量的索引，有时数据源有一点增加也需要重建索引。</td>
						</tr>
						<tr>
								<td width="18%">数据源</td>
								<td width="45%">Lucene没有定义具体的数据源，而是一个文档的结构，因此可以非常灵活的适应各种应用（只要前端有合适的转换器把数据源转换成相应结构），</td>
								<td width="37%">很多系统只针对网页，缺乏其他格式文档的灵活性。</td>
						</tr>
						<tr>
								<td width="18%">索引内容抓取</td>
								<td width="45%">Lucene的文档是由多个字段组成的，甚至可以控制那些字段需要进行索引，那些字段不需要索引，近一步索引的字段也分为需要分词和不需要分词的类型：<br />   需要进行分词的索引，比如：标题，文章内容字段<br />   不需要进行分词的索引，比如：作者/日期字段</td>
								<td width="37%">缺乏通用性，往往将文档整个索引了</td>
						</tr>
						<tr>
								<td width="18%">语言分析</td>
								<td width="45%">通过语言分析器的不同扩展实现：<br />可以过滤掉不需要的词：an the of 等，<br />西文语法分析：将jumps jumped jumper都归结成jump进行索引/检索<br />非英文支持：对亚洲语言，阿拉伯语言的索引支持</td>
								<td width="37%">缺乏通用接口实现</td>
						</tr>
						<tr>
								<td width="18%">查询分析</td>
								<td width="45%">通过查询分析接口的实现，可以定制自己的查询语法规则：<br />比如： 多个关键词之间的 + - and or关系等</td>
								<td width="37%">　</td>
						</tr>
						<tr>
								<td width="18%">并发访问</td>
								<td width="45%">能够支持多用户的使用</td>
								<td width="37%">　</td>
						</tr>
				</tbody>
		</table>
		<p>　</p>
		<p>
				<b>
						<a name="segment">关于亚洲语言的的切分词问题(Word Segment)</a>
				</b>
		</p>
		<p>对于中文来说，全文索引首先还要解决一个语言分析的问题，对于英文来说，语句中单词之间是天然通过空格分开的，但亚洲语言的中日韩文语句中的字是一个字挨一个，所有，首先要把语句中按“词”进行索引的话，这个词如何切分出来就是一个很大的问题。</p>
		<p>首先，肯定不能用单个字符作(si-gram)为索引单元，否则查“上海”时，不能让含有“海上”也匹配。</p>
		<p>但一句话：“北京天安门”，计算机如何按照中文的语言习惯进行切分呢？<br />“北京 天安门” 还是“北 京 天安门”？让计算机能够按照语言习惯进行切分，往往需要机器有一个比较丰富的词库才能够比较准确的识别出语句中的单词。</p>
		<p>另外一个解决的办法是采用自动切分算法：将单词按照2元语法(bigram)方式切分出来，比如：<br />"北京天安门" ==&gt; "北京 京天 天安 安门"。</p>
		<p>这样，在查询的时候，无论是查询"北京" 还是查询"天安门"，将查询词组按同样的规则进行切分："北京"，"天安安门"，多个关键词之间按与"and"的关系组合，同样能够正确地映射到相应的索引中。这种方式对于其他亚洲语言：韩文，日文都是通用的。</p>
		<p>基于自动切分的最大优点是没有词表维护成本，实现简单，缺点是索引效率低，但对于中小型应用来说，基于2元语法的切分还是够用的。基于2元切分后的索引一般大小和源文件差不多，而对于英文，索引文件一般只有原文件的30%-40%不同，</p>
		<table height="68" width="100%" border="1">
				<tbody>
						<tr>
								<td align="middle" width="11%" height="18">
										<br />
								</td>
								<td align="middle" width="39%" height="18">自动切分</td>
								<td align="middle" width="50%" height="18">词表切分</td>
						</tr>
						<tr>
								<td width="11%" height="16">实现</td>
								<td width="39%" height="16">实现非常简单</td>
								<td width="50%" height="16">实现复杂</td>
						</tr>
						<tr>
								<td width="11%" height="16">查询</td>
								<td width="39%" height="16">增加了查询分析的复杂程度，</td>
								<td width="50%" height="16">适于实现比较复杂的查询语法规则</td>
						</tr>
						<tr>
								<td width="11%" height="16">存储效率</td>
								<td width="39%" height="16">索引冗余大，索引几乎和原文一样大</td>
								<td width="50%" height="16">索引效率高，为原文大小的30％左右</td>
						</tr>
						<tr>
								<td width="11%" height="16">维护成本</td>
								<td width="39%" height="16">无词表维护成本</td>
								<td width="50%" height="16">词表维护成本非常高：中日韩等语言需要分别维护。<br />还需要包括词频统计等内容</td>
						</tr>
						<tr>
								<td width="11%" height="16">适用领域</td>
								<td width="39%" height="16">嵌入式系统：运行环境资源有限<br />分布式系统：无词表同步问题<br />多语言环境：无词表维护成本</td>
								<td width="50%" height="16">对查询和存储效率要求高的专业搜索引擎<br /></td>
						</tr>
				</tbody>
		</table>
		<p>目前比较大的搜索引擎的语言分析算法一般是基于以上2个机制的结合。关于中文的语言分析算法，大家可以在Google查关键词"wordsegment search"能找到更多相关的资料。</p>
		<p>
				<a name="demo">
						<b>安装和使用</b>
				</a>
		</p>
		<p>下载：<a href="http://jakarta.apache.org/lucene/">http://jakarta.apache.org/lucene/</a></p>
		<p>注意：Lucene中的一些比较复杂的词法分析是用JavaCC生成的（JavaCC：JavaCompilerCompiler，纯Java的词法分析生成器），所以如果从源代码编译或需要修改其中的QueryParser、定制自己的词法分析器，还需要从<a href="https://javacc.dev.java.net/">https://javacc.dev.java.net/</a>下载javacc。</p>
		<p>lucene的组成结构：对于外部应用来说索引模块(index)和检索模块(search)是主要的外部应用入口</p>
		<table width="100%" border="1">
				<tbody>
						<tr>
								<td width="27%">org.apache.Lucene.search/</td>
								<td width="73%">搜索入口</td>
						</tr>
						<tr>
								<td width="27%">org.apache.Lucene.index/</td>
								<td width="73%">索引入口</td>
						</tr>
						<tr>
								<td width="27%">org.apache.Lucene.analysis/</td>
								<td width="73%">语言分析器</td>
						</tr>
						<tr>
								<td width="27%">org.apache.Lucene.queryParser/</td>
								<td width="73%">查询分析器</td>
						</tr>
						<tr>
								<td width="27%">org.apache.Lucene.document/</td>
								<td width="73%">存储结构</td>
						</tr>
						<tr>
								<td width="27%">org.apache.Lucene.store/ </td>
								<td width="73%">底层IO/存储结构</td>
						</tr>
						<tr>
								<td width="27%">org.apache.Lucene.util/</td>
								<td width="73%">一些公用的数据结构</td>
						</tr>
				</tbody>
		</table>
		<p>简单的例子演示一下Lucene的使用方法：</p>索引过程：从命令行读取文件名（多个），将文件分路径(path字段)和内容(body字段)2个字段进行存储，并对内容进行全文索引：索引的单位是Document对象，每个Document对象包含多个字段Field对象，针对不同的字段属性和数据输出的需求，对字段还可以选择不同的索引/存储字段规则，列表如下： 
<table border="1"><tbody><tr><th>方法</th><th>切词</th><th>索引</th><th>存储</th><th>用途</th></tr><tr><td>Field.Text(String name, String value)</td><td>Yes</td><td>Yes</td><td>Yes</td><td valign="top">切分词索引并存储，比如：标题，内容字段</td></tr><tr><td>Field.Text(String name, Reader value)</td><td>Yes</td><td>Yes</td><td>No</td><td valign="top">切分词索引不存储，比如：META信息，<br />不用于返回显示，但需要进行检索内容</td></tr><tr><td>Field.Keyword(String name, String value)</td><td>No</td><td>Yes</td><td>Yes</td><td valign="top">不切分索引并存储，比如：日期字段</td></tr><tr><td>Field.UnIndexed(String name, String value)</td><td>No</td><td>No</td><td>Yes</td><td valign="top">不索引，只存储，比如：文件路径</td></tr><tr><td>Field.UnStored(String name, String value)</td><td>Yes</td><td>Yes</td><td>No</td><td valign="top">只全文索引，不存储</td></tr></tbody></table><pre>public class IndexFiles { <br />  //使用方法：: IndexFiles [索引输出目录] [索引的文件列表] ... <br />  public static void main(String[] args) throws Exception {<br />    String indexPath = args[0];<br />    IndexWriter writer;<br />    //用指定的语言分析器构造一个新的写索引器（第3个参数表示是否为追加索引）<br />    writer = new IndexWriter(indexPath, new SimpleAnalyzer(), false);<br /><br />    for (int i=1; i&lt;args.length; i++) {<br />      System.out.println("Indexing file " + args[i]);<br />      InputStream is = new FileInputStream(args[i]);<br /><br />      //构造包含2个字段Field的Document对象<br />      //一个是路径path字段，不索引，只存储<br />      //一个是内容body字段，进行全文索引，并存储<br />      Document doc = new Document();<br />      doc.add(Field.UnIndexed("path", args[i]));<br />      doc.add(Field.Text("body", (Reader) new InputStreamReader(is)));<br />      //将文档写入索引<br />      writer.addDocument(doc);<br />      is.close();<br />    };<br />    //关闭写索引器<br />    writer.close();<br />  }<br />}<br />　</pre><p>索引过程中可以看到：</p><ul><li>语言分析器提供了抽象的接口，因此语言分析(Analyser)是可以定制的，虽然lucene缺省提供了2个比较通用的分析器SimpleAnalyser和StandardAnalyser，这2个分析器缺省都不支持中文，所以要加入对中文语言的切分规则，需要修改这2个分析器。 
</li><li>Lucene并没有规定数据源的格式，而只提供了一个通用的结构（Document对象）来接受索引的输入，因此输入的数据源可以是：数据库，WORD文档，PDF文档，HTML文档……只要能够设计相应的解析转换器将数据源构造成成Docuement对象即可进行索引。 
</li><li>对于大批量的数据索引，还可以通过调整IndexerWrite的文件合并频率属性（mergeFactor）来提高批量索引的效率。 </li></ul><p>检索过程和结果显示：</p><p>搜索结果返回的是Hits对象，可以通过它再访问Document==&gt;Field中的内容。</p><p>假设根据body字段进行全文检索，可以将查询结果的path字段和相应查询的匹配度(score)打印出来，</p><pre>public class Search { <br />  public static void main(String[] args) throws Exception {<br />    String indexPath = args[0], queryString = args[1];<br />    //指向索引目录的搜索器<br />    Searcher searcher = new IndexSearcher(indexPath);<br />    //查询解析器：使用和索引同样的语言分析器<br />    Query query = QueryParser.parse(queryString, "body", <br />                              new SimpleAnalyzer());<br />    //搜索结果使用Hits存储<br />    Hits hits = searcher.search(query);<br />    //通过hits可以访问到相应字段的数据和查询的匹配度<br />    for (int i=0; i&lt;hits.length(); i++) {<br />      System.out.println(hits.doc(i).get("path") + "; Score: " + <br />                         hits.score(i));<br />    };<br />  }<br />}</pre>在整个检索过程中，语言分析器，查询分析器，甚至搜索器（Searcher）都是提供了抽象的接口，可以根据需要进行定制。 
<p><b><a name="hacking">Hacking Lucene</a></b></p><p><b>简化的查询分析器</b></p><p>个人感觉lucene成为JAKARTA项目后，画在了太多的时间用于调试日趋复杂QueryParser，而其中大部分是大多数用户并不很熟悉的，目前LUCENE支持的语法：</p><p>Query ::= ( Clause )*<br />Clause ::= ["+", "-"] [&lt;TERM&gt; ":"] ( &lt;TERM&gt; | "(" Query ")")</p><p>中间的逻辑包括：and or + - &amp;&amp;||等符号，而且还有"短语查询"和针对西文的前缀/模糊查询等，个人感觉对于一般应用来说，这些功能有一些华而不实，其实能够实现目前类似于Google的查询语句分析功能其实对于大多数用户来说已经够了。所以，Lucene早期版本的QueryParser仍是比较好的选择。</p><p><b>添加修改删除指定记录（Document）</b></p><p>Lucene提供了索引的扩展机制，因此索引的动态扩展应该是没有问题的，而指定记录的修改也似乎只能通过记录的删除，然后重新加入实现。如何删除指定的记录呢？删除的方法也很简单，只是需要在索引时根据数据源中的记录ID专门另建索引，然后利用IndexReader.delete(Termterm)方法通过这个记录ID删除相应的Document。</p><p><b>根据某个字段值的排序功能</b></p><p>lucene缺省是按照自己的相关度算法（score）进行结果排序的，但能够根据其他字段进行结果排序是一个在LUCENE的开发邮件列表中经常提到的问题，很多原先基于数据库应用都需要除了基于匹配度（score）以外的排序功能。而从全文检索的原理我们可以了解到，任何不基于索引的搜索过程效率都会导致效率非常的低，如果基于其他字段的排序需要在搜索过程中访问存储字段，速度回大大降低，因此非常是不可取的。</p><p>但这里也有一个折中的解决方法：在搜索过程中能够影响排序结果的只有索引中已经存储的docID和score这2个参数，所以，基于score以外的排序，其实可以通过将数据源预先排好序，然后根据docID进行排序来实现。这样就避免了在LUCENE搜索结果外对结果再次进行排序和在搜索过程中访问不在索引中的某个字段值。</p><p>这里需要修改的是IndexSearcher中的HitCollector过程：</p><pre>...<br />　scorer.score(new HitCollector() {<br />	private float minScore = 0.0f;<br />	public final void collect(int doc, float score) {<br />	  if (score &gt; 0.0f &amp;&amp;			  // ignore zeroed buckets<br />	      (bits==null || bits.get(doc))) {	  // skip docs not in bits<br />	    totalHits[0]++;<br />	    if (score &gt;= minScore) {<br />              /* 原先：Lucene将docID和相应的匹配度score例入结果命中列表中：<br />	       * hq.put(new ScoreDoc(doc, score));	  // update hit queue<br />               * 如果用doc 或 1/doc 代替 score，就实现了根据docID顺排或逆排<br />               * 假设数据源索引时已经按照某个字段排好了序，而结果根据docID排序也就实现了<br />               * 针对某个字段的排序，甚至可以实现更复杂的score和docID的拟合。<br />               */<br />              hq.put(new ScoreDoc(doc, (float) 1/doc )); <br />	      if (hq.size() &gt; nDocs) {		  // if hit queue overfull<br />		hq.pop();			  // remove lowest in hit queue<br />		minScore = ((ScoreDoc)hq.top()).score; // reset minScore<br />	      }<br />	    }<br />	  }<br />	}<br />      }, reader.maxDoc());</pre><p><b>更通用的输入输出接口</b></p><p>虽然lucene没有定义一个确定的输入文档格式，但越来越多的人想到使用一个标准的中间格式作为Lucene的数据导入接口，然后其他数据，比如PDF只需要通过解析器转换成标准的中间格式就可以进行数据索引了。这个中间格式主要以XML为主，类似实现已经不下4，5个：</p><pre>数据源: WORD       PDF     HTML    DB       other<br />         \          |       |      |         /<br />                       XML中间格式<br />                            |<br />                     Lucene INDEX</pre><p>目前还没有针对MSWord文档的解析器，因为Word文档和基于ASCII的RTF文档不同，需要使用COM对象机制解析。这个是我在Google上查的相关资料：<a href="http://www.intrinsyc.com/products/enterprise_applications.asp">http://www.intrinsyc.com/products/enterprise_applications.asp</a><br />另外一个办法就是把Word文档转换成text：<a href="http://www.winfield.demon.nl/index.html">http://www.winfield.demon.nl/index.html</a><br /></p><p><br /><b>索引过程优化</b></p><p>索引一般分2种情况，一种是小批量的索引扩展，一种是大批量的索引重建。在索引过程中，并不是每次新的DOC加入进去索引都重新进行一次索引文件的写入操作（文件I/O是一件非常消耗资源的事情）。</p><p>Lucene先在内存中进行索引操作，并根据一定的批量进行文件的写入。这个批次的间隔越大，文件的写入次数越少，但占用内存会很多。反之占用内存少，但文件IO操作频繁，索引速度会很慢。在IndexWriter中有一个MERGE_FACTOR参数可以帮助你在构造索引器后根据应用环境的情况充分利用内存减少文件的操作。根据我的使用经验：缺省Indexer是每20条记录索引后写入一次，每将MERGE_FACTOR增加50倍，索引速度可以提高1倍左右。<br /></p><p><span style="FONT-WEIGHT: bold">搜索过程优化<br /></span></p><p><span style="FONT-WEIGHT: bold"></span>lucene支持内存索引：这样的搜索比基于文件的I/O有数量级的速度提升。<br /><a href="http://www.onjava.com/lpt/a/3273">http://www.onjava.com/lpt/a/3273</a><br />而尽可能减少IndexSearcher的创建和对搜索结果的前台的缓存也是必要的。<br /><span style="FONT-WEIGHT: bold"></span></p><p><b></b></p><p>Lucene面向全文检索的优化在于首次索引检索后，并不把所有的记录（Document）具体内容读取出来，而起只将所有结果中匹配度最高的头100条结果（TopDocs）的ID放到结果集缓存中并返回，这里可以比较一下数据库检索：如果是一个10,000条的数据库检索结果集，数据库是一定要把所有记录内容都取得以后再开始返回给应用结果集的。所以即使检索匹配总数很多，Lucene的结果集占用的内存空间也不会很多。对于一般的模糊检索应用是用不到这么多的结果的，头100条已经可以满足90%以上的检索需求。<br /></p><p>如果首批缓存结果数用完后还要读取更后面的结果时Searcher会再次检索并生成一个上次的搜索缓存数大1倍的缓存，并再重新向后抓取。所以如果构造一个Searcher去查1－120条结果，Searcher其实是进行了2次搜索过程：头100条取完后，缓存结果用完，Searcher重新检索再构造一个200条的结果缓存，依此类推，400条缓存，800条缓存。由于每次Searcher对象消失后，这些缓存也访问那不到了，你有可能想将结果记录缓存下来，缓存数尽量保证在100以下以充分利用首次的结果缓存，不让Lucene浪费多次检索，而且可以分级进行结果缓存。<br /></p><p>Lucene的另外一个特点是在收集结果的过程中将匹配度低的结果自动过滤掉了。这也是和数据库应用需要将搜索的结果全部返回不同之处。</p><p><a href="http://sourceforge.net/projects/weblucene/">我的一些尝试</a>：</p><ul><li>支持中文的Tokenizer：这里有2个版本，一个是通过JavaCC生成的，对CJK部分按一个字符一个TOKEN索引，另外一个是从SimpleTokenizer改写的，对英文支持数字和字母TOKEN，对中文按迭代索引。 
</li><li>基于XML数据源的索引器：XMLIndexer，因此所有数据源只要能够按照DTD转换成指定的XML，就可以用XMLIndxer进行索引了。 
</li><li>根据某个字段排序：按记录索引顺序排序结果的搜索器：IndexOrderSearcher，因此如果需要让搜索结果根据某个字段排序，可以让数据源先按某个字段排好序（比如：PriceField），这样索引后，然后在利用这个按记录的ID顺序检索的搜索器，结果就是相当于是那个字段排序的结果了。 </li></ul><p><a name="learn"><b>从Lucene学到更多</b></a></p><p>Luene的确是一个面对对象设计的典范</p><ul><li>所有的问题都通过一个额外抽象层来方便以后的扩展和重用：你可以通过重新实现来达到自己的目的，而对其他模块而不需要； 
</li><li>简单的应用入口Searcher, Indexer，并调用底层一系列组件协同的完成搜索任务； 
</li><li>所有的对象的任务都非常专一：比如搜索过程：QueryParser分析将查询语句转换成一系列的精确查询的组合(Query),通过底层的索引读取结构IndexReader进行索引的读取，并用相应的打分器给搜索结果进行打分/排序等。所有的功能模块原子化程度非常高，因此可以通过重新实现而不需要修改其他模块。  
</li><li>除了灵活的应用接口设计，Lucene还提供了一些适合大多数应用的语言分析器实现（SimpleAnalyser,StandardAnalyser），这也是新用户能够很快上手的重要原因之一。 </li></ul><p>这些优点都是非常值得在以后的开发中学习借鉴的。作为一个通用工具包，Lunece的确给予了需要将全文检索功能嵌入到应用中的开发者很多的便利。</p><p>此外，通过对Lucene的学习和使用，我也更深刻地理解了为什么很多数据库优化设计中要求，比如：</p><ul><li>尽可能对字段进行索引来提高查询速度，但过多的索引会对数据库表的更新操作变慢，而对结果过多的排序条件，实际上往往也是性能的杀手之一。 
</li><li>很多商业数据库对大批量的数据插入操作会提供一些优化参数，这个作用和索引器的merge_factor的作用是类似的， 
</li><li>20%/80%原则：查的结果多并不等于质量好，尤其对于返回结果集很大，如何优化这头几十条结果的质量往往才是最重要的。 
</li><li>尽可能让应用从数据库中获得比较小的结果集，因为即使对于大型数据库，对结果集的随机访问也是一个非常消耗资源的操作。<br /></li></ul><p>参考资料：</p><p>Apache: Lucene Project<br /><a href="http://jakarta.apache.org/lucene/">http://jakarta.apache.org/lucene/<br /></a>Lucene开发/用户邮件列表归档<br /><a href="http://www.mail-archive.com/lucene-dev@jakarta.apache.org/">Lucene-dev@jakarta.apache.org</a><br /><a href="http://www.mail-archive.com/lucene-user@jakarta.apache.org/">Lucene-user@jakarta.apache.org</a></p><p>The Lucene search engine: Powerful, flexible, and free<br /><a href="http://www.javaworld.com/javaworld/jw-09-2000/jw-0915-lucene_p.html">http://www.javaworld.com/javaworld/jw-09-2000/jw-0915-Lucene_p.html</a></p><p>Lucene Tutorial<br /><a href="http://www.darksleep.com/puff/lucene/lucene.html">http://www.darksleep.com/puff/lucene/lucene.html</a></p><p>Notes on distributed searching with Lucene<br /><a href="http://home.clara.net/markharwood/lucene/">http://home.clara.net/markharwood/lucene/</a></p><p>中文语言的切分词<br /><a href="http://www.google.com/search?sourceid=navclient&amp;hl=zh-CN&amp;q=chinese+word+segment">http://www.google.com/search?sourceid=navclient&amp;hl=zh-CN&amp;q=chinese+word+segment</a></p><p>搜索引擎工具介绍<a href="http://searchtools.com/"><br />http://searchtools.com/</a></p><p>Lucene作者Cutting的几篇论文和专利<br /><a href="http://lucene.sourceforge.net/publications.html">http://lucene.sourceforge.net/publications.html</a> </p><p>Lucene的.NET实现：dotLucene<br /><a href="http://sourceforge.net/projects/dotlucene/">http://sourceforge.net/projects/dotlucene/<br /></a></p><p>Lucene作者Cutting的另外一个项目：基于Java的搜索引擎Nutch<br /><a href="http://www.nutch.org/">http://www.nutch.org/</a>   <a href="http://sourceforge.net/projects/nutch/">http://sourceforge.net/projects/nutch/<br /></a></p><p>关于基于词表和N-Gram的切分词比较<br /><a href="http://china.nikkeibp.co.jp/cgi-bin/china/news/int/int200302100112.html">http://china.nikkeibp.co.jp/cgi-bin/china/news/int/int200302100112.html</a><br /><br />2005-01-08 <a href="http://lucene.sourceforge.net/talks/pisa/">Cutting在Pisa大学做的关于Lucene的讲座：非常详细的Lucene架构解说</a></p><p>特别感谢：<br /><a href="http://www.google.com/search?q=%22Jack+Xu%22+Excite">前网易CTO许良杰(Jack Xu)</a>给我的指导：是您将我带入了搜索引擎这个行业。</p>原文出处：&lt;ahref="http://www.chedong.com/tech/lucene.html"&gt;http://www.chedong.com/tech/lucene.html&lt;/a&gt;<br /><br />from: <img src ="http://www.blogjava.net/weidagang2046/aggbug/87684.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-12-14 13:05 <a href="http://www.blogjava.net/weidagang2046/articles/87684.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深入浅出理解索引结构 </title><link>http://www.blogjava.net/weidagang2046/articles/86464.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 08 Dec 2006 14:01:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/86464.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/86464.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/86464.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/86464.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/86464.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: （一）深入浅出理解索引结构																								        实际上，您可以把索引理解为一种特殊的目录。微软的						SQL SERVER						提供了两种索引：聚集索引（						clustered index						，也称聚类索引、簇集索引）和非聚集索引（						nonclustered index	...&nbsp;&nbsp;<a href='http://www.blogjava.net/weidagang2046/articles/86464.html'>阅读全文</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/86464.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-12-08 22:01 <a href="http://www.blogjava.net/weidagang2046/articles/86464.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>提高查询速度方法总结 </title><link>http://www.blogjava.net/weidagang2046/articles/86461.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 08 Dec 2006 13:55:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/86461.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/86461.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/86461.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/86461.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/86461.html</trackback:ping><description><![CDATA[
		<p>这个帖子主要总结提高查询速度的方法，涉及到减少连接数据库次数、建立索引、优化语句等方面。</p>
		<p>关于索引，推荐转载的这篇文章<br /><a href="http://blog.csdn.net/dutguoyi/archive/2006/01/10/575617.aspx">http://blog.csdn.net/dutguoyi/archive/2006/01/10/575617.aspx</a></p>
		<p>改善SQL语句的效率<br /><a href="http://community.csdn.net/Expert/topic/5087/5087396.xml?temp=.345669">http://community.csdn.net/Expert/topic/5087/5087396.xml?temp=.345669</a><br />数据量很大怎样加快索检速度<br /><a href="http://community.csdn.net/Expert/topic/5058/5058320.xml?temp=.1229517">http://community.csdn.net/Expert/topic/5058/5058320.xml?temp=.1229517</a><br />索引建立方法的区别<br /><a href="http://community.csdn.net/Expert/topic/5068/5068154.xml?temp=.3010218">http://community.csdn.net/Expert/topic/5068/5068154.xml?temp=.3010218</a><br />频繁插入删除数据需要更新索引<br /><a href="http://community.csdn.net/Expert/topic/4937/4937910.xml?temp=.8428614">http://community.csdn.net/Expert/topic/4937/4937910.xml?temp=.8428614</a><br />测试了一下sql server 2005 全文检索<br /><a href="http://community.csdn.net/Expert/topic/4878/4878430.xml?temp=.6049311">http://community.csdn.net/Expert/topic/4878/4878430.xml?temp=.6049311</a></p>
		<p>其他关于效率的高频问题</p>
		<p>判断一个表的数据不在另一个表中最优秀方法？<br /><a href="http://community.csdn.net/Expert/topic/5038/5038742.xml?temp=.4704553">http://community.csdn.net/Expert/topic/5038/5038742.xml?temp=.4704553</a><br />删除千万级表中重复记录的办法<br /><a href="http://community.csdn.net/Expert/topic/5089/5089261.xml?temp=.7907068">http://community.csdn.net/Expert/topic/5089/5089261.xml?temp=.7907068</a></p>
		<p>数据库数据查询变得不正常类型问题</p>
		<p>大数据量，稳定运行一段时候以后无法得到查询结果。<br /><a href="http://community.csdn.net/Expert/topic/4810/4810464.xml?temp=9.014529E-02">http://community.csdn.net/Expert/topic/4810/4810464.xml?temp=9.014529E-02</a><br /><br />from: <a href="/zqli/archive/2006/12/08/86391.html">http://www.blogjava.net/zqli/archive/2006/12/08/86391.html</a></p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/86461.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-12-08 21:55 <a href="http://www.blogjava.net/weidagang2046/articles/86461.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>编写高性能Web应用程序的10个入门技巧</title><link>http://www.blogjava.net/weidagang2046/articles/85980.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Wed, 06 Dec 2006 15:57:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/85980.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/85980.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/85980.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/85980.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/85980.html</trackback:ping><description><![CDATA[
		<p>编写高性能Web应用程序的10个入门技巧<br /><br />  数据层性能 <br />  技巧 1 — 返回多个结果集 <br />  技巧 2 — 分页的数据访问 <br />  技巧 3 — 连接池 <br />  技巧 4 — ASP.NET 缓存 API <br />  技巧 5 — 每请求缓存 <br />  技巧 6 — 后台处理 <br />  技巧 7 — 页输出缓存和代理<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a><br />  技巧 8 — 运行 IIS 6.0（只要用于内核缓存） <br />  技巧 9 — 使用 Gzip 压缩 <br />  技巧 10 — <a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>控件视图状态 <br />  <br /><br />使用 ASP.NET 编写 Web 应用程序的简单程度令人不敢相信。正因为如此简单，所以很多<br />开发人员就不会花时间来设计其应用程序的结构，以获得更好的性能了。在本文中，我将<br />讲述 10 个用于编写高性能 Web 应用程序的技巧。但是我并不会将这些建议仅局限于 <br />ASP.NET 应用程序，因为这些应用程序只是 Web 应用程序的一部分。本文不作为对 Web <br />应用程序进行性能调整的权威性指南 — 一整本书恐怕都无法轻松讲清楚这个问题。请将<br />本文视作一个很好的起点。 <br /><br />成为工作狂之前，我原来喜欢攀岩。在进行任何大型攀岩活动之前，我都会首先仔细查看<br />指南中的路线，阅读以前游客提出的建议。但是，无论指南怎么好，您都需要真正的攀岩<br />体验，然后才能尝试一个特别具有挑战性的攀登。与之相似，当您面临修复性能问题或者<br />运行一个高吞吐量站点的问题时，您只能学习如何编写高性能 Web 应用程序。<br /><br />我的个人体验来自在 Microsoft 的 ASP.NET 部门作为基础架构程序经理的经验，在此期<br />间我运行和管理 www.ASP.NET，帮助设计社区<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>的结构，社区<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>是几个著名 <br />ASP.NET 应用程序（组合到一个平台的 ASP.NET Forums、.Text 和 nGallery）。我确信<br />有些曾经帮助过我的技巧对您肯定也会有所帮助。<br /><br />您应该考虑将应用程序分为几个逻辑层。您可能听说过 3 层（或者 n 层）物理体系结构<br />一词。这些通常都是规定好的体系结构方式，将功能在进程和/或硬件之间进行了物理分离<br />。当系统需要扩大时，可以很轻松地添加更多的硬件。但是会出现一个与进程和机器跳跃<br />相关的性能下降，因此应该避免。所以，如果可能的话，请尽量在同一个应用程序中一起<br />运行 ASP.NET 页及其相关组件。<br /><br />因为代码分离以及层之间的边界，所以使用 Web 服务或远程处理将会使得性能下降 20% <br />甚至更多。<br /><br />数据层有点与众不同，因为通常情况下，最好具有专用于<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>的硬件。然而进程跳跃到<br /><a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>的成本依然很高，因此数据层的性能是您在优化代码时首先要考虑的问题。<br /><br />在深入应用程序的性能修复问题之前，请首先确保对应用程序进行剖析，以便找出具体的<br />问题所在。主要性能计数器（如表示执行垃圾回收所需时间百分比的计数器）对于找出应<br />用程序在哪些位置花费了其主要时间也非常有用。然而花费时间的位置通常非常不直观。<br /><br />本文讲述了两种类型的性能改善：大型优化（如使用 ASP.NET 缓存），和进行自身重复的<br />小型优化。这些小型优化有时特别有意思。您对代码进行一点小小的更改，就会获得很多<br />很多时间。使用大型优化，您可能会看到整体性能的较大飞跃。而使用小型优化时，对于<br />某个特定请求可能只会节省几毫秒的时间，但是每天所有请求加起来，则可能会产生巨大<br />的改善。<br /><br />数据层性能<br /><br /><br />谈到应用程序的性能调整，有一个试纸性的测试可用来对工作进行优先级划分：代码是否<br />访问<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>？如果是，频率是怎样的？请注意，这一相同测试也可应用于使用 Web 服务或<br />远程处理的代码，但是本文对这些内容未做讲述。<br /><br />如果某个特定的代码路径中必需进行<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>请求，并且您认为要首先优化其他领域（如字<br />符串操作），则请停止，然后执行这个试纸性测试。如果您的性能问题不是非常严重的话<br />，最好花一些时间来优化一下与<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>、返回的数据量、进出<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>的往返频率相关的花<br />费时间。<br /><br />了解这些常规信息之后，我们来看一下可能会有助于提高应用程序性能的十个技巧。首先<br />，我要讲述可能会引起最大改观的更改。<br /><br /><br />技巧 1 — 返回多个结果集<br /><br /><br />仔细查看您的<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>代码，看是否存在多次进入<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>的请求路径。每个这样的往返都会<br />降低应用程序可以提供的每秒请求数量。通过在一个<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>请求中返回多个结果集，可以<br />节省与<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>进行通信所需的总时间长度。同时因为减少了<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a><a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>管理请求的工作<br />，还会使得系统伸缩性更强。<br /><br />虽然可以使用动态 SQL 返回多个结果集，但是我首选使用存储过程。关于业务逻辑是否应<br />该驻留于存储过程的问题还存在一些争议，但是我认为，如果存储过程中的逻辑可以约束<br />返回数据的话（缩小数据集的大小、缩短网络上所花费时间，不必筛选逻辑层的数据），<br />则应赞成这样做。<br /><br />使用 SqlCommand 实例及其 ExecuteReader 方法填充强类型的业务类时，可以通过调用 <br />NextResult 将结果集指针向前移动。图 1 显示了使用类型类填充几个 ArrayList 的示例<br />会话。只从<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>返回您需要的数据将进一步减少<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>上的内存分配。 <br /><br />Figure 1 Extracting Multiple Resultsets from a DataReader<br />// read the first resultset<br />reader = command.ExecuteReader();<br /><br />// read the data from that resultset<br />while (reader.Read()) {<br />    suppliers.Add(PopulateSupplierFromIDataReader( reader ));<br />}<br /><br />// read the next resultset<br />reader.NextResult();<br /><br />// read the data from that second resultset<br />while (reader.Read()) {<br />    products.Add(PopulateProductFromIDataReader( reader ));<br />}<br /><br /><br />技巧 2 — 分页的数据访问<br /><br /><br />ASP.NET DataGrid 具有一个很好的功能：数据分页支持。在 DataGrid 中启用分页时，一<br />次会显示固定数量的记录。另外，在 DataGrid 的底部还会显示分页 UI，以便在记录之间<br />进行导航。该分页 UI 使您能够在所显示的数据之间向前和向后导航，并且一次显示固定<br />数量的记录。<br /><br />还有一个小小的波折。使用 DataGrid 的分页需要所有数据均与网格进行绑定。例如，您<br />的数据层需要返回所有数据，那么 DataGrid 就会基于当前页筛选显示的所有记录。如果<br />通过 DataGrid 进行分页时返回了 100,000 个记录，那么针对每个请求会放弃 99,975 个<br />记录（假设每页大小为 25 个记录）。当记录的数量不断增加时，应用程序的性能就会受<br />到影响，因为针对每个请求必须发送越来越多的数据。<br /><br />要编写性能更好的分页代码，一个极佳的方式是使用存储过程。图 2 显示了针对 <br />Northwind <a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>中的 Orders 表进行分页的一个示例存储过程。简而言之，您此时要做<br />的只是传递页索引和页大小。然后就会计算合适的结果集，并将其返回。 <br /><br />Figure 2 Paging Through the Orders Table<br />CREATE PROCEDURE northwind_OrdersPaged<br />(<br />    @PageIndex int, <br />    @PageSize int<br />)<br />AS<br />BEGIN<br />DECLARE @PageLowerBound int<br />DECLARE @PageUpperBound int<br />DECLARE @RowsToReturn int<br /><br />-- First set the rowcount<br />SET @RowsToReturn = @PageSize * (@PageIndex + 1)<br />SET ROWCOUNT @RowsToReturn<br /><br />-- Set the page bounds<br />SET @PageLowerBound = @PageSize * @PageIndex<br />SET @PageUpperBound = @PageLowerBound + @PageSize + 1<br /><br />-- Create a temp table to store the select results<br />CREATE TABLE #PageIndex <br />(<br />    IndexId int IDENTITY (1, 1) NOT NULL,<br />    OrderID int<br />)<br /><br />-- Insert into the temp table<br />INSERT INTO #PageIndex (OrderID)<br />SELECT <br />    OrderID<br />FROM <br />    Orders<br />ORDER BY <br />    OrderID DESC<br /><br />-- Return total count<br />SELECT COUNT(OrderID) FROM Orders<br /><br />-- Return paged results<br />SELECT <br />    O.*<br />FROM <br />    Orders O,<br />    #PageIndex PageIndex<br />WHERE <br />    O.OrderID = PageIndex.OrderID AND<br />    PageIndex.IndexID &gt; @PageLowerBound AND<br />    PageIndex.IndexID &lt; @PageUpperBound<br />ORDER BY <br />    PageIndex.IndexID<br /><br />END<br /><br /><br /><br />在社区<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>中，我们编写了一个分页<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>控件，以完成所有的数据分页。您将会看到<br />，我使用的就是技巧 1 中讨论的理念，从一个存储过程返回两个结果集：记录的总数和请<br />求的数据。<br /><br />返回记录的总数可能会根据所执行查询的不同而有所变化。例如，WHERE 子句可用来约束<br />返回的数据。为了计算在分页 UI 中显示的总页数，必须了解要返回记录的总数。例如，<br />如果总共有 1,000,000 条记录，并且要使用一个 WHERE 子句将其筛选为 1000 条记录，<br />那么分页逻辑就需要了解记录的总数才能正确呈现分页 UI。<br /><br /><br />技巧 3 — 连接池<br /><br /><br />在 Web 应用程序和 <a href="http://www.chinahtml.com/databases/2/" target="_blank">SQL Server</a>? 之间设置 TCP 连接可能是一个非常消耗资源的操作。Mi<br />crosoft 的开发人员到目前为止能够使用连接池已经有一段时间了，这使得他们能够重用<br /><a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>连接。他们不是针对每个请求都设置一个新的 TCP 连接，而是只在连接池中没有任<br />何连接时才设置新连接。当连接关闭时，它会返回连接池，在其中它会保持与<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>的连<br />接，而不是完全破坏该 TCP 连接。<br /><br />当然，您需要小心是否会出现泄漏连接。当您完成使用连接时，请一定要关闭这些连接。<br />再重复一遍：无论任何人对 Microsoft?.NET Framework 中的垃圾回收有什么评论，请一<br />定要在完成使用连接时针对该连接显式调用 Close 或 Dispose。不要相信公共语言运行库<br />(CLR) 会在预先确定的时间为您清除和关闭连接。尽管 CLR 最终会破坏该类，并强制连<br />接关闭，但是当针对对象的垃圾回收真正发生时，并不能保证。 <br /><br />要以最优化的方式使用连接池，需要遵守一些规则。首先打开连接，执行操作，然后关闭<br />该连接。如果您必须如此的话，可以针对每个请求多次打开和关闭连接（最好应用技巧 1<br />），但是不要一直将连接保持打开状态并使用各种不同的方法对其进行进出传递。第二，<br />使用相同的连接字符串（如果使用集成身份验证的话，还要使用相同的线程标识）。如果<br />不使用相同的连接字符串，例如根据登录的用户自定义连接字符串，那么您将无法得到连<br />接池提供的同一个优化值。如果您使用集成身份验证，同时还要模拟大量用户，连接池的<br />效率也会大大下降。尝试跟踪与连接池相关的任何性能问题时，.NET CLR 数据性能计数器<br />可能非常有用。 <br /><br />每当应用程序连接资源时，如在另一个进程中运行的<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>，您都应该重点考虑连接该资<br />源所花时间、发送或检索数据所花时间，以及往返的数量，从而进行优化。优化应用程序<br />中任何种类的进程跳跃都是获得更佳性能的首要一点。<br /><br />应用层包含了连接数据层、将数据转换为有意义类实例和业务流程的逻辑。例如社区服务<br />器，您要在其中填充Forums 或 Threads集合，应用业务规则（如权限）；最重要的是要在<br />其中执行缓存逻辑。<br /><br /><br />技巧 4 — ASP.NET 缓存 API<br /><br /><br />编写应用程序代码行之前，一个首要完成的操作是设计应用层的结构，以便最大化利用 <br />ASP.NET 缓存功能。<br /><br />如果您的组件要在 ASP.NET 应用程序中运行，则只需在该应用程序项目中包括一个 <br />System.Web.dll 引用。当您需要访问该缓存时，请使用 HttpRuntime.Cache 属性（通过 <br />Page.Cache 和 HttpContext.Cache 也可访问这个对象）。<br /><br />对于缓存数据，有几个规则。首先，如果数据可能会多次使用时，则这是使用缓存的一个<br />很好的备选情况。第二，如果数据是通用的，而不特定于某个具体的请求或用户时，则也<br />是使用缓存的一个很好的备选情况。如果数据是特定于用户或请求的，但是寿命较长的话<br />，仍然可以对其进行缓存，但是这种情况可能并不经常使用。第三，一个经常被忽略的规<br />则是，有时可能您缓存得太多。通常在一个 x86 计算机上，为了减少内存不足错误出现的<br />机会，您会想使用不高于 800MB 的专用字节运行进程。因此缓存应该有个限度。换句话说<br />，您可能能够重用某个计算结果，但是如果该计算采用 10 个参数的话，您可能要尝试缓<br />存 10 个排列，这样有可能给您带来麻烦。一个要求 ASP.NET 的最常见支持是由于过度缓<br />存引起的内存不足错误，尤其是对于大型数据集。<br /><br /><br />缓存有几个极佳的功能，您需要对它们有所了解。首先，缓存会实现最近最少使用的算法<br />，使得 ASP.NET 能够在内存运行效率较低的情况下强制缓存清除 － 从缓存自动删除未使<br />用过的项目。第二，缓存支持可以强制失效的过期依赖项。这些依赖项包括时间、密钥和<br />文件。时间经常会用到，但是对于 ASP.NET 2.0，引入了一个功能更强的新失效类型：数<br />据库缓存失效。它指的是当<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a>中的数据发生变化时自动删除缓存中的项。有关<a href="http://www.chinahtml.com/databases/" target="_blank">数据库</a><br />缓存失效的详细信息，请参阅 MSDN?Magazine 2004 年 7 月的 Dino Esposito Cutting <br />Edge 专栏。要了解缓存的体系结构，请参阅图 3。<br /><br />技巧 5 — 每请求缓存<br /><br />在本文前面部分，我提到了经常遍历代码路径的一些小改善可能会导致较大的整体性能收<br />益。对于这些小改善，其中有一个绝对是我的最爱，我将其称之为"每请求缓存"。<br /><br />缓存 API 的设计目的是为了将数据缓存较长的一段时间，或者缓存至满足某些条件时，但<br />每请求缓存则意味着只将数据缓存为该请求的持续时间。对于每个请求，要经常访问某个<br />特定的代码路径，但是数据却只需提取、应用、修改或更新一次。这听起来有些理论化，<br />那么我们来举一个具体的示例。<br /><br />在社区<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>的论坛应用程序中，页面上使用的每个<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>控件都需要个性化的数据来确<br />定使用什么外观、使用什么样式表，以及其他个性化数据。这些数据中有些可以长期缓存<br />，但是有些数据却只针对每个请求提取一次，然后在执行该请求期间对其重用多次，如要<br />用于控件的外观。<br /><br />为了达到每请求缓存，请使用 ASP.NET HttpContext。对于每个请求，都会创建一个 <br />HttpContext 实例，在该请求期间从 HttpContext.Current 属性的任何位置都可访问该实<br />例。该 HttpContext 类具有一个特殊的 Items 集合属性；添加到此 Items 集合的对象和<br />数据只在该请求持续期间内进行缓存。正如您可以使用缓存来存储经常访问的数据一样，<br />您也可以使用 HttpContext.Items 来存储只基于每个请求使用的数据。它背后的逻辑非常<br />简单：数据在它不存在的时候添加到 HttpContext.Items 集合，在后来的查找中，只是返<br />回 HttpContext.Items 中的数据。<br /><br /><br />技巧 6 — 后台处理<br /><br /><br />通往代码的路径应该尽可能快速，是吗？可能有时您会觉得针对每个请求执行的或者每 <br />n 个请求执行一次的任务所需资源非常多。发送电子邮件或者分析和验证传入数据就是这<br />样的一些例子。<br /><br />剖析 ASP.NET Forums 1.0 并重新构建组成社区<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>的内容时，我们发现添加新张贴的<br />代码路径非常慢。每次添加新张贴时，应用程序首先需要确保没有重复的张贴，然后必须<br />使用"坏词"筛选器分析该张贴，分析张贴的字符图释，对张贴添加标记并进行索引，请求<br />时将张贴添加到合适的队列，验证附件，最终张贴之后，立即向所有订阅者发出电子邮件<br />通知。很清楚，这涉及很多操作。<br /><br />经研究发现，大多数时间都花在了索引逻辑和发送电子邮件上。对张贴进行索引是一个非<br />常耗时的操作，人们发现内置的 System.Web.Mail 功能要连接 SMYP <a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>，然后连续发<br />送电子邮件。当某个特定张贴或主题领域的订阅者数量增加时，执行 AddPost 功能所需的<br />时间也越来越长。<br /><br />并不需要针对每个请求都进行电子邮件索引。理想情况下，我们想要将此操作进行批处理<br />，一次索引 25 个张贴或者每五分钟发送一次所有电子邮件。我们决定使用以前用于对数<br />据缓存失效进行原型设计的代码，这个失效是用于最终进入 Visual Studio? 2005 的内容<br />的。<br /><br />System.Threading 命名空间中的 Timer 类非常有用，但是在 .NET Framework 中不是很<br />有名，至少对于 Web 开发人员来说是这样。创建之后，这个 Timer 类将以一个可配置的<br />间隔针对 ThreadPool 中的某个线程调用指定的回调。这就表示，您可以对代码进行设置<br />，使其能够在没有对 ASP.NET 应用程序进行传入请求的情况下得以执行，这是后台处理的<br />理想情况。您还可以在此后台进程中执行如索引或发送电子邮件之类的操作。 <br /><br />但是，这一技术有几个问题。如果应用程序域卸载，该计时器实例将停止触发其事件。另<br />外，因为 CLR 对于每个进程的线程数量具有一个硬性标准，所以可能会出现这样的情形：<br /><a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>负载很重，其中计时器可能没有可在其基础上得以完成的线程，在某种程度上可能<br />会造成延迟。ASP.NET 通过在进程中保留一定数量的可用线程，并且仅使用总线程的一部<br />分用于请求处理，试图将上述情况发生的机会降到最低。但是，如果您具有很多异步操作<br />时，这可能就是一个问题了。 <br /><br />这里没有足够的空间来放置该代码，但是您可以下载一个可以看懂的示例，网址是 <br />www.rob-howard.net。请了解一下 Blackbelt TechEd 2004 演示中的幻灯片和演示。<br /><br /><br />技巧 7 — 页输出缓存和代理<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a><br /><br /><br />ASP.NET 是您的表示层（或者说应该是您的表示层）；它由页、用户控件、<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>控件（H<br />ttpHandlers 和 HttpModules）以及它们生成的内容组成。如果您具有一个 ASP.NET 页，<br />它会生成输出（HTML、XML、图像或任何其他数据），并且您针对每个请求运行此代码时，<br />它都会生成相同的输出，那么您就拥有一个可用于页输出缓存的绝佳备选内容。 <br /><br />将此行内容添加页的最上端 <br /><br />&lt;%@ Page OutputCache VaryByParams="none" Duration="60" %&gt; <br /><br />就可以高效地为此页生成一次输出，然后对它进行多次重用，时间最长为 60 秒，此时该<br />页将重新执行，输出也将再一次添加到 ASP.NET 缓存。通过使用一些低级程序化 API 也<br />可以完成此行为。对于输出缓存有几个可配置的设置，如刚刚讲到的 VaryByParams 属性<br />。VaryByParams 刚好被请求到，但还允许您指定 HTTP GET 或 HTTP POST 参数来更改缓<br />存项。例如，只需设置 VaryByParam="Report" 即可对 default.aspx?Report=1 或 <br />default.aspx?Report=2 进行输出缓存。通过指定一个以分号分隔的列表，还可以指定其<br />他参数。 <br /><br />很多人都不知道何时使用输出缓存，ASP.NET 页还会生成一些位于缓存<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>下游的 <br />HTTP 标头，如 Microsoft Internet Security and Acceleration Server 或 Akamai 使<br />用的标头。设置了 HTTP 缓存标头之后，可以在这些网络资源上对文档进行缓存，客户端<br />请求也可在不必返回原始<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>的情况下得以满足。<br /><br />因此，使用页输出缓存不会使得您的应用程序效率更高，但是它可能会减少<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>上的负<br />载，因为下游缓存技术会缓存文档。当然，这可能只是匿名内容；一旦它成为下游之后，<br />您就再也不会看到这些请求，并且再也无法执行身份验证以阻止对它的访问了。<br /><br /><br />技巧 8 — 运行 IIS 6.0（只要用于内核缓存）<br /><br /><br />如果您未运行 IIS 6.0 (<a href="http://www.chinahtml.com/systems/2/" target="_blank">Windows</a> Server? 2003)，那么您就错过了 Microsoft Web 服务<br />器中的一些很好的性能增强。在技巧 7 中，我讨论了输出缓存。在 IIS 5.0 中，请求是<br />通过 IIS 然后进入 ASP.NET 的。涉及到缓存时，ASP.NET 中的 HttpModule 会接收该请<br />求，并返回缓存中的内容。<br /><br />如果您正在使用 IIS 6.0，就会发现一个很好的小功能，称为内核缓存，它不需要对 <br />ASP.NET 进行任何代码更改。当请求由 ASP.NET 进行输出缓存时，IIS 内核缓存会接收缓<br />存数据的一个副本。当请求来自网络驱动程序时，内核级别的驱动程序（无上下文切换到<br />用户模式）就会接收该请求，如果经过了缓存，则会将缓存的数据刷新到响应，然后完成<br />执行。这就表示，当您将内核模式缓存与 IIS 和 ASP.NET 输出缓存一起使用时，就会看<br />到令人不敢相信的性能结果。在 ASP.NET 的 Visual Studio 2005 开发过程中，我一度是<br />负责 ASP.NET 性能的程序经理。开发人员完成具体工作，但是我要看到每天进行的所有报<br />告。内核模式缓存结果总是最有意思的。最常见的特征是网络充满了请求/响应，而 IIS <br />运行时的 CPU 使用率只有大约 5%。这太令人震惊了！当然使用 IIS 6.0 还有一些其他原<br />因，但是内核模式缓存是其中最明显的一个。<br /><br /><br />技巧 9 — 使用 Gzip 压缩<br /><br /><br />虽然使用 gzip 并不一定是<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>性能技巧（因为您可能会看到 CPU 使用率的提高），但<br />是使用 gzip 压缩可以减少<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>发送的字节数量。这就使人们觉得页速度加快了，并且<br />还减少了带宽的用量。根据所发送数据、可以压缩的程度以及客户端浏览器是否支持（IIS<br />只会向支持 gzip 压缩的客户端发送经过 gzip 压缩的内容，如 Internet Explorer <br />6.0 和 Firefox），您的<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>每秒可以服务于更多的请求。实际上，几乎每当您减少所<br />返回数据的数量时，都会增加每秒请求数。 <br /><br />Gzip 压缩已经内置到 IIS 6.0 中，并且其性能比 IIS 5.0 中使用的 gzip 压缩要好的多<br />，这是好消息。但不幸的是，当尝试在 IIS 6.0 中打开 gzip 压缩时，您可能无法在 <br />IIS 的属性对话中找到该设置。IIS 小组在该<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>中置入了卓越的 gzip 功能，但是忘<br />了包括一个用于启用该功能的管理 UI。要启用 gzip 压缩，您必须深入到 IIS 6.0 的 <br />XML 配置设置内部（这样不会引起心脏虚弱）。顺便提一句，这归功于 OrcsWeb 的 <br />Scott Forsyth，他帮助我提出了在 OrcsWeb 上宿主的 www.asp.net <a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>的这个问题。<br /><br /><br />本文就不讲述步骤了，请阅读 Brad Wilson 的文章，网址是 IIS6 Compression。还有一<br />篇有关为 ASPX 启用压缩的知识库文章，网址是 Enable ASPX Compression in IIS。但是<br />您应该注意，由于一些实施细节，IIS 6.0 中不能同时存在动态压缩和内核缓存。<br /><br /><br />技巧 10 — <a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>控件视图状态<br /><br /><br />视图状态是一个有趣的名称，用于表示在所生成页的隐藏输出字段中存储一些状态数据的 <br />ASP.NET。当该页张贴回<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>时，<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>可以分析、验证、并将此视图状态数据应用回该<br />页的控件树。视图状态是一个非常强大的功能，因为它允许状态与客户端一起保持，并且<br />它不需要 cookie 或<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>内存即可保存此状态。很多 ASP.NET <a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>控件都使用视图状<br />态来保持在与页元素进行交互期间创建的设置，例如保存对数据进行分页时显示的当前页<br />。 <br /><br />然而使用视图状态也有一些缺点。首先，服务或请求页时，它都会增加页的总负载。对张<br />贴回<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>的视图状态数据进行序列化或取消序列化时，也会发生额外的开销。最后，视<br />图状态会增加<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>上的内存分配。<br /><br />几个<a href="http://www.chinahtml.com/systems/" target="_blank">服务器</a>控件有着过度使用视图状态的趋势，即使在并不需要的情况下也要使用它，其<br />中最著名的是 DataGrid。ViewState 属性的默认行为是启用，但是如果您不需要，则可以<br />在控件或页级别关闭。在控件内，只需将 EnableViewState 属性设置为 false，或者在页<br />中使用下列设置即可对其进行全局设置： <br /><br />&lt;%@ Page EnableViewState="false" %&gt;<br /><br />如果您不回发页，或者总是针对每个请求重新生成页上的控件，则应该在页级别禁用视图<br />状态。 <br /><br /><br />我为您讲述了一些我认为在编写高性能 ASP.NET 应用程序时有所帮助的技巧。正如我在本<br />文前面部分提到的那样，这是一个初步指南，并不是 ASP.NET 性能的最后结果。（有关改<br />善 ASP.NET 应用程序性能的信息，请参阅 Improving ASP.NET Performance。）只有通过<br />自己的亲身体验才能找出解决具体性能问题的最好方法。但是，在您的旅程中，这些技巧<br />应该会为您提供一些好的指南。在软件开发中，几乎没有绝对的东西；每个应用程序都是<br />唯一的。<br /><br />from: <a href="http://www.chinahtml.com/programming/8/2006/11622676777784_2.shtml">http://www.chinahtml.com/programming/8/2006/11622676777784_2.shtml</a><br /></p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/85980.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-12-06 23:57 <a href="http://www.blogjava.net/weidagang2046/articles/85980.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux下postgresql数据库中汉字的插入</title><link>http://www.blogjava.net/weidagang2046/articles/85213.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 03 Dec 2006 11:55:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/85213.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/85213.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/85213.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/85213.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/85213.html</trackback:ping><description><![CDATA[首先用postgresql的用户登陆,(su - postgres) ,然后进入数据库,假设数据库名称为“house”,命令为 <br /><br />[postgres@ITC-S postgres]$ psql house<br />Welcome to psql 8.0.1, the PostgreSQL interactive terminal.<br /><p>Type:  copyright for distribution terms<br />　       h for help with SQL commands<br />　       ? for help with psql commands<br />　       g or terminate with semicolon to execute query<br />　       q to quit<br />house=# <br />输入encoding GBK<br />house=#set encoding GBK<br />然后就可以插入汉字了。<br /><br />from: <a href="http://publish.it168.com/2006/0219/20060219173801.shtml?positioncode=1547">http://publish.it168.com/2006/0219/20060219173801.shtml?positioncode=1547</a></p><img src ="http://www.blogjava.net/weidagang2046/aggbug/85213.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-12-03 19:55 <a href="http://www.blogjava.net/weidagang2046/articles/85213.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一劳永逸的数据库编码解决方案</title><link>http://www.blogjava.net/weidagang2046/articles/85178.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 03 Dec 2006 05:22:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/85178.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/85178.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/85178.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/85178.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/85178.html</trackback:ping><description><![CDATA[
		<p>　　<strong>问题提出</strong></p>
		<p>　　现在几乎所有的应用系统都无法避免使用数据库系统。在JAVA世界里访问数据库是一件非常轻松的事情，JDBC为JAVA应用程序访问数据库提供了一个统一的接口，通过使用JDBC接口开发者无需关心系统最终采用哪种数据库，因为JDBC仅仅是定义了访问几个JAVA的接口类，具体的实现是由数据库厂商提供的，这种做法其实与其他数据库连接方式例如ODBC是类似的。但是在实际的应用过程中，开发者发现离JDBC设计的初衷还是有一定距离，就比如说在存储字符串时的编码问题，我想很多开发者都会遇见这个问题，倒不是因为说解决它有什么技术方面的难度，而是它的的确确非常繁琐。我们必须在每次写入或者读出字符串的时候进行编码和反编码处理；或者说我们可以写一个方法可以进行编码处理的，但又必须在每次数据库操作的时候调用，虽然调用很简单，可是我非得这样吗？要是忘了编码那又要DEBUG了。当然你可能觉得这并没有什么，或者你可能很勤快，喜欢写大量重复的代码，可是你难道没有觉得这种繁琐的工作正在浪费你过于宝贵的青春吗？停止你的键盘输入，让我们来解决这个问题吧！</p>
		<p>　　<strong>解决思路</strong></p>
		<p>　　在传统的应用程序中数据库操作部分我们可以想象成两层，如图所示：一个是数据库的"连接池"，另外一个业务数据操作层。在这里数据库的连接池是广义的，你可以把JDBC中的DriverManager也当成是连接池，具体的意思就是我们可以通过这层来获取到指定数据库的连接而不去关心它是怎么获取的。如果这个时候数据库系统（有如Informix，SQL Server）要求对字符串进行转码才能存储（例如最常见的GBK-&gt;ISO8859_1转码），那我们就必须在业务数据操作层来进行，这样有多少业务数据操作我们就要做多少编码转码的工作，太麻烦了，代码中充斥中大量重复的内容。本文提出的解决方案就是利用对获取到的数据库连接实例进行二次封装，也就是在数据库连接池与业务数据操作层之间加入了连接封装层，当然了，我们也完全可以直接将连接封装集成到数据库连接池内部。关于连接池的实现请参照我的另外一篇文章《使用JAVA动态代理实现数据库连接池》</p>
		<p align="center">
				<img height="173" src="http://www.javafan.net/uploadfiles/20041212111952100.gif" width="247" border="0" />
				<br />图一</p>
		<p>　　我们知道进行编码和转码工作都是集中在JDBC的两个接口PreparedStatement和ResultSet上进行的，主要涉及PreparedStatement的setString方法以及ResultSet的getString方法。前面我们讲过需要加入一个连接封装层来对数据库连接实例进行二次封装，但是怎么通过这个封装来改变PreparedStatement和ResultSet这两个接口的行为呢？这个问题其实也很简单，因为PreparedStatement接口必须通过Connection接口来获取实例，而ResultSet接口又必须从Statement或者PreparedStatement接口来获取实例，有了这样的级联关系，问题也就迎刃而解了。还是利用我在文章《使用JAVA动态代理实现数据库连接池》中使用的动态接口代理技术。首先我们设计Connection接口的代理类_Connection，这个代理类接管了Connection接口中所有可能获取到Statement或者PreparedStatement接口实例的方法，例如：prepareStatement和createStatement。改变这两个方法使之返回的是经过接管后的Statement或者PreparedStatement实例。通过对于Statement接口也有相应的代理类_Statement，这个代理类接管用于获取ResultSet接口实例的所有方法，包括对setString方法的接管以决定是否对字符串进行编码处理。对于接口ResultSet的接管类_ResultSet就相应的比较简单，它只需要处理getString方法即可。</p>
		<p>　　<strong>关键代码</strong></p>
		<p>　　前面我们大概介绍了这个解决方案的思路，下面我们给出关键的实现代码包括Connection的代理类，Statement的代理类，ResultSet的代理类。这些代码是在原来关于数据库连接池实现的基础上进行扩充使之增加对自动编码处理的功能。有需要源码打包的可以通过电子邮件跟我联系。</p>
		<p>_Connection.java</p>
		<p style="BACKGROUND: #eeeeee">/* <br /> * Created on 2003-10-23 by Liudong <br /> */<br />package lius.pool;<br />import java.sql.*;<br />import java.lang.reflect.*;<br /><br />/*<br /> * <br /> * 数据库连接的代理类 <br /> * @author Liudong <br /> */<br /> class _Connection implements InvocationHandler{<br /> private Connection conn = null;<br /> private boolean coding = false;<br /> //指定是否进行字符串转码操作<br /> _Connection(Connection conn, boolean coding){<br />  this.conn = conn;<br />  this.coding = coding;<br />  initConnectionParam(this.conn);<br /> <br /> }<br /> <br /> /**  <br />  * Returns the conn.  <br />  * @return Connection  <br />  */<br />  <br /> public Connection getConnection() {<br />  Class[] interfaces = conn.getClass().getInterfaces();<br />  if(interfaces==null||interfaces.length==0){<br />   interfaces = new Class[1];<br />   interfaces[0] = Connection.class;<br />  <br />  }<br /> <br />  Connection conn2 = (Connection)Proxy.newProxyInstance( conn.getClass().getClassLoader(), interfaces,this);<br />  return conn2;<br /> <br /> }<br /> <br /> /**  <br />  * @see java.lang.reflect.InvocationHandler#invoke  <br />  */<br /> public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { <br />  String method = m.getName();<br />  //调用相应的操作<br />  Object obj = null;<br />  try{<br />   obj = m.invoke(conn, args);<br />   //接管用于获取语句句柄实例的方法<br />   if((CS.equals(method)||PS.equals(method))&amp;&amp;coding) <br />    return new _Statement((Statement)obj,true).getStatement();<br />  <br />  } catch(InvocationTargetException e) {<br />   throw e.getTargetException();<br />  }<br />  return obj;<br /> }<br /> <br /> private final static String PS = "prepareStatement";<br /> private final static String CS = "createStatement";<br />}</p>
		<p>
				<br />_Statement.java</p>
		<p style="BACKGROUND: #eeeeee">/* <br /> * Created on 2003-10-23 by Liudong <br /> */<br /> <br />package lius.pool;<br />import java.sql.*;<br />import java.lang.reflect.*;<br /><br />/** <br /> * 数据库语句对象实例的代理类 <br /> * @author Liudong <br /> */<br />class _Statement implements InvocationHandler{ <br /> private Statement statement ; //保存所接管对象的实例 <br /> private boolean decode = false; //指定是否进行字符串转码 <br /><br /> public _Statement(Statement stmt,boolean decode) { <br />  this.statement = stmt;<br />  this.decode = decode;<br /> }<br /> <br /> /**  <br />  * 获取一个接管后的对象实例  <br />  * @return  <br />  */<br /> public Statement getStatement() {<br />  Class[] interfaces = statement.getClass().getInterfaces();<br />  if(interfaces==null||interfaces.length==0){ <br />   interfaces = new Class[1];<br />   interfaces[0] = Statement.class;<br />  } <br />  Statement stmt = (Statement)Proxy.newProxyInstance(   <br />   statement.getClass().getClassLoader(), <br />   interfaces,this);<br />  return stmt;<br /> <br /> }<br /> <br /> /**  <br />  * 方法接管  <br />  */<br /> public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {<br />  String method = m.getName(); //接管setString方法 <br />  if(decode &amp;&amp; SETSTRING.equals(method)) {<br />   try{<br />    String param = (String)args[1];<br />    if(param!=null)<br />     param = new String(param.getBytes(),"8859_1");<br />    return m.invoke(statement,new Object[]{args[0],param});<br />   } catch(InvocationTargetException e){<br />    throw e.getTargetException();<br />        <br />   }  <br />  }<br />  <br />  //接管executeQuery方法<br />  <br />  if(decode &amp;&amp; EXECUTEQUERY.equals(method)){<br />   try{<br />    ResultSet rs = (ResultSet)m.invoke(statement,args);<br />    return new _ResultSet(rs,decode).getResultSet();<br />   <br />   }catch(InvocationTargetException e){<br />    throw e.getTargetException();<br />    }  <br />  }<br />  <br />  try{<br />   return m.invoke(statement, args);<br />  } catch(InvocationTargetException e) {<br />   throw e.getTargetException();<br />   } <br /> }<br /> //两个要接管的方法名<br /> <br /> private final static String SETSTRING = "setString";<br /> private final static String EXECUTEQUERY = "executeQuery";<br />}</p>
		<p>
				<br />_ResultSet.java</p>
		<p style="BACKGROUND: #eeeeee">/* <br /> * Created on 2003-10-23 by Liudong <br /> */<br /> <br />package lius.pool;<br />import java.sql.ResultSet;<br />import java.lang.reflect.InvocationHandler;<br />import java.lang.reflect.InvocationTargetException;<br />import java.lang.reflect.Method;<br />import java.lang.reflect.Proxy;<br /><br />/** <br /> * 数据库结果集的代理类 <br /> * @author Liudong <br /> */<br /> class _ResultSet implements InvocationHandler{ <br /> private ResultSet rs = null;<br /> private boolean decode = false;<br /> <br /> public _ResultSet(ResultSet rs,boolean decode) {<br />  this.rs = rs;<br />  this.decode = decode;<br /> }<br /> <br /> public ResultSet getResultSet(){ <br />  Class[] interfaces = rs.getClass().getInterfaces();<br />  if(interfaces==null||interfaces.length==0){<br />   interfaces = new Class[1];<br />   interfaces[0] = ResultSet.class;  <br />  }<br /> <br />  ResultSet rs2 = (ResultSet)Proxy.newProxyInstance(rs.getClass().getClassLoader(),interfaces,this);<br />  return rs2;<br /> <br /> }<br /><br /> /**  <br />  * 结果getString方法  <br />  */<br /> public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { <br />  String method = m.getName();<br />  if(decode &amp;&amp; GETSTRING.equals(method)){<br />   try{<br />    String result = (String)m.invoke(rs,args);<br />    if(result!=null)     <br />     return new String(result.getBytes("8859_1"));<br />    return null;<br />   <br />   }catch(InvocationTargetException e){<br />    throw e.getTargetException();<br />    }<br />   <br />  } <br />  <br />  try{<br />   return m.invoke(rs, args);<br />  }catch(InvocationTargetException e){<br />   throw e.getTargetException();<br />  }<br /> }<br /> <br /> private final static String GETSTRING = "getString";<br /><br />}</p>
		<p>　　现在我们已经把三个接口的代理类做好了，下一步就是怎么来使用这三个类。其实对于使用者来讲并不需要关心三个类，只需要了解_Connection就可以了，因为另外两个是_Connection直接调用的。为了使用_Connection我们必须传入两个参数，第一个是数据库实际的数据库连接实例，另外一个是布尔值代表是否进行转码处理。我们必须先通过实际的情况获取到数据库连接后再传入_Connection的构造函数作为参数，下面例子告诉你如何来使用_Connection这个类：</p>
		<p style="BACKGROUND: #eeeeee">　　Connection conn = getConnection(); //获取数据库连接<br />　　boolean coding = false; //从配置或者其他地方读取是否进行转码的配置 <br />　　//接管数据库连接实例 <br />　　_Connection _conn = new _Connection(conn,coding);<br />　　//获得接管后的数据库连接实例，以后直接使用conn2而不是conn <br />　　Connection conn2 = _conn.getConnection();</p>
		<p>　　因为对一个应用系统来讲，数据库连接的获取必然有统一的方法，在这个方法中加入对连接的接管就可以一劳永逸的解决数据库的编码问题。</p>
		<p>　　<strong>性能比较</strong></p>
		<p>　　功能没有问题了，开发者接下来就会关心性能的问题，因为在进行一些对响应速度要求很高或者大数据量的处理情况下性能就成为一个非常突出的问题。由于JAVA中的动态接口代理采用的是反射（Reflection）机制，同时又加入我们自己的一些代码例如方法名判断，字符串转码等操作因此在性能上肯定比不上直接使用没有经过接管的数据库连接。但是这点性能上的差别是不是我们可以忍受的呢，为此我做了一个试验对二者进行了比较：</p>
		<p>　　测试环境简单描述：</p>
		<p>　　使用ACCESS数据库，建两张结构一样的表，计算从获取连接后到插入数据完毕后的时间差，两个程序（直连数据库和使用连接接管）都进行的字符串的转码操作。</p>
		<p>　　测试结果：</p>
		<table cellspacing="1" cellpadding="1" width="500" align="center" bgcolor="#999999" border="0">
				<tbody>
						<tr bgcolor="#ffffff">
								<td align="middle" height="25">插入记录数</td>
								<td align="middle">直连数据库程序耗时 单位：ms</td>
								<td align="middle">使用连接接管程序耗时</td>
								<td align="middle">性能比较</td>
						</tr>
						<tr bgcolor="#ffffff">
								<td height="25">1000</td>
								<td>2063</td>
								<td>2250</td>
								<td>9.0%</td>
						</tr>
						<tr bgcolor="#ffffff">
								<td height="25">5000</td>
								<td>8594</td>
								<td>8359</td>
								<td>-2.7%</td>
						</tr>
						<tr bgcolor="#ffffff">
								<td height="25">10000</td>
								<td>16750</td>
								<td>17219</td>
								<td>2.8%</td>
						</tr>
						<tr bgcolor="#ffffff">
								<td height="25">15000</td>
								<td>22187</td>
								<td>23000</td>
								<td>3.6%</td>
						</tr>
						<tr bgcolor="#ffffff">
								<td height="25">20000</td>
								<td>27031</td>
								<td>27813</td>
								<td>2.9%</td>
						</tr>
				</tbody>
		</table>
		<p>　　从上面这张测试结果表中来看，二者的性能的差别非常小，尽管在两万条数据的批量插入的时候时间差别也不会多于一秒钟，这样的结果应该说还是令人满意的，毕竟为了程序良好的结构有时候牺牲一点点性能还是值得的。</p>
		<p>　　本文算是我之前文章《使用JAVA动态代理实现数据库连接池》中提出的数据库连接池实现的进一步完善，同样使用动态接口代理的技术来解决数据库编码的问题。JAVA的这个高级技术可以用来解决许多实际中非常棘手的问题，就像本文提到的编码问题的处理以及数据库连接池的实现，同时在WEB开发框架的实现上也有非常大的作为。欢迎对这方面感兴趣的朋友来信共同来研究。<br /><br />from: <a href="http://www.javafan.net/article/20041212111952983.html">http://www.javafan.net/article/20041212111952983.html</a></p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/85178.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-12-03 13:22 <a href="http://www.blogjava.net/weidagang2046/articles/85178.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JDBC性能优化技巧</title><link>http://www.blogjava.net/weidagang2046/articles/84051.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Tue, 28 Nov 2006 06:59:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/84051.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/84051.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/84051.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/84051.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/84051.html</trackback:ping><description><![CDATA[ 
<p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">如果可能，避免访问数据库</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l159 level1 lfo12; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings; mso-bidi-font-weight: bold">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">为应用选择最好最快的 JDBC </span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">驱动 ,参考<a href="http://www.flyjava.com/performance/index.htm"><font color="#0000ff">本站文章</font></a> 。 JDBC3.0</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">提供了新的特性来提高性能，诸如连接池， statemente池的改进</span><b><span lang="EN-US"><o:p>   </o:p></span></b></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l159 level1 lfo12; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings; mso-bidi-font-weight: bold">l<span style="FONT: 7pt 'Times New Roman'">        </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">对数据库使用连接池并且重用连接，而不要重复打开和关闭连接。最佳的连接池大小是当连接池大到足够使服务请求不等待</span><b><span lang="EN-US"><o:p></o:p></span></b></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l159 level1 lfo12; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">尽量使用支持 JDBC3.0 的驱动，因为 JDBC3.0 支持包括 DataSource 对象，连接池，分布式事务支持， RowSets 和 prepared statement 池等性能增强特性</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l159 level1 lfo12; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">Prepared statement 池（自从 JDBC3.0 开始有）高速缓存已经预先优化并运行了的 SQL 查询，这样，他们被再次请求的时候，不必经历再次的优化预处理（避免最优化步骤，诸如检查语法，验证地址，优化访问路径和执行计划）。 Statement 池是一个很好的，重要的性能优化方法</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-list: l172 level1 lfo14; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">JDBC3.0 中的 Statement 池和连接池能合作共享 statement 池，这样，能使用一个已高速缓存的 statement （该 statement 来自另外一个连接）的连接，在由任一连接执行的 一些SQL 首次被执行时，产生的 statement 准备开销仅一次</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l159 level1 lfo12; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">RowSet对象与 ResultSet 对象相似，但是能提供当断开连接的时候对数据库数据的访问。这允许数据以它最简单的形式被高效的高速缓存</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-list: l172 level1 lfo14; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">用同一个连接执行多个 statements</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l159 level1 lfo12; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">关闭 autocommit ，但不要让事务打开太久</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l159 level1 lfo12; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">避免将事务分布开（事务跨越多个连接）</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l159 level1 lfo12; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">最小化数据库的行和列数据获取。使用 setMaxRows, setMaxFieldSize,</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">和 SetFetchSize</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l159 level1 lfo12; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings; mso-bidi-font-weight: bold">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">使用最高效的数据类型：字符串比整数型快，整数型比浮点类型和时间戳类型都要高效（是否不太理解^&amp;^，这是针对DB2数据库处理来说的，处理character类型最快，而处理integer类型通常需要一些转换或者字节排序）</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l159 level1 lfo12; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">使用 updateXXX()</span>方法<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">更新： updateXXX() 在可更新的结果集上调用。结果集已经定位到了一行 , 因此当使用一个 UPDATE statement 时，可以消除通常的查找要更新的数据行的开销</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l159 level1 lfo12; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">Cache任何请求的元数据（ metadata ）并尽可能少的使用元数据 方法，其慢的程度一用便知</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l159 level1 lfo12; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">避免在元数据 查询中使用 null 参数</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l159 level1 lfo12; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">使用虚拟查询获得一行的元数据，不要使用getcolumns()（假如应用允许用户使用列数据，应用是使用getColumns来返回列的信息给用户还是准备一个虚拟查询而后调用getMetadata呢？</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l159 level1 lfo12; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">使用存储过程，避免多余的网络传输</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l159 level1 lfo12; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">在存储过程中使用参量，不要将数据挨个地放在statement中，最小化解析开销。此条针对DB2来说，其它数据库未必适用。SQL总是以字符串形式发送给DB2数据库，例如：</span><br /><font color="#ff0000">CallableStatement cstmt = conn.prepareCall ("call getCustName (12345)");<br />ResultSet rs = cstmt.executeQuery ();</font><br />DB2服务器必须解析该SQL，验证参量类型，并将参量转化为正确的数据类型。</p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l159 level1 lfo12; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">对需要重复执行的statement使用预处理statement（PreparedStatement）</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l159 level1 lfo12; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">选择使用最佳游标：对连续读取使用游标；对双向滚动使用游标。对仅返回一行的查询避免使用游标。</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">在JVM中Cache频繁请求的数据，避免不必要的数据库请求</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">采用预读取机制， 批量取行，而不要一次一行 。调整批大小和预取行的数量。避免使用预取 BLOB 数据。</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">除非绝对需要，否则避免移动数据</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">在数据穿过网络之前要使流化数据（ Streamline data ）</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">避免每次处理一行，尽可能一起处理多行。</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">在表中统计个数（例如：使用 select count(*) from myTable,yourTable where …</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">）属于资源密集型的。试试首先选入临时表，仅返回该计数（count），然后发送精确的二次查询获得临时表中的行的子集。</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">恰</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">当的使用 SQL 能减少资源请求。使用返回所需数据的最小值的查询：避免 select * 查询。一个返回小的数据子集的复杂查询，比一个简单的，返回超过所需的大量数据的简单查询更高效。</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">使你的查询尽可能精巧，例如：尽可能精确地最小化要传输的数据，使其是所需的子集</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">努力批量更新：将 statement 收集到一起，然后在一个事务里面一起执行。如果可能，使用有条件的逻辑和临时变量来达到 statement 批处理</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">永远不要让 DBMS 事务跨越用户输入</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">考虑使用乐观锁。乐观锁使用时间戳验证数据是否还没有被其他用户改变，否则事务失败</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">使用 恰当的更新，例如：更新行</span>/<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">表中已经存在的数据，而不要添加或者删除行</span>/<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">表。在适当的位置更新数据要比移动数据快得多，如果更新需要的空间比表设计能提供的更多，这可能是需要的。如果你设计的行需要空间初始化，更新将会更快。交易是你的表可能需要更多的磁盘空间，但可能速度更快。由于磁盘空间是便宜的，使用一点点能提高性能</span>，这应该说是非常有价值的投资</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">分开存储正在操作的数据和历史数据（更一般的情况是将频繁使用的数据和不常使用的数据分开存储）</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">尽可能小的保留你的操作数据集，避免必须读那些不相关的数据</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">DBMS可以很好的并行运转，尽量将应用设计成当和 DBMS交互时应用能做其他事情。</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">使用流水线操作和并行操作。 将应用设计成支持大量并行进程， 使应用运行更快。如果要处理多步，努力设计好应用，以使后来的步骤能够在任何优先的进程已经完成的数据部分上开始工作，而不是必须等到优先进程完成</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">      </span></span> <span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">事物的保护级别越高，性能损失就越大。事物级别按增长的顺序为： TRANSACTION_NONE, TRANSACTION_READ_UNCOMMITTED, TRANSACTION_READ_COMMITTED, TRANSACTION_REPEATABLE_READ, TRANSACTION_SERIALIZABLE。使用Connection.setTransactionIsolation() 设置你想要的事物级别</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">      </span></span><span lang="EN-US" style="FONT-WEIGHT: normal; FONT-SIZE: 7pt; FONT-STYLE: normal; FONT-FAMILY: Wingdings; FONT-VARIANT: normal"></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">默认的自动提交模式由于使每一个数据库命令都成为一个单独的事务，这会严重影响性能，关闭自动提交（Connection.setAutoCommit(false) ），明确声明事务</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">通过整合多个事务为一个的批量操作，并在一个statement中使用Statement.addBatch() 和Statement.executeBatch()</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">     </span></span><span style="FONT: 7pt 'Times New Roman'"> </span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman"> Savepoints (from JDBC3.0)需要昂贵的资源。一旦不再需要，就立刻使用Connection.releaseSavepoint()释放掉Savepoints</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">ConnectionPoolDataSource (from JDBC3.0)和PooledConnection接口为连接池提供了built-in支持</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT: 7pt 'Times New Roman'">  </span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">使用setLogWriter() (from Driver, DataSource, or ConnectionPooledDataSource; from JDBC3.0) 帮助跟踪JDBC流</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT: 7pt 'Times New Roman'">  </span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">使用Connection.setReadOnly(true)优化只读数据库（操作）交互</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span lang="EN-US"></span><span style="FONT: 7pt 'Times New Roman'">  <span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'; mso-bidi-font-size: 12.0pt; mso-bidi-font-family: 'Times New Roman'; mso-font-kerning: 1.0pt; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA"></span></span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">使用Connection.nativeSQL()察看SQL查询如何在数据库种执行，帮助确保SQL已被优化</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT: 7pt 'Times New Roman'"> </span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">切记：一旦可能，立刻关闭Statement和ResultSet</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT: 7pt 'Times New Roman'"> </span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">使用DatabaseMetaData获得数据库功能性信息</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT: 7pt 'Times New Roman'"> </span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">一直捕捉和处理数据库警告和异常</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT: 7pt 'Times New Roman'"> </span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">使用最恰当的数据类型明确数据的类型，例如：以date类型存储日期，儿不要用varchar</span></p><p class="MsoNormal" style="MARGIN-LEFT: 21pt; TEXT-INDENT: -21pt; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l77 level1 lfo11; tab-stops: list 21.0pt"><span lang="EN-US" style="FONT-FAMILY: Wingdings">l<span style="FONT: 7pt 'Times New Roman'">         </span></span><span style="FONT: 7pt 'Times New Roman'"> </span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">使用可滚动ResultSet (JDBC 2.0)<br /><br />from: <a href="http://www.ijsp.net/2/2003-9/20/0000431.shtml">http://www.ijsp.net/2/2003-9/20/0000431.shtml</a></span></p><img src ="http://www.blogjava.net/weidagang2046/aggbug/84051.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-28 14:59 <a href="http://www.blogjava.net/weidagang2046/articles/84051.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>我怎样创建一个序列号或是自动递增的字段？</title><link>http://www.blogjava.net/weidagang2046/articles/83633.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 26 Nov 2006 09:06:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/83633.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/83633.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/83633.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/83633.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/83633.html</trackback:ping><description><![CDATA[
		<p>PostgreSQL 支持 SERIAL 数据类型。（字段定义为SERIAL后）将自动创建一个序列生成器，例如： </p>
		<pre>   CREATE TABLE person ( 
      id   SERIAL, 
      name TEXT 
   );
</pre>
		<p>会自动转换为以下SQL语句： </p>
		<pre>   CREATE SEQUENCE person_id_seq;
   CREATE TABLE person ( 
      id   INT4 NOT NULL DEFAULT nextval('person_id_seq'),
      name TEXT
   );<br /><br />from: <a href="http://www.pgsqldb.org/twiki/bin/view/PgSQL/PostgreFAQ#4.11.1">http://www.pgsqldb.org/twiki/bin/view/PgSQL/PostgreFAQ#4.11.1</a></pre>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/83633.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-26 17:06 <a href="http://www.blogjava.net/weidagang2046/articles/83633.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>放弃 ORM 改用 SqlMap 的 N 个理由</title><link>http://www.blogjava.net/weidagang2046/articles/83620.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 26 Nov 2006 08:02:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/83620.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/83620.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/83620.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/83620.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/83620.html</trackback:ping><description><![CDATA[
		<p>1. 在项目中经常碰到的数据库分页查询, ORM 一般都支持的不好, 如果用 ORM, 这部分程序往往要自己扩展, 而 SqlMap 对各种查询语句不分彼此, 一概在 Map 文件里定义。</p>
		<p>2. 对一些稍微复杂些的语句, 例如在对金额等敏感数据操作时, 一个常用的操作序列是:<br />a. 先取出当前金额<br />b. 运算后得到更新的金额<br />c. 执行 Update 语句: Update &lt; tableName &gt; set amount= &lt; New amount &gt; where amount= &lt; Old amount &gt; <br />这种操作是 ORM 不能支持的, SqlMap 能很好的支持。</p>
		<p>3. SqlMap 的 Domain 对象可以直接放在业务层, 一般 ORM 的对数据访问的基类要放在数据访问层(因为带有对数据访问的接口, 放在业务层不合适), 增加了代码的冗余度。</p>
		<p>4. 用 ORM 的目的是什么, 最主要的目的是减少重复的底层编程工作量, SqlMap 完全可以做到。</p>
		<p>再说说 SqlMap 的不足:</p>
		<p>1. 因为不象 ORM 那样生成稳定可靠的对数据访问的基类, 所以要对 Map 操作做好充足的单元测试, 增加了测试的工作量。</p>
		<p>2. 每次改动数据库, Map 和 Domain 文件往往要手工修改, 因为 SqlMap 的灵活性, 往往我们会手工调整 Map 而不会直接使用 Generator 生成的代码。<br /><br />from: <a href="http://matrix.foresee.cn/blogs/simon/archives/001638.html">http://matrix.foresee.cn/blogs/simon/archives/001638.html</a></p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/83620.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-26 16:02 <a href="http://www.blogjava.net/weidagang2046/articles/83620.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>海量数据库的查询优化及分页算法方案</title><link>http://www.blogjava.net/weidagang2046/articles/77597.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 27 Oct 2006 04:17:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/77597.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/77597.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/77597.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/77597.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/77597.html</trackback:ping><description><![CDATA[在以下的文章中，我将以“办公自动化”系统为例，探讨如何在有着1000万条数据的MS SQL SERVER数据库中实现快速的数据提取和数据分页。以下代码说明了我们实例中数据库的“红头文件”一表的部分数据结构：
<p>CREATE TABLE [dbo].[TGongwen] (    --TGongwen是红头文件表名</p><p>   [Gid] [int] IDENTITY (1, 1) NOT NULL ,<br />--本表的id号，也是主键</p><p>   [title] [varchar] (80) COLLATE Chinese_PRC_CI_AS NULL ,  <br />--红头文件的标题</p><p>   [fariqi] [datetime] NULL ,<br />--发布日期</p><p>   [neibuYonghu] [varchar] (70) COLLATE Chinese_PRC_CI_AS NULL ,<br />--发布用户</p><p>   [reader] [varchar] (900) COLLATE Chinese_PRC_CI_AS NULL ,</p><p>--需要浏览的用户。每个用户中间用分隔符“,”分开</p><p>) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]</p><p>GO</p><p><br />　　下面，我们来往数据库中添加1000万条数据：</p><p>declare @i int</p><p>set @i=1</p><p>while @i&lt;=250000</p><p>begin</p><p>    insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-2-5','通信科','通信科,办公室,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,治安支队,外事科','这是最先的25万条记录')</p><p>    set @i=@i+1</p><p>end</p><p>GO</p><p> </p><p>declare @i int</p><p>set @i=1</p><p>while @i&lt;=250000</p><p>begin</p><p>    insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-9-16','办公室','办公室,通信科,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,外事科','这是中间的25万条记录')</p><p>    set @i=@i+1</p><p>end</p><p>GO</p><p> </p><p>declare @h int</p><p>set @h=1</p><p>while @h&lt;=100</p><p>begin</p><p>declare @i int</p><p>set @i=2002</p><p>while @i&lt;=2003</p><p>begin</p><p>declare @j int</p><p>        set @j=0</p><p>        while @j&lt;50</p><p>            begin</p><p>declare @k int</p><p>            set @k=0</p><p>            while @k&lt;50</p><p>            begin</p><p>    insert into Tgongwen(fariqi,neibuyonghu,reader,title) values(cast(@i as varchar(4))+'-8-15 3:'+cast(@j as varchar(2))+':'+cast(@j as varchar(2)),'通信科','办公室,通信科,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,外事科','这是最后的50万条记录')</p><p>            set @k=@k+1</p><p>            end</p><p>set @j=@j+1</p><p>        end</p><p>set @i=@i+1</p><p>end</p><p>set @h=@h+1</p><p>end</p><p>GO</p><p> </p><p>declare @i int</p><p>set @i=1</p><p>while @i&lt;=9000000</p><p>begin</p><p>    insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-5-5','通信科','通信科,办公室,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,治安支队,外事科','这是最后添加的900万条记录')</p><p>    set @i=@i+1000000</p><p>end</p><p>GO</p><p>　　通过以上语句，我们创建了25万条由通信科于2004年2月5日发布的记录，25万条由办公室于2004年9月6日发布的记录，2002年和2003年各100个2500条相同日期、不同分秒的由通信科发布的记录（共50万条），还有由通信科于2004年5月5日发布的900万条记录，合计1000万条。<br /><br />from: <a href="http://www.pconline.com.cn/pcedu/empolder/db/sql/0501/538958.html">http://www.pconline.com.cn/pcedu/empolder/db/sql/0501/538958.html</a></p><img src ="http://www.blogjava.net/weidagang2046/aggbug/77597.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-10-27 12:17 <a href="http://www.blogjava.net/weidagang2046/articles/77597.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SQL查询的分页思路</title><link>http://www.blogjava.net/weidagang2046/articles/77598.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 27 Oct 2006 04:17:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/77598.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/77598.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/77598.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/77598.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/77598.html</trackback:ping><description><![CDATA[如果用一般的SELECT * 查询SQL数据库,然后用recordset进行分页的话,在返回结果很多的情况下将会是一个漫长的过程,而且很消耗内存.你可能会有感觉,用access也比SQL快.<br />其实我们可以只取出我们每页需要显示的记录数,这样的速度是惊人的,非常快.这里我们会用到聚集索引来快速确定我们需要取出的记录数的位置.如下面:<br />if p&gt;1 then 'p为PAGE页数<br />if n="next" then'下一页<br />sql="select top 26 * from song1 where id &gt; "&amp;pk&amp;" and contains(songtitle,'"&amp;songname&amp;"')" 'PK为当前页的最大ID数<br />elseif n="prev" then'上一页<br />sql="select top 26 * from song1 where id &lt; "&amp;previd&amp;" and contains(songtitle,'"&amp;songname&amp;"') order by id desc"'previd最小ID<br />end if<br />else<br />sql="select top 26 * from song1 where contains(songtitle,'"&amp;songname&amp;"')"'没有指定PAGE值,默认第一页<br />end if<br />这里用到了全文检索,速度也是很快的,我在52万记录下测试,最快可以46MS(机器C1.7. DDR 256M),感觉比较可以,我用like代码模糊查询页测试过,在结果集很多的情况下比全文更快,但是如果结果很少(整个表只有那么几条)将是漫长的过程,因为要对全表进行扫描!
<p>按上面的方法不能得出所查询结果得总记录数,这里我们可以用select count(*) 来获取记录总数,速度页还过得去,不过感觉要慢一截,特别是记录集很多得情况,不过这样占用得内存很小得.<br />以上是我最近搞SQL查询得心得. <br /><br />from: <a href="http://www.fixdown.com/article/article/2121.htm">http://www.fixdown.com/article/article/2121.htm</a></p><img src ="http://www.blogjava.net/weidagang2046/aggbug/77598.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-10-27 12:17 <a href="http://www.blogjava.net/weidagang2046/articles/77598.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SQL Server 存储过程的分页</title><link>http://www.blogjava.net/weidagang2046/articles/77596.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 27 Oct 2006 04:16:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/77596.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/77596.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/77596.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/77596.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/77596.html</trackback:ping><description><![CDATA[
		<p>
				<font color="#004fc6">
						<font color="#000000">　　建立表： <br /><br />CREATE TABLE [TestTable] ( <br />[ID] [int] IDENTITY (1, 1) NOT NULL , <br />[FirstName] [nvarchar] (100) COLLATE Chinese_PRC_CI_AS NULL , <br />[LastName] [nvarchar] (100) COLLATE Chinese_PRC_CI_AS NULL , <br />[Country] [nvarchar] (50) COLLATE Chinese_PRC_CI_AS NULL , <br />[Note] [nvarchar] (2000) COLLATE Chinese_PRC_CI_AS NULL <br />) ON [PRIMARY] <br />GO <br /><br /><br /><br />插入数据：(2万条，用更多的数据测试会明显一些) <br />SET IDENTITY_INSERT TestTable ON <br /><br />declare @i int <br />set @i=1 <br />while @i&lt;=20000 <br />begin <br />insert into TestTable([id], FirstName, LastName, Country,Note) values(@i, ''FirstName_XXX'',''LastName_XXX'',''Country_XXX'',''Note_XXX'') <br />set @i=@i+1 <br />end <br /><br />SET IDENTITY_INSERT TestTable OFF <br /><br /><br /><br />------------------------------------- <br /><br />分页方案一：(利用Not In和SELECT TOP分页) <br />语句形式： <br />SELECT TOP 10 * <br />FROM TestTable <br />WHERE (ID NOT IN <br />(SELECT TOP 20 id <br />FROM TestTable <br />ORDER BY id)) <br />ORDER BY ID <br /><br /><br />SELECT TOP 页大小 * <br />FROM TestTable <br />WHERE (ID NOT IN <br />(SELECT TOP 页大小*页数 id <br />FROM 表 <br />ORDER BY id)) <br />ORDER BY ID <br /><br />------------------------------------- <br /><br />分页方案二：(利用ID大于多少和SELECT TOP分页） <br />语句形式： <br />SELECT TOP 10 * <br />FROM TestTable <br />WHERE (ID &gt; <br />(SELECT MAX(id) <br />FROM (SELECT TOP 20 id <br />FROM TestTable <br />ORDER BY id) AS T)) <br />ORDER BY ID <br /><br /><br />SELECT TOP 页大小 * <br />FROM TestTable <br />WHERE (ID &gt; <br />(SELECT MAX(id) <br />FROM (SELECT TOP 页大小*页数 id <br />FROM 表 <br />ORDER BY id) AS T)) <br />ORDER BY ID <br /><br /><br />------------------------------------- <br /><br />分页方案三：(利用SQL的游标存储过程分页) <br />create procedure XiaoZhengGe <br />@sqlstr nvarchar(4000), --查询字符串 <br />@currentpage int, --第N页 <br />@pagesize int --每页行数 <br />as <br />set nocount on <br />declare @P1 int, --P1是游标的id <br />@rowcount int <br />exec sp_cursoropen @P1 output,@sqlstr,@scrollopt=1,@ccopt=1,@rowcount=@rowcount output <br />select ceiling(1.0*@rowcount/@pagesize) as 总页数--,@rowcount as 总行数,@currentpage as 当前页 <br />set @currentpage=(@currentpage-1)*@pagesize+1 <br />exec sp_cursorfetch @P1,16,@currentpage,@pagesize <br />exec sp_cursorclose @P1 <br />set nocount off <br /><br />其它的方案：如果没有主键，可以用临时表，也可以用方案三做，但是效率会低。 <br />建议优化的时候，加上主键和索引，查询效率会提高。 <br /><br />通过SQL 查询分析器，显示比较：我的结论是: <br />分页方案二：(利用ID大于多少和SELECT TOP分页）效率最高，需要拼接SQL语句 <br />分页方案一：(利用Not In和SELECT TOP分页) 效率次之，需要拼接SQL语句 <br />分页方案三：(利用SQL的游标存储过程分页) 效率最差，但是最为通用 <br /><br />在实际情况中，要具体分析。 </font>
						<strong>
								<img height="1" src="http://cfan.net.cn/down_info.asp?id=15547" width="1" border="0" />
								<br />
								<br />from: <a href="http://cfan.net.cn/info/15547.html">http://cfan.net.cn/info/15547.html</a></strong>
				</font>
		</p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/77596.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-10-27 12:16 <a href="http://www.blogjava.net/weidagang2046/articles/77596.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>存储过程从入门到熟练(多个存储过程完整实例及调用方法)</title><link>http://www.blogjava.net/weidagang2046/articles/77595.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 27 Oct 2006 04:14:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/77595.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/77595.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/77595.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/77595.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/77595.html</trackback:ping><description><![CDATA[
		<span class="t18">  
<p>①为什么要使用存储过程?<br />因为它比SQL语句执行快.</p><p>②存储过程是什么?<br />把一堆SQL语句罗在一起,还可以根据条件执行不同SQL语句.(AX写作本文时观点)</p><p>③来一个最简单的存储过程<br />CREATE PROCEDURE dbo.testProcedure_AX<br />AS<br />select userID from USERS order by userid desc</p><p>注:dbo.testProcedure_AX是你创建的存储过程名,可以改为:AXzhz等,别跟关键字冲突就行了.AS下面就是一条SQL语句,不会写SQL语句的请回避.</p><p>④我怎么在ASP.NET中调用这个存储过程?<br />下面黄底的这两行就够使了.<br />        public static string GetCustomerCName(ref ArrayList arrayCName,ref ArrayList arrayID)<br />        {<br />            SqlConnection con=ADConnection.createConnection();<br />            SqlCommand cmd=new SqlCommand("testProcedure_AX",con);<br />            cmd.CommandType=CommandType.StoredProcedure;<br />            con.Open();<br />            try<br />            {<br />                SqlDataReader dr=cmd.ExecuteReader();<br />                while(dr.Read())<br />                {<br />                    if(dr[0].ToString()=="")<br />                    {<br />                        arrayCName.Add(dr[1].ToString());<br />                    }<br />                }<br />                con.Close(); <br />                return "OK!";<br />            }<br />            catch(Exception ex)<br />            {<br />                con.Close();<br />                return ex.ToString();<br />            }<br />        }<br />注:其实就是把以前<br />SqlCommand cmd=new SqlCommand("select userID from USERS order by userid desc",con);<br />中的SQL语句替换为存储过程名,再把cmd的类型标注为CommandType.StoredProcedure(存储过程)</p><p>⑤写个带参数的存储过程吧,上面这个简单得有点惨不忍睹,不过还是蛮实用的.<br />参数带就带两,一个的没面子,太小家子气了.</p><p>CREATE PROCEDURE dbo.AXzhz<br />/*<br />这里写注释<br />*/<br />@startDate varchar(16),<br />@endDate varchar(16) <br />AS<br /> select id  from table_AX where commentDateTime&gt;@startDate and commentDateTime&lt;@endDate order by contentownerid DESC</p><p>注:@startDate varchar(16)是声明@startDate 这个变量,多个变量名间用【,】隔开.后面的SQL就可以使用这个变量了.</p><p>⑥我怎么在ASP.NET中调用这个带参数的存储过程?</p><p> public static string GetCustomerCNameCount(string startDate,string endDate,ref DataSet ds)<br />{<br />            SqlConnection con=ADConnection.createConnection();<br />//-----------------------注意这一段--------------------------------------------------------------------------------------------------------<br />            SqlDataAdapter da=new SqlDataAdapter("AXzhz",con);</p><p>            para0=new SqlParameter("@startDate",startDate);<br />            para1=new SqlParameter("@endDate",endDate);<br />            da.SelectCommand.Parameters.Add(para0);<br />            da.SelectCommand.Parameters.Add(para1);<br />            da.SelectCommand.CommandType=CommandType.StoredProcedure;<br />//-------------------------------------------------------------------------------------------------------------------------------</p><p><br />            try<br />            {<br />                con.Open();<br />                da.Fill(ds);<br />                con.Close();<br />                return "OK";<br />            }<br />            catch(Exception ex)<br />            {<br />                return ex.ToString();<br />            }            <br />        }</p><p>注:把命令的参数添加进去,就OK了<br />鸟的,改字体颜色的东西太垃圾了,改不好,大家凑活着看.</p><p>⑦我还想看看SQL命令执行成功了没有.<br />注意看下面三行红色的语句</p><p>CREATE PROCEDURE dbo.AXzhz<br />/*<br />  @parameter1 用户名<br />  @parameter2 新密码<br />*/<br />@password nvarchar(20),<br />@userName nvarchar(20)<br />AS<br />declare @err0 int<br />update WL_user set <a href="mailto:password=@password"><font color="#000000">password=@password</font></a> where <a href="mailto:UserName=@userName"><font color="#000000">UserName=@userName</font></a><br />set @err0=@@error <br />select  @err0 as err0</p><p>注:先声明一个整型变量@err0,再给其赋值为@@error(这个是系统自动给出的语句是否执行成功,0为成功,其它为失败),最后通过select把它选择出来,某位高人说可以通过Return返回,超出本人的认知范围,俺暂时不会,以后再补充吧</p><p>⑧那怎么从后台获得这个执行成功与否的值呢?<br />下面这段代码可以告诉你答案:<br />    public static string GetCustomerCName()<br />        {<br />            SqlConnection con=ADConnection.createConnection();<br />            <br />            SqlCommand cmd=new SqlCommand("AXzhz",con);<br />            cmd.CommandType=CommandType.StoredProcedure;<br />            para0=new SqlParameter("@startDate","2006-9-10");<br />            para1=new SqlParameter("@endDate","2006-9-20");<br />            da.SelectCommand.Parameters.Add(para0);<br />            da.SelectCommand.Parameters.Add(para1); <br />            con.Open();<br />            try<br />            {<br />               Int32 re=(int32)cmd.ExecuteScalar(); <br />                con.Close(); <br />                if (re==0)<br />                 return "OK!";<br />                else<br />                 return "false";<br />            }<br />            catch(Exception ex)<br />            {<br />                con.Close();<br />                return ex.ToString();<br />            }<br />        }<br />注:就是通过SqlCommand的ExecuteScalar()方法取回这个值,这句话是从MSDN上找的,俺认为改成:<br />     int re=(int)cmd.ExecuteScalar();  99%正确,现在没时间验证,期待您的测试!!!</p><p>⑨我要根据传入的参数判断执行哪条SQL语句!!~<br />下面这个存储过程可以满足我们的要求,竟然是Pascal/VB的写法,Begin----End ,不是{},,,对使用C#的我来说,这个语法有点恶心.........</p><p>ALTER PROCEDURE dbo.selectCustomerCNameCount<br />@customerID int<br />AS<br />if @customerID=-1<br /> begin<br /> select contentownerid ,userCName,count(*) as countAll from view_usercomment group by contentownerid,userCName order by contentownerid DESC<br /> end<br />else<br /> begin<br /> select contentownerid ,userCName,count(*) as countAll from view_usercomment where <a href="mailto:contentownerid=@customerID"><font color="#000000">contentownerid=@customerID</font></a> group by contentownerid,userCName order by contentownerid DESC<br /> end</p><p>好了,俺的水平只止于此,也够菜鸟们喝一壶的了,还有更多东西等着我们去发现,无尽的征途!!!!!!!!!!!<br /><br />from: <a href="http://www.knowsky.com/340678.html">http://www.knowsky.com/340678.html</a></p></span>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/77595.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-10-27 12:14 <a href="http://www.blogjava.net/weidagang2046/articles/77595.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>数据库索引应用(ms-sql)</title><link>http://www.blogjava.net/weidagang2046/articles/77594.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 27 Oct 2006 04:13:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/77594.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/77594.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/77594.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/77594.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/77594.html</trackback:ping><description><![CDATA[
		<span class="t18"> 
<p>一、索引的概念<br />        索引就是加快检索表中数据的方法。<a href="http://www.knowsky.com/sql.asp">数据库</a>的索引类似于书籍的索引。在书籍中，索引允许用户不必翻阅完整个书就能迅速地找到所需要的信息。在数据库中，索引也允许数据库程序迅速地找到表中的数据，而不必扫描整个数据库。</p><p>二、索引的特点<br />    1.索引可以加快数据库的检索速度 <br />    2.索引降低了数据库插入、修改、删除等维护任务的速度 <br />    3.索引创建在表上，不能创建在视图上 <br />    4.索引既可以直接创建，也可以间接创建 <br />    5.可以在优化隐藏中，使用索引 <br />    6.使用查询处理器执行SQL语句，在一个表上，一次只能使用一个索引 <br />    7.其他</p><p>三、索引的优点<br />    1.创建唯一性索引，保证数据库表中每一行数据的唯一性<br />    2.大大加快数据的检索速度，这也是创建索引的最主要的原因<br />    3.加速表和表之间的连接，特别是在实现数据的参考完整性方面特别有意义。<br />    4.在使用分组和排序子句进行数据检索时，同样可以显著减少查询中分组和排序的时间。<br />    5.通过使用索引，可以在查询的过程中使用优化隐藏器，提高系统的性能。</p><p>四、索引的缺点<br />    1.创建索引和维护索引要耗费时间，这种时间随着数据量的增加而增加<br />    2.索引需要占物理空间，除了数据表占数据空间之外，每一个索引还要占一定的物理空间，如果要建立聚簇索引，那么需要的空间就会更大<br />    3.当对表中的数据进行增加、删除和修改的时候，索引也要动态的维护，降低了数据的维护速度</p><p>五、索引分类<br />    1.直接创建索引和间接创建索引<br />    直接创建索引： CREATE INDEX mycolumn_index ON mytable (myclumn)<br />    间接创建索引：定义主键约束或者唯一性键约束，可以间接创建索引<br />    2.普通索引和唯一性索引<br />    普通索引：CREATE INDEX mycolumn_index ON mytable (myclumn)<br />    唯一性索引：保证在索引列中的全部数据是唯一的，对聚簇索引和非聚簇索引都可以使用<br />    CREATE UNIQUE COUSTERED INDEX myclumn_cindex ON mytable(mycolumn)<br />    3.单个索引和复合索引<br />    单个索引：即非复合索引<br />    复合索引：又叫组合索引，在索引建立语句中同时包含多个字段名，最多16个字段<br />    CREATE INDEX name_index ON username(firstname,lastname)<br />    4.聚簇索引和非聚簇索引(聚集索引，群集索引)<br />   聚簇索引：物理索引，与基表的物理顺序相同，数据值的顺序总是按照顺序排列<br />    CREATE CLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn) WITH<br />    ALLOW_DUP_ROW(允许有重复记录的聚簇索引)<br />   非聚簇索引：CREATE UNCLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn)</p><p>六、索引的使用<br />   1.当字段数据更新频率较低，查询使用频率较高并且存在大量重复值是建议使用聚簇索引<br />    2.经常同时存取多列，且每列都含有重复值可考虑建立组合索引<br />    3.复合索引的前导列一定好控制好，否则无法起到索引的效果。如果查询时前导列不在查询条件中则该复合索引不会被使用。前导列一定是使用最频繁的列<br />    4.多表操作在被实际执行前，查询优化器会根据连接条件，列出几组可能的连接方案并从中找出系统开销最小的最佳方案。连接条件要充份考虑带有索引的表、行数多的表；内外表的选择可由公式：外层表中的匹配行数*内层表中每一次查找的次数确定，乘积最小为最佳方案<br />    5.where子句中对列的任何操作结果都是在sql运行时逐列计算得到的，因此它不得不进行表搜索，而没有使用该列上面的索引；如果这些结果在查询编译时就能得到，那么就可以被sql优化器优化，使用索引，避免表搜索(例：select * from record where substring(card_no,1,4)=’5378’ <br />&amp;&amp; select * from record where card_no like ’5378%’)任何对列的操作都将导致表扫描，它包括数据库函数、计算表达式等等，查询时要尽可能将操作移至等号右边<br />    6.where条件中的’in’在逻辑上相当于’or’，所以语法分析器会将in ('0','1')转化为column='0' or column='1'来执行。我们期望它会根据每个or子句分别查找，再将结果相加，这样可以利用column上的索引；但实际上它却采用了"or策略"，即先取出满足每个or子句的行，存入临时数据库的工作表中，再建立唯一索引以去掉重复行，最后从这个临时表中计算结果。因此，实际过程没有利用column上索引，并且完成时间还要受tempdb数据库性能的影响。in、or子句常会使用工作表，使索引失效；如果不产生大量重复值，可以考虑把子句拆开；拆开的子句中应该包含索引<br />    7.要善于使用存储过程，它使sql变得更加灵活和高效<br /><br />from: <a href="http://www.knowsky.com/339315.html">http://www.knowsky.com/339315.html</a></p></span>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/77594.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-10-27 12:13 <a href="http://www.blogjava.net/weidagang2046/articles/77594.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>浅谈数据库设计技巧</title><link>http://www.blogjava.net/weidagang2046/articles/72880.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 29 Sep 2006 09:39:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/72880.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/72880.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/72880.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/72880.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/72880.html</trackback:ping><description><![CDATA[说到<a href="http://www.knowsky.com/sql.asp"><font color="#000000">数据库</font></a>，我认为不能不先谈数据结构。1996年，在我初入大学学习计算机编程时，当时的老师就告诉我们说：计算机程序＝数据结构＋算法。尽管现在的程序开发已由面向过程为主逐步过渡到面向对象为主，但我还是深深赞同8年前老师的告诉我们的公式：计算机程序＝数据结构＋算法。面向对象的程序开发，要做的第一件事就是，先分析整个程序中需处理的数据，从中提取出抽象模板，以这个抽象模板设计类，再在其中逐步添加处理其数据的函数(即算法)，最后，再给类中的数据成员和函数划分访问权限，从而实现封装。
<p>　　数据库的最初雏形据说源自美国一个奶牛场的记账薄(纸质的，由此可见，数据库并不一定是存储在电脑里的数据^_^)，里面记录的是该奶牛场的收支账目，程序员在将其整理、录入到电脑中时从中受到启发。当按照规定好的数据结构所采集到的数据量大到一定程度后，出于程序执行效率的考虑，程序员将其中的检索、更新维护等功能分离出来，做成单独调用的模块，这个模块后来就慢慢发展、演变成现在我们所接触到的数据库管理系统(DBMS)——程序开发中的一个重要分支。</p><p>　　下面进入正题，首先按我个人所接触过的程序给数据库设计人员的功底分一下类：<br />　　１、没有系统学习过数据结构的程序员。这类程序员的作品往往只是他们的即兴玩具，他们往往习惯只设计有限的几个表，实现某类功能的数据全部塞在一个表中，各表之间几乎毫无关联。网上不少的免费管理软件都是这样的东西，当程序功能有限，数据量不多的时候，其程序运行起来没有什么问题，但是如果用其管理比较重要的数据，风险性非常大。<br />　　２、系统学习过数据结构，但是还没有开发过对程序效率要求比较高的管理软件的程序员。这类人多半刚从学校毕业不久，他们在设计数据库表结构时，严格按照教科书上的规定，死扣E-R图和3NF(别灰心，所有的数据库设计高手都是从这一步开始的)。他们的作品，对于一般的access型轻量级的管理软件，已经够用。但是一旦该系统需要添加新功能，原有的数据库表差不多得进行大换血。<br />　　３、第二类程序员，在经历过数次程序效率的提升，以及功能升级的折腾后，终于升级成为数据库设计的老鸟，第一类程序员眼中的高人。这类程序员可以胜任二十个表以上的中型商业数据管理系统的开发工作。他们知道该在什么样的情况下保留一定的冗余数据来提高程序效率，而且其设计的数据库可拓展性较好，当用户需要添加新功能时，原有数据库表只需做少量修改即可。<br />　　４、在经历过上十个类似数据库管理软件的重复设计后，第三类程序员中坚持下来没有转行，而是希望从中找出“偷懒”窍门的有心人会慢慢觉悟，从而完成量变到质变的转换。他们所设计的数据库表结构有一定的远见，能够预测到未来功能升级所需要的数据，从而预先留下伏笔。这类程序员目前大多晋级成数据挖掘方面的高级软件开发人员。<br />　　５、第三类程序员或第四类程序员，在对现有的各家数据库管理系统的原理和开发都有一定的钻研后，要么在其基础上进行二次开发，要么自行开发一套有自主版权的通用数据库管理系统。</p><p>　　我个人正处于第三类的末期，所以下面所列出的一些设计技巧只适合第二类和部分第三类数据库设计人员。同时，由于我很少碰到有兴趣在这方面深钻下去的同行，所以文中难免出现错误和遗漏，在此先行声明，欢迎大家指正，不要藏私哦8)</p><p>　　一、树型关系的数据表<br />　　不少程序员在进行数据库设计的时候都遇到过树型关系的数据，例如常见的类别表，即一个大类，下面有若干个子类，某些子类又有子类这样的情况。当类别不确定，用户希望可以在任意类别下添加新的子类，或者删除某个类别和其下的所有子类，而且预计以后其数量会逐步增长，此时我们就会考虑用一个数据表来保存这些数据。按照教科书上的教导，第二类程序员大概会设计出类似这样的数据表结构：</p><p>类别表_1(Type_table_1)<br />名称　　　　　类型　　　　约束条件　　　说明<br />type_id   　  int    　   无重复　　   类别标识，主键<br />type_name　　 char(50)    不允许为空   类型名称，不允许重复<br />type_father   int         不允许为空   该类别的父类别标识，如果是顶节点的话设定为某个唯一值</p><p>　　这样的设计短小精悍，完全满足3NF，而且可以满足用户的所有要求。是不是这样就行呢？答案是NO！Why？</p><p>　　我们来估计一下用户希望如何罗列出这个表的数据的。对用户而言，他当然期望按他所设定的层次关系一次罗列出所有的类别，例如这样：<br />总类别<br />　　类别1<br />　　　　类别1.1<br />　　　　　　类别1.1.1<br />　　　　类别1.2<br />　　类别2<br />　　　　类别2.1<br />　　类别3<br />　　　　类别3.1<br />　　　　类别3.2<br />　　……</p><p>　　看看为了实现这样的列表显示(树的先序遍历)，要对上面的表进行多少次检索？注意，尽管类别1.1.1可能是在类别3.2之后添加的记录，答案仍然是N次。这样的效率对于少量的数据没什么影响，但是日后类型扩充到数十条甚至上百条记录后，单单列一次类型就要检索数十次该表，整个程序的运行效率就不敢恭维了。或许第二类程序员会说，那我再建一个临时数组或临时表，专门保存类型表的先序遍历结果，这样只在第一次运行时检索数十次，再次罗列所有的类型关系时就直接读那个临时数组或临时表就行了。其实，用不着再去分配一块新的内存来保存这些数据，只要对数据表进行一定的扩充，再对添加类型的数量进行一下约束就行了，要完成上面的列表只需一次检索就行了。下面是扩充后的数据表结构：</p><p>类别表_2(Type_table_2)<br />名称　　　　　类型　　　　约束条件　　　                    说明<br />type_id   　  int     　  无重复　　                   类别标识，主键<br />type_name　　 char(50)    不允许为空                   类型名称，不允许重复<br />type_father   int         不允许为空                   该类别的父类别标识，如果是顶节点的话设定为某个唯一值<br />type_layer    char(6)     限定3层,初始值为000000       类别的先序遍历，主要为减少检索数据库的次数</p><p>　　按照这样的表结构，我们来看看上面例子记录在表中的数据是怎样的：</p><p>type_id      type_name          type_father          type_layer<br />1             总类别               0                 000000<br />2             类别1                1                 010000<br />3             类别1.1              2                 010100<br />4             类别1.2              2                 010200<br />5             类别2                1                 020000<br />6             类别2.1              5                 020100<br />7             类别3                1                 030000<br />8             类别3.1              7                 030100<br />9             类别3.2              7                 030200<br />10            类别1.1.1            3                 010101<br />……</p><p>　　现在按type_layer的大小来检索一下：SELECT * FROM Type_table_2 ORDER BY type_layer</p><p>列出记录集如下：</p><p>type_id      type_name          type_father          type_layer<br />1             总类别               0                 000000<br />2             类别1                1                 010000<br />3             类别1.1              2                 010100<br />10            类别1.1.1            3                 010101<br />4             类别1.2              2                 010200<br />5             类别2                1                 020000<br />6             类别2.1              5                 020100<br />7             类别3                1                 030000<br />8             类别3.1              7                 030100<br />9             类别3.2              7                 030200<br />……</p><p>　　现在列出的记录顺序正好是先序遍历的结果。在控制显示类别的层次时，只要对type_layer字段中的数值进行判断，每2位一组，如大于0则向右移2个空格。当然，我这个例子中设定的限制条件是最多3层，每层最多可设99个子类别，只要按用户的需求情况修改一下type_layer的长度和位数，即可更改限制层数和子类别数。其实，上面的设计不单单只在类别表中用到，网上某些可按树型列表显示的论坛程序大多采用类似的设计。</p><p>　　或许有人认为，Type_table_2中的type_father字段是冗余数据，可以除去。如果这样，在插入、删除某个类别的时候，就得对type_layer 的内容进行比较繁琐的判定，所以我并没有消去type_father字段，这也正符合数据库设计中适当保留冗余数据的来降低程序复杂度的原则，后面我会举一个故意增加数据冗余的案例。</p><p>　　<br />　　二、商品信息表的设计<br />　　假设你是一家百货公司电脑部的开发人员，某天老板要求你为公司开发一套网上电子商务平台，该百货公司有数千种商品出售，不过目前仅打算先在网上销售数十种方便运输的商品，当然，以后可能会陆续在该电子商务平台上增加新的商品出售。现在开始进行该平台数据库的商品信息表的设计。每种出售的商品都会有相同的属性，如商品编号，商品名称，商品所属类别，相关信息，供货厂商，内含件数，库存，进货价，销售价，优惠价。你很快就设计出4个表：商品类型表(Wares_type)，供货厂商表(Wares_provider)，商品信息表(Wares_info)：</p><p>商品类型表(Wares_type)<br />名称　　　　　类型　　　　约束条件　　　                    说明<br />type_id   　  int    　   无重复　　                   类别标识，主键<br />type_name　　 char(50)    不允许为空                   类型名称，不允许重复<br />type_father   int         不允许为空                   该类别的父类别标识，如果是顶节点的话设定为某个唯一值<br />type_layer    char(6)     限定3层,初始值为000000       类别的先序遍历，主要为减少检索数据库的次数</p><p>供货厂商表(Wares_provider)<br />名称　　　　　类型　　　　约束条件　　　                    说明<br />provider_id   int    　   无重复　　                   供货商标识，主键<br />provider_name char(100)   不允许为空                   供货商名称</p><p>商品信息表(Wares_info)<br />名称　　　　  类型　　　　约束条件　　　                    说明<br />wares_id       int    　  无重复　　                     商品标识，主键<br />wares_name     char(100)  不允许为空                     商品名称<br />wares_type　　 int        不允许为空　　　　　　　　　　 商品类型标识，和Wares_type.type_id关联<br />wares_info     char(200)  允许为空                       相关信息<br />provider       int        不允许为空                     供货厂商标识，和Wares_provider.provider_id关联<br />setnum         int        初始值为1                      内含件数，默认为1<br />stock          int        初始值为0                      库存，默认为0<br />buy_price      money      不允许为空                     进货价<br />sell_price     money      不允许为空                     销售价<br />discount       money      不允许为空                     优惠价</p><p>　　你拿着这3个表给老板检查，老板希望能够再添加一个商品图片的字段，不过只有一部分商品有图片。OK，你在商品信息表(Wares_info)中增加了一个haspic的BOOL型字段，然后再建了一个新表——商品图片表(Wares_pic)：</p><p>商品图片表(Wares_pic)<br />名称　　　　  类型　　　　约束条件　　　                    说明<br />pic_id        int    　   无重复　　                     商品图片标识，主键<br />wares_id      int         不允许为空                     所属商品标识，和Wares_info.wares_id关联<br />pic_address　 char(200)   不允许为空　　　　　　　　　　 图片存放路径</p><p>　　程序开发完成后，完全满足老板目前的要求，于是正式启用。一段时间后，老板打算在这套平台上推出新的商品销售，其中，某类商品全部都需添加“长度”的属性。第一轮折腾来了……当然，你按照添加商品图片表的老方法，在商品信息表(Wares_info)中增加了一个haslength的BOOL型字段，又建了一个新表——商品长度表(Wares_length)：</p><p>商品长度表(Wares_length)<br />名称　　　　  类型　　　　约束条件　　　                    说明<br />length_id     int    　   无重复　　                     商品图片标识，主键<br />wares_id      int         不允许为空                     所属商品标识，和Wares_info.wares_id关联<br />length　      char(20)    不允许为空　　　　　　　　　　 商品长度说明</p><p>　　刚刚改完没多久，老板又打算上一批新的商品，这次某类商品全部需要添加“宽度”的属性。你咬了咬牙，又照方抓药，添加了商品宽度表(Wares_width)。又过了一段时间，老板新上的商品中有一些需要添加“高度”的属性，你是不是开始觉得你所设计的数据库按照这种方式增长下去，很快就能变成一个迷宫呢？那么，有没有什么办法遏制这种不可预见性，但却类似重复的数据库膨胀呢？我在阅读《敏捷软件开发：原则、模式与实践》中发现作者举过类似的例子：7.3　“Copy”程序。其中，我非常赞同敏捷软件开发这个观点：在最初几乎不进行预先设计，但是一旦需求发生变化，此时作为一名追求卓越的程序员，应该从头审查整个架构设计，在此次修改中设计出能够满足日后类似修改的系统架构。下面是我在需要添加“长度”的属性时所提供的修改方案：</p><p>　　去掉商品信息表(Wares_info)中的haspic字段，添加商品额外属性表(Wares_ex_property)和商品额外信息表(Wares_ex_info)2个表来完成添加新属性的功能。</p><p>商品额外属性表(Wares_ex_property)<br />名称　　　　  类型　　　　约束条件　　　                    说明<br />ex_pid        int    　   无重复　　                     商品额外属性标识，主键<br />p_name        char(20)    不允许为空                     额外属性名称</p><p>商品额外信息表(Wares_ex_info)<br />名称　　　　    类型　　　　约束条件　　　                    说明<br />ex_iid          int    　   无重复　　                     商品额外信息标识，主键<br />wares_id        int         不允许为空                     所属商品标识，和Wares_info.wares_id关联<br />property_id　   int         不允许为空　　　　　　　　　　 商品额外属性标识，和Wares_ex_property.ex_pid关联<br />property_value  char(200)   不允许为空                     商品额外属性值</p><p>　　在商品额外属性表(Wares_ex_property)中添加2条记录：<br />ex_pid            p_name<br />1                商品图片<br />2                商品长度</p><p>　　再在整个电子商务平台的后台管理功能中追加一项商品额外属性管理的功能，以后添加新的商品时出现新的属性，只需利用该功能往商品额外属性表(Wares_ex_property)中添加一条记录即可。不要害怕变化，被第一颗子弹击中并不是坏事，坏的是被相同轨道飞来的第二颗、第三颗子弹击中。第一颗子弹来得越早，所受的伤越重，之后的抵抗力也越强8)<br />三、多用户及其权限管理的设计<br />　　开发<a href="http://www.knowsky.com/sql.asp"><font color="#000000">数据库</font></a>管理类的软件，不可能不考虑多用户和用户权限设置的问题。尽管目前市面上的大、中型的后台数据库系统软件都提供了多用户，以及细至某个数据库内某张表的权限设置的功能，我个人建议：一套成熟的数据库管理软件，还是应该自行设计用户管理这块功能，原因有二：<br />　　1.那些大、中型后台数据库系统软件所提供的多用户及其权限设置都是针对数据库的共有属性，并不一定能完全满足某些特例的需求；<br />　　2.不要过多的依赖后台数据库系统软件的某些特殊功能，多种大、中型后台数据库系统软件之间并不完全兼容。否则一旦日后需要转换数据库平台或后台数据库系统软件版本升级，之前的架构设计很可能无法重用。</p><p>　　下面看看如何自行设计一套比较灵活的多用户管理模块，即该数据库管理软件的系统管理员可以自行添加新用户，修改已有用户的权限，删除已有用户。首先，分析用户需求，列出该数据库管理软件所有需要实现的功能；然后，根据一定的联系对这些功能进行分类，即把某类用户需使用的功能归为一类；最后开始建表：<br />　　<br />功能表(Function_table)<br />名称　　　　　类型　　　　约束条件　　　说明<br />f_id          int    　   无重复　　   功能标识，主键<br />f_name        char(20)    不允许为空   功能名称，不允许重复<br />f_desc        char(50)    允许为空     功能描述</p><p>用户组表(User_group)<br />名称　　　　　类型　　　　约束条件　　　说明<br />group_id      int         无重复        用户组标识，主键<br />group_name    char(20)    不允许为空    用户组名称<br />group_power   char(100)   不允许为空    用户组权限表，内容为功能表f_id的集合</p><p>用户表(User_table)<br />名称　　　　　类型　　　　约束条件　　　说明<br />user_id       int         无重复        用户标识，主键<br />user_name     char(20)    无重复        用户名<br />user_pwd      char(20)    不允许为空    用户密码<br />user_type     int         不允许为空    所属用户组标识，和User_group.group_id关联</p><p>　　采用这种用户组的架构设计，当需要添加新用户时，只需指定新用户所属的用户组；当以后系统需要添加新功能或对旧有功能权限进行修改时，只用操作功能表和用户组表的记录，原有用户的功能即可相应随之变化。当然，这种架构设计把数据库管理软件的功能判定移到了前台，使得前台开发相对复杂一些。但是，当用户数较大(10人以上)，或日后软件升级的概率较大时，这个代价是值得的。</p><p><br />　　四、简洁的批量m:n设计<br />　　碰到m:n的关系，一般都是建立3个表，m一个，n一个，m:n一个。但是，m:n有时会遇到批量处理的情况，例如到图书馆借书，一般都是允许用户同时借阅n本书，如果要求按批查询借阅记录，即列出某个用户某次借阅的所有书籍，该如何设计呢？让我们建好必须的3个表先：</p><p>书籍表(Book_table)<br />名称　　　　　类型　　　　约束条件　　　说明<br />book_id       int         无重复        书籍标识，主键<br />book_no       char(20)    无重复        书籍编号<br />book_name     char(100)   不允许为空    书籍名称<br />……</p><p>借阅用户表(Renter_table)<br />名称　　　　　类型　　　　约束条件　　　说明<br />renter_id     int         无重复        用户标识，主键<br />renter_name   char(20)    不允许为空    用户姓名<br />……</p><p>借阅记录表(Rent_log)<br />名称　　　　　类型　　　　约束条件　　　说明<br />rent_id       int         无重复        借阅记录标识，主键<br />r_id          int         不允许为空    用户标识，和Renter_table.renter_id关联<br />b_id          int         不允许为空    书籍标识，和Book_table.book_id关联<br />rent_date     datetime    不允许为空    借阅时间<br />……</p><p>　　为了实现按批查询借阅记录，我们可以再建一个表来保存批量借阅的信息，例如：</p><p>批量借阅表(Batch_rent)<br />名称　　　　　类型　　　　约束条件　　　说明<br />batch_id      int         无重复        批量借阅标识，主键<br />batch_no      int         不允许为空    批量借阅编号，同一批借阅的batch_no相同<br />rent_id       int         不允许为空    借阅记录标识，和Rent_log.rent_id关联<br />batch_date    datetime    不允许为空    批量借阅时间</p><p>　　这样的设计好吗？我们来看看为了列出某个用户某次借阅的所有书籍，需要如何查询？首先检索批量借阅表(Batch_rent)，把符合条件的的所有记录的rent_id字段的数据保存起来，再用这些数据作为查询条件带入到借阅记录表(Rent_log)中去查询。那么，有没有什么办法改进呢？下面给出一种简洁的批量设计方案，不需添加新表，只需修改一下借阅记录表(Rent_log)即可。修改后的记录表(Rent_log)如下：</p><p>借阅记录表(Rent_log)<br />名称　　　　　类型　　　　约束条件　　　说明<br />rent_id       int         无重复        借阅记录标识，主键<br />r_id          int         不允许为空    用户标识，和Renter_table.renter_id关联<br />b_id          int         不允许为空    书籍标识，和Book_table.book_id关联<br />batch_no      int         不允许为空    批量借阅编号，同一批借阅的batch_no相同<br />rent_date     datetime    不允许为空    借阅时间<br />……</p><p>　　其中，同一次借阅的batch_no和该批第一条入库的rent_id相同。举例：假设当前最大rent_id是64，接着某用户一次借阅了3本书，则批量插入的3条借阅记录的batch_no都是65。之后另外一个用户租了一套碟，再插入出租记录的rent_id是68。采用这种设计，查询批量借阅的信息时，只需使用一条标准T_SQL的嵌套查询即可。当然，这种设计不符合3NF，但是和上面标准的3NF设计比起来，哪一种更好呢？答案就不用我说了吧。</p><p><br />　　五、冗余数据的取舍<br />　　上篇的“树型关系的数据表”中保留了一个冗余字段，这里的例子更进一步——添加了一个冗余表。先看看例子：我原先所在的公司为了解决员工的工作餐，和附近的一家小餐馆联系，每天吃饭记账，费用按人数平摊，月底由公司现金结算，每个人每个月的工作餐费从工资中扣除。当然，每天吃饭的人员和人数都不是固定的，而且，由于每顿工作餐的所点的菜色不同，每顿的花费也不相同。例如，星期一中餐5人花费40元，晚餐2人花费20，星期二中餐6人花费36元，晚餐3人花费18元。为了方便计算每个人每个月的工作餐费，我写了一个简陋的就餐记账管理程序，数据库里有3个表：</p><p>员工表(Clerk_table)<br />名称　　　　　类型　　　　约束条件　　　说明<br />clerk_id      int         无重复        员工标识，主键<br />clerk_name    char(10)    不允许为空    员工姓名</p><p>每餐总表(Eatdata1)<br />名称　　　　　类型　　　　约束条件　　　说明<br />totle_id      int         无重复        每餐总表标识，主键<br />persons       char(100)   不允许为空    就餐员工的员工标识集合<br />eat_date      datetime    不允许为空    就餐日期<br />eat_type      char(1)     不允许为空    就餐类型，用来区分中、晚餐<br />totle_price   money       不允许为空    每餐总花费<br />persons_num   int         不允许为空    就餐人数</p><p>就餐计费细表(Eatdata2)<br />名称　　　　　类型　　　　约束条件　　　说明<br />id            int         无重复        就餐计费细表标识，主键<br />t_id          int         不允许为空    每餐总表标识，和Eatdata1.totle_id关联<br />c_id          int         不允许为空    员工标识标识，和Clerk_table.clerk_id关联<br />price         money       不允许为空    每人每餐花费</p><p>　　其中，就餐计费细表(Eatdata2)的记录就是把每餐总表(Eatdata1)的一条记录按就餐员工平摊拆开，是个不折不扣的冗余表。当然，也可以把每餐总表(Eatdata1)的部分字段合并到就餐计费细表(Eatdata2)中，这样每餐总表(Eatdata1)就成了冗余表，不过这样所设计出来的就餐计费细表重复数据更多，相比来说还是上面的方案好些。但是，就是就餐计费细表(Eatdata2)这个冗余表，在做每月每人餐费统计的时候，大大简化了编程的复杂度，只用类似这么一条查询语句即可统计出每人每月的寄餐次数和餐费总帐：</p><p>SELECT clerk_name AS personname,COUNT(c_id) as eattimes,SUM(price) AS ptprice FROM Eatdata2 JOIN Clerk_tabsle ON (c_id=clerk_id) JOIN eatdata1 ON (totleid=tid) WHERE eat_date&gt;=CONVERT(datetime,'"&amp;the_date&amp;"') AND eat_date&lt;DATEADD(month,1,CONVERT(datetime,'"&amp;the_date&amp;"')) GROUP BY c_id</p><p>　　想象一下，如果不用这个冗余表，每次统计每人每月的餐费总帐时会多麻烦，程序效率也够呛。那么，到底什么时候可以增加一定的冗余数据呢？我认为有2个原则：</p><p>　　１、用户的整体需求。当用户更多的关注于，对数据库的规范记录按一定的算法进行处理后，再列出的数据。如果该算法可以直接利用后台数据库系统的内嵌函数来完成，此时可以适当的增加冗余字段，甚至冗余表来保存这些经过算法处理后的数据。要知道，对于大批量数据的查询，修改或删除，后台数据库系统的效率远远高于我们自己编写的代码。<br />　　２、简化开发的复杂度。现代软件开发，实现同样的功能，方法有很多。尽管不必要求程序员精通绝大部分的开发工具和平台，但是还是需要了解哪种方法搭配哪种开发工具的程序更简洁，效率更高一些。冗余数据的本质就是用空间换时间，尤其是目前硬件的发展远远高于软件，所以适当的冗余是可以接受的。不过我还是在最后再强调一下：不要过多的依赖平台和开发工具的特性来简化开发，这个度要是没把握好的话，后期维护升级会栽大跟头的。<br /><br />from: <a href="http://www.knowsky.com/4937.html">http://www.knowsky.com/4937.html</a></p><img src ="http://www.blogjava.net/weidagang2046/aggbug/72880.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-09-29 17:39 <a href="http://www.blogjava.net/weidagang2046/articles/72880.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关系型数据库设计篇</title><link>http://www.blogjava.net/weidagang2046/articles/72878.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 29 Sep 2006 09:37:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/72878.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/72878.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/72878.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/72878.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/72878.html</trackback:ping><description><![CDATA[在这篇文章的第二章中，我们已经建立了一个供我们使用的非常简单的笑话数据库，这个库中只包括了一个名叫Jokes的数据表。这作为我们使用MySQL数据库的入门已经是足够了，但是在关系型数据库的设计中还有很多其它的东西。在这一章中，我们会对我们的例子进行扩充，学习一些有关MySQL的新知识，并试图理解并掌握关系型数据库所能提供的功能。<br /><br />首先，我们得说明我们对许多问题的解决只是不正规的（也就是说非正式的）。正如你在许多计算机科学专业中了解的那样，数据库设计是一个严肃的领域，数据库设计必须包括对它的测试并会涉及到一些数学的原理。但这些可能是超过我们这篇文章的范围了。要得到更多的信息，你可以停下来到http://www.datamodel.org/去看看，在那儿你可以看到许多好的书籍，并得到一些关于这个问题的有用的资源。<br /><br /><b>给予应有的权限</b><br />在开始之前，让我们回忆一下我们的Jokes数据表的结构，这个表包含三个列：ID、JokeText和 JokeDate。这些列可以使我们标识笑话（ID），明了他们的内容（JokeText）以及他们被加入的时间（JokeDate）。<br /><br />现在我们想要保存我们的笑话中的其它一些信息：提交者的姓名。这看上去很自然，我们需要在我们的Jokes数据表中添加一个新的列。SQL的ALTER命令（我们在之前没看到过这个命令）可以帮助我们完成这件事。使用mysql命令行程序登录到MySQL服务器，选择你的数据库（如果你使用我们在第二章中的命名，数据库名应该是joke），然后输入下面的命令：<br /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6">mysql&gt; ALTER TABLE Jokes ADD COLUMN<br />-&gt; AuthorName VARCHAR(100);<br /></td></tr></tbody></table><br />这将会在我们的数据表中增加一个叫AuthorName的列。其数据类型是一个可变长度的字符串，其最大长度是100个字符（这对于最复杂的名字应该也是足够了）。让我们再添加一列用来保存作者的e-mail地址：<br /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6">mysql&gt; ALTER TABLE Jokes ADD COLUMN<br />-&gt; AuthorEMail VARCHAR(100);<br /></td></tr></tbody></table><br />要得到更多的有关ALTER命令的信息，请参看MySQL参考手册。要确认我们是不是正确地添加了两列，你可以要求MySQL为我们对这个表进行描述：<br /><img src="http://www0.ccidnet.com/tech/web/2001/12/03/image/5-01.gif" /><br />看上去很不错。明显地，我们需要对我们在第四章中建立的添加新笑话的HTML以及PHP格式的代码进行调整，但是我们会把这留给你作为一个练习。使用UPDATE查询，你现在可以对表中的所有笑话添加作者的详细资料。然而，在你开始接受这个数据结构之前，我们必须考虑一下我们在这儿选择的设计是否确当。在这种情况下，我们会发现一些我们还没有做到的事情。<br /><br /><b>一个基本的规则：保持事物的分离</b><br />在你建立数据库驱动的网站的过程中，你已经觉得仅仅是有一个笑话列表是不够的。事实上，除了你自己的笑话以外，你开始接收其他人提交的笑话。你决定做一个让全世界人都可以共享笑话的网站。你有没有听说过Internet电影数据库（IMDB）？实际上你现在做的是Internet笑话数据库（IJDB）！对每一个笑话添加作者的姓名和e-mail地址肯定是最容易想到的办法，但是这种方法会导致一些潜在的问题：<br /><br />如果一个经常投稿的名叫Joan Smith的人改变了她的e-mail地址将会发生什么什么情况呢？她会开始使用新地址来提交新的笑话，但是对于所有的旧笑话，你所能看到的还是旧的地址。从你的数据库来看，你也许只能认为有两人名字都叫Joan Smith的人在向你的数据库中提交笑话。如果她是特别体贴的，她也许会通知你改变地址，你可以将所有的旧笑话改成新的地址，但是如果你遗漏了一个，那就意味着你的数据库中存储了错误的信息。数据库设计专家将这种类型的问题称之为一个“更正异常”。<br /><br />很自然地你会想到从你的数据库中得到所有曾经向你的站点提交过笑话的人的列表。实际上，你可以使用下面的查询很容易地得到这样的列表：<br /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6">mysql&gt; SELECT DISTINCT AuthorName, AuthorEMail -&gt; FROM Jokes;</td></tr></tbody></table><br />上面查询中DISTINCT是告诉MySQL不输出重复的结果行。例如，如果Joan Smith向我们的站点提交过20个笑话，如果我们使用了DISTINCT选项，她的名字和e-mail地址将会只在列表中出现一次，否则会出现20次。<br /><br />如果因为某种原因，你决定要从数据库中删除某个特定的作者所提交的所有笑话，但是，与此同时，你将不能再通过e-mail与他们联系！而你的e-mail清单可能是你的网站的收入的主要来源，所以你并不想只因为你不喜欢他们提交的笑话，就删除他们的e-mail地址。数据库设计专家将这称之为“删除异常”。<br /><br />你并不能保证不会出现这样的情况：Joan Smith输入的姓名一会儿是“Joan Smith”，一会儿是“J. Smith”，一会儿又是“Smith, Joan”。这将使得你要确定一个特定的作者变得非常困难（特别是Joan Smith又经常使用几个不同的email地址的时候）。<br /><br />这些问题的解决其实很简单。只要你不再将作者的信息存储到Jokes数据表中，而是建立一个新的数据表来存储作者列表。因为我们在Jokes数据表中使用了一个叫ID的列来用一个数据标识每个笑话，所以我们在新的数据表中使用了同样名字的列来标识我们的作者。我们可以在我们的Jokes表中使用“author ID's”来建立笑话和他的作者之间的关联。全部的数据库设计应该是这样的： <br /><div align="center"><img src="http://www0.ccidnet.com/tech/web/2001/12/03/image/5-04.gif" /></div><br />上面的两个表包含了三个笑话和两个作者。Jokes表的AID列（“Author ID”的缩写）提供了两个表之间的关联（指出Kevin Yank 提交了笑话1和笑话2，Joan Smith提交了笑话3）。在这里，你还需要注意到每一个作者只会在数据库中出现一次，而且他们是独立于他们提交的笑话而存在的，因此我们已经解决了我们上面提出的那些问题。<br /><br />这个数据库设计的最重要的特征是，因为我们要存储两种类型的事物（笑话和作者），所以我们设计两个表。这是我们在数据库设计中要遵守的一个基本规则：对于每一个要存储其信息的实体（或事物），我们都应该给他一个自己的表。<br /><br />重新生成上面的数据是非常简单的（只要使用两个CREATE TABLE 查询就行了），但是因为我们想要在做这些变动时不会有破坏性的效果（也就是说不会丢失我们已经存入的笑话），所以我们需要再次使用ALTER命令。 首先，我们删除Jokes表中有关作者的列：<br /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6">mysql&gt; ALTER TABLE Jokes DROP COLUMN AuthorName;<br />Query OK, 0 rows affected (0.00 sec)<br />Records: 0 Duplicates: 0 Warnings: 0<br />mysql&gt; ALTER TABLE Jokes DROP COLUMN AuthorEMail;<br />Query OK, 0 rows affected (0.00 sec)<br />Records: 0 Duplicates: 0 Warnings: 0<br /></td></tr></tbody></table><br />现在我们建立我们的新的数据表： <br /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6">mysql&gt; CREATE TABLE Authors (<br />-&gt; ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY,<br />-&gt; Name VARCHAR(100),<br />-&gt; EMail VARCHAR(100)<br />-&gt; );<br /></td></tr></tbody></table><br />最后，我们在我们的Jokes表中添加AID列：<br />mysql&gt; ALTER TABLE Jokes ADD COLUMN AID INT;<br />现在剩下来的就是向新的表中添加一些作者，并通过填充AID列来对数据库中已经存在的笑话指定作者。<br /><br /><b>处理多个表</b><br />现在我们的数据被分布在两个表当中，要从其中获得数据看上去变得更加复杂了。例如，我们最初的目标是：显示一个笑话的列表并在每一个笑话后面显示作者的姓名和e-mail地址。在我们的单表结构中，要获得所有的信息，只需要在我们的PHP代码中使用一个SELECT语句就行了：<br /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6">$jokelist = mysql_query(<br />"SELECT JokeText, AuthorName, AuthorEMail ".<br />"FROM Jokes");<br />while ($joke = mysql_fetch_array($jokelist)) {<br />$joketext = $joke["JokeText"];<br />$name = $joke["AuthorName"];<br />$email = $joke["AuthorEMail"]; // Display the joke with author information<br />echo( "&lt;P&gt;$joketext&lt;BR&gt;" .<br />"(by ＜HREF='mailto:$email'＞$name)&lt;/P&gt;" );<br />}<br /></td></tr></tbody></table><br />在我们的新系统中，这样做初看起来是不可能了。因为有关每个笑话的作者的详细资料不是存储在Jokes表中，我们可能想到的一个解决方案是我们对于我们想要显示的笑话单独地获得这些资料。代码将是这样的：<br /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6">// Get the list of jokes<br />$jokelist = mysql_query(<br />"SELECT JokeText, AID FROM Jokes");<br />while ($joke = mysql_fetch_array($jokelist)) {<br />// Get the text and Author ID for the joke<br />$joketext = $joke["JokeText"];<br />$aid = $joke["AID"];<br />// Get the author details for the joke<br />$authordetails = mysql_query(<br />"SELECT Name, Email FROM Authors WHERE ID=$aid");<br />$author = mysql_fetch_array($authordetails);<br />$name = $author["Name"];<br />$email = $author["EMail"];<br />// Display the joke with author information<br />echo( "&lt;P&gt;$joketext&lt;BR&gt;" .<br />"(by ＜A HREF='mailto:$email'＞$name)&lt;/P&gt;" );<br />}<br /></td></tr></tbody></table><br />很混乱，而且对于每一个显示的笑话都包含了一个对数据库的查询，这将会我们的页面的显示非常缓慢。现在看来，“老方法”可能是更好的解决方案，尽管它有其自身的弱点。<br />幸运的是，关系型数据库可以很容易地处理多个表中的数据！在SELECT语句中使用一个新的被称之为“join”的格式，我们可以找到两全其美的办法。连接可以使我们象对存储在单个表中的数据那样对待多个表中的关联数据。一个连接的格式应该是这样的：<br /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6">mysql&gt; SELECT &lt;columns&gt; FROM &lt;tables&gt;<br />-&gt; WHERE &lt;condition(s) for data to be related&gt;;<br /></td></tr></tbody></table><br />在我们目前的情况下，我们所需要的列是Jokes表中的JokeText列以及Authors表中的Name列和Email列。Jokes表和Authors表的关联条件是Jokes表中的AID列的值等于Authors表中的ID列的值。下面是一个连接的例子（前两个查询只是用来显示我们的两个表中所包含的内容）：<br /><img src="http://www0.ccidnet.com/tech/web/2001/12/03/image/5-02.gif" /><br />现在明白了吗？第三个SELECT的结果就是一个连接，它将存储在两个表中的数据关联数据显示到了一个结果表中，尽管我们的数据是存储在两个表中的，我们仍然可以使用一个数据库查询就获得我们的Web页面所需要的笑话列表的全部信息。<br /><br />在这里，要注意一个问题，因为在两个表中都有一个叫ID的列，所以我们在用到Authors表中的ID列时我们必须指定表名（Authors.ID）。如果我们没有指定表名，MySQL将无法知道我们指的是哪一个表中的ID，这会导致这样的一个错误：<br /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6">mysql&gt; SELECT LEFT(JokeText,20), Name, Email<br />-&gt; FROM Jokes, Authors WHERE AID = ID;<br />ERROR 1052: Column: 'ID' in where clause is ambiguous<br /></td></tr></tbody></table><br />现在我们知道如何有效率地从我们的两个表中获取信息了，我们可以利用连接来重新编写我们的笑话列表的程序：<br /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6">$jokelist = mysql_query(<br />"SELECT JokeText, Name, EMail " .<br />"FROM Jokes, Authors WHERE AID=Authors.ID");<br />while ($joke = mysql_fetch_array($jokelist)) {<br />$joketext = $joke["JokeText"];<br />$name = $joke["Name"];<br />$email = $joke["EMail"];<br />// Display the joke with author information<br />echo( "&lt;P&gt;$joketext&lt;BR&gt;" .<br />"(by ＜A HREF='mailto:$email'＞$name)&lt;/P&gt;" );<br />}<br /></td></tr></tbody></table><br />随着你对数据库的使用，你会越来越发现连接的功能有多大的意义。例如，下面的查询用来显示所有由Joan Smith写的笑话：<br /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6">mysql&gt; SELECT JokeText FROM Jokes, Authors WHERE<br />-&gt; Name="Joan Smith" AND AID=Authors.ID;<br /></td></tr></tbody></table><br />上面的查询的输出结果仅仅来源于Jokes表，但是我们使用了一个连接来通过存储在Authors表中的值搜索笑话。在我们的这篇文章中会有更多的这样的精巧的查询，在实际应用中，连接是经常会被使用的，而且在绝大多数的情况下，这会很大程度地简化我们的工作！<br /><br /><b>简单的数据关系</b><br />对于给定的情况的最好的数据模型往往决定于我们所工作的两种数据之间的关系类型。我这篇文章中，我们将对典型的关系类型进行研究，并学会如何在一个关系型数据中用最好的方法描述它。<br /><br />对于简单的一对一的关系，只要用一个表就足够了。一对一关系的一个例子就是我们在前面已经看到的在笑话数据库中的每一个作者的e-mail地址。因为对于每一个作者只有一个e-mail地址，而且对于一个e-mail地址对应的也只有一个作者，将它们分到两个数据库中是没有道理的。<br /><br />多对一的关系可能会稍微复杂一点，但是在之前其实我们也已经解决了这个问题，我们的数据库中的每一个笑话只会有一个作者，但是同一个作者可能写了很多笑话。笑话和作者之间的关系就是一个多对一的关系。我们曾经有过一个初步的解决方案，那就是将与这个笑话关联的作者的信息也促成在同一个数据库中。但是这样做，对于同一个数据会有许多拷贝，这不仅会在同步上造成困难，而且会浪费空间。将数据分开到两个数据表中并使用一个ID列来连接两个表（象上面所说的那样使用连接），所有的问题会得到很好的解决。<br /><br />到目前为止，我们还没接触到一对多的关系，但是想象这样的一个关系应该是不困难的。在我们之前建立的数据库中，我们假定一个作者只有一个e-mail地址。事实上情况并不总是这样的，作出这个限制的理由只是因为我们只需要一个e-mail地址来与作者联系。我们简单地假设了作者总会输入他们常用的e-mail地址，或者至少是一个正常使用的e-mail地址。如果我们想要支持多个e-mail地址，我们将面对一个一对多的关系（一个作者会有几个e-mail地址，但是一个e-mail地址只会与一个确定的作者对应）。<br /><br />一个没有经验的数据库设计者面对一个一对多的关系时，他首先会想到的是试图把多个数据存储到一个数据库域中，就象这样：<br /><div align="center"><img src="http://www0.ccidnet.com/tech/web/2001/12/03/image/5-05.gif" /></div><br />这种结构在投入使用后，要从数据库中获得一个单个的e-mail地址，将不得不通过搜索逗号（或者你所选择的用来分隔的其他符号）来分割字符串，这样做并不简单，而且会很耗时。设想一下如果要用PHP来删除某个作者的某个e-mail地址，那也将会是很困难的事。另外，对于EMail列我们需要很长的长度，这会导致磁盘空间的浪费，因为大多数的作者都只会有一个e-mail地址。<br /><br />解决一对多的关系和我们上面解决多对一的关系是非常类似的。实际上两者之前只是一个简单的颠倒。我们可将Authors表分成两个表，Authors和EMails，然后在EMails表中使用作者的ID（AID）这样的一个列来实现两个表之间的连接：<br /><div align="center"><img src="http://www0.ccidnet.com/tech/web/2001/12/03/image/5-06.gif" /></div><br />使用一个连接，显示某个作者的所有E-mail地址将会是很简单的：<br /><img src="http://www0.ccidnet.com/tech/web/2001/12/03/image/5-03.gif" /><br /><b>多对多的关系</b><br />Ok，现在你有了一个发布在你的网站上的稳定增长的笑话数据库。事实上，这种增长是非常迅速的，笑话的数量会变得难以管理！你的访问者将面对一个庞大的页面，在这个页面上杂乱地排列了数以百计的笑话。现在，我们不得不考虑作一些变动了。<br /><br />你决定将你的笑话放置到不同的目录中，这些目录可能是“Knock-Knock笑话”、“Crossing the Road笑话”、“Lawyer笑话”和“Political笑话”。记住我们之前的处理规则，因为我们的笑话目录是一个不同类型的“事物”，所以我们要为它们建立一个新的数据表：<br /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6">mysql&gt; CREATE TABLE Categories (<br />-&gt; ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY,<br />-&gt; Name VARCHAR(100),<br />-&gt; Description TEXT<br />-&gt; );<br />Query OK, 0 rows affected (0.00 sec)<br /></td></tr></tbody></table><br />对你的笑话定义其所属目录将会是一个困难的任务。因为一个“political”笑话可能也是一个“crossing the road”笑话，同样，一个“knock-knock”可能也是一个“lawyer”笑话。一个单个的笑话可能属于许多目录，每一个目录也会包含许多笑话。这是一个多对多的关系。<br /><br />许多没有经验的设计者又会想到将几个数据存储到一个列中，最直接的解决方案是在Jokes表中增加Categories列，并在其中列举笑话所属的目录的ID。现在适用我们的第二个处理规则了：如果你需要在一个列中存储多个值，那证明你的设计可能是有缺陷的。<br /><br />描述一个多对多关系的正确方法是使用一个“lookup”表。这个表不包含任何实际的数据，只是用来定义关联的事物。这儿是我们这部分的数据库设计的示意图：<br /><div align="center"><img src="http://www0.ccidnet.com/tech/web/2001/12/03/image/5-07.gif" /></div><br />JokeLookup 表将笑话的ID（JID）的目录的ID（CID）进行了关联。从上面的例子我们可以看出，以“How many lawyers...”开头的笑话既属于“Lawyer”目录，又属于“Light Bulb”目录。<br /><br />建立lookup表的方法和建立其他表的方法基本一样。不同点在于选择主键。我们之前所建立的每一个表都有一个名为ID的列，这一列被我们定义为PRIMARY KEY。将一个列定义为主键意味着这一列不会出现重复值。而且可以加快基于这一列的连接操作的速度。 <br /><br />对于我们的lookup表来说，没有一个单个的列可以保证不出现重复值。每一个笑话可以属于几个目录，所以一个joke ID可能会出现多次；同样的，一个目录可能包含多个笑话，所以一个category ID也可能会出现多次。我们所要求的只是相同的数据对不应重复出现。因为我们这个表的唯一作用就是用来实现连接，所以使用主键来提高连接操作的速度对我们肯定有价值。所以，我们通常会为lookup表建立一个多列的主键：<br /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6">mysql&gt; CREATE TABLE JokeLookup (<br />-&gt; JID INT NOT NULL,<br />-&gt; CID INT NOT NULL,<br />-&gt; PRIMARY KEY(JID,CID)<br />-&gt; );</td></tr></tbody></table><br />现在我们的表中的JID和CID共同组成了这个表的主键。保持lookup表中数据的唯一性是有价值的（防止重复定义某一个笑话属于某一个目录），而且这会提高这个表用来连接时的速度。<br />使用我们的lookup表中包含的目录分配，我们可以使用连接来建立几个有趣而且非常实用的查询。下面的查询列出了“Knock-Knock”目录下的所有笑话：<br /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6">mysql&gt; SELECT JokeText<br />-&gt; FROM Jokes, Categories, JokeLookup<br />-&gt; WHERE Name="Knock-Knock" AND<br />-&gt; CID=Categories.ID AND JID=Jokes.ID;<br /></td></tr></tbody></table><br />下面这个查询列举了以“How many lawyers...”开头的笑话所属的所有目录： <br /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6">mysql&gt; SELECT Categories.Name<br />-&gt; FROM Jokes, Categories, JokeLookup<br />-&gt; WHERE JokeText LIKE "How many lawyers%"<br />-&gt; AND CID=Categories.ID AND JID=Jokes.ID;<br /></td></tr></tbody></table><br />下面的查询，同时使用了我们的Authors表形成了一个四个表的连接（！！！），列举了写过 Knock-Knock笑话的所有作者的名字：<br /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6">mysql&gt; SELECT Authors.Name<br />-&gt; FROM Jokes, Authors, Categories, JokeLookup<br />-&gt; WHERE Categories.Name="Knock-Knock"<br />-&gt; AND CID=Categories.ID AND JID=Jokes.ID<br />-&gt; AND AID=Authors.ID;<br /></td></tr></tbody></table><br /><b>结语</b><br />这一章中，我们学习了正确的数据库设计的基本原则，以及MySQL（实际上，对其他关系型数据库同样适用）如何对描述事件之间的不同类型的关系提供支持。我们不仅仅探讨了一对一的关系，还详细讨论了多对一、一对多以及多对多的关系。<br /><br />在这一过程中，我们还学习了一些有关SQL命令的新的东西。特别的，我们学习了如何使用一个SELECT去连接多个表中的数据并将其反映到一个结果集中。<br /><br />在第六章中，我们将使用我们已经获得的知识，并加上很少的一些新知识，去用PHP构建一个内容管理系统。我们希望这个系统可以提供一个可定制的、安全的、基于Web的界面来管理数据库的内容，而不再是在MySQL命令行中来解决问题。<br /><br />from: <a href="http://www0.ccidnet.com/tech/web/2001/12/03/92_3846.html">http://www0.ccidnet.com/tech/web/2001/12/03/92_3846.html</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/72878.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-09-29 17:37 <a href="http://www.blogjava.net/weidagang2046/articles/72878.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SQL查询某类中的最高分</title><link>http://www.blogjava.net/weidagang2046/articles/63589.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Tue, 15 Aug 2006 01:16:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/63589.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/63589.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/63589.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/63589.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/63589.html</trackback:ping><description><![CDATA[表className中有如下分类:
<p>classID   className<br />1              衣服<br />2              裤子<br />5              帽子<br />10            鞋子</p><p>表productInfo有如下记录:</p><p>productID             productName            parentID            clickNum<br /><br />1                            男士衣服                      1                         90            --衣服类别中这条记录的点击率最高<br />2                            女士衣服                      1                         80<br />3                            男士裤子                      2                         70<br />4                            女士裤子                      2                         90            --裤子类别中这条记录点击率最高<br />5                            男士帽子                      5                         15<br />6                            女士帽子                      5                         30            --帽子类别中这条点击率最高<br />7                            男士鞋子                      10                       65            --鞋子类别中这条点击率最高<br />8                            女士鞋子                      10                       52<br />9                            女士鞋子1                    10                       54</p><p>现在要求分别把衣服,裤子,帽子,鞋子这些类别中点击率最高的一条记录找出来,然后再降序排列,结果应如下:</p><p>productID             productName            clickNum<br />1                            男士衣服                      90<br />4                            女士裤子                      90<br />7                            男士鞋子                      65<br />6                            女士帽子                      30<br /><br /></p><div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" /><span style="COLOR: #0000ff">select</span><span style="COLOR: #000000"> </span><span style="COLOR: #808080">*</span><span style="COLOR: #000000"> </span><span style="COLOR: #0000ff">from</span><span style="COLOR: #000000"> goods </span><span style="COLOR: #0000ff">as</span><span style="COLOR: #000000"> g1 </span><span style="COLOR: #0000ff">where</span><span style="COLOR: #000000"> <br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" /><br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />    </span><span style="COLOR: #808080">not</span><span style="COLOR: #000000"> </span><span style="COLOR: #808080">exists</span><span style="COLOR: #000000"> <br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />    <br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />    (</span><span style="COLOR: #0000ff">select</span><span style="COLOR: #000000"> </span><span style="COLOR: #808080">*</span><span style="COLOR: #000000"> </span><span style="COLOR: #0000ff">from</span><span style="COLOR: #000000"> goods </span><span style="COLOR: #0000ff">as</span><span style="COLOR: #000000"> g2 </span><span style="COLOR: #0000ff">where</span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />         g1.parentId </span><span style="COLOR: #808080">=</span><span style="COLOR: #000000"> g2.parentId </span><span style="COLOR: #808080">and</span><span style="COLOR: #000000"> g2.clickNum </span><span style="COLOR: #808080">&gt;</span><span style="COLOR: #000000"> g1.clickNum<br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />    )</span></div>      <img src ="http://www.blogjava.net/weidagang2046/aggbug/63589.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-08-15 09:16 <a href="http://www.blogjava.net/weidagang2046/articles/63589.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SQL Server存储过程编写和优化措施</title><link>http://www.blogjava.net/weidagang2046/articles/62873.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Thu, 10 Aug 2006 14:33:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/62873.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/62873.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/62873.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/62873.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/62873.html</trackback:ping><description><![CDATA[　　一、适合读者对象：<a class="bluekey" href="http://www.yesky.com/key/421/25421.html" target="_blank"><font color="#002c99">数据库开发</font></a>程序员，数据库的数据量很多，涉及到对SP（<a class="bluekey" href="http://www.yesky.com/key/1652/31652.html" target="_blank"><font color="#002c99">存储过程</font></a>）的优化的项目开发人员，对数据库有浓厚兴趣的人。 　 <br /><br />　　二、介绍：在数据库的开发过程中，经常会遇到复杂的业务逻辑和对数据库的操作，这个时候就会用SP来封装数据库操作。如果项目的SP较多，书写又没有一定的规范，将会影响以后的系统维护困难和大SP逻辑的难以理解，另外如果数据库的数据量大或者项目对SP的性能要求很，就会遇到优化的问题，否则速度有可能很慢，经过亲身经验，一个经过优化过的SP要比一个性能差的SP的效率甚至高几百倍。 　 <br /><br />　　三、内容： 　 <br /><br />　　1、开发人员如果用到其他库的Table或View，务必在当前库中建立View来实现跨库操作，最好不要直接使用“databse.dbo.table_name”，因为sp_depends不能显示出该SP所使用的跨库table或view，不方便校验。　　 <br /><br />　　2、开发人员在提交SP前，必须已经使用set showplan on分析过查询计划，做过自身的查询优化检查。 　 <br /><br />　　3、<a class="bluekey" href="http://www.yesky.com/key/580/30580.html" target="_blank"><font color="#002c99">高程</font></a>序运行效率，优化应用程序，在SP编写过程中应该注意以下几点： 　　 <br /><br />　　a)SQL的使用规范： <br /><br />　　　i.　尽量避免大事务操作，慎用holdlock子句，提高系统并发能力。 <br /><br />　　　ii.　尽量避免反复访问同一张或几张表，尤其是数据量较大的表，可以考虑先根据条件提取数据到临时表中，然后再做连接。 <br /><br />　　　iii.　尽量避免使用<a class="bluekey" href="http://www.yesky.com/key/1401/46401.html" target="_blank"><font color="#002c99">游标</font></a>，因为游标的效率较差，如果游标操作的数据超过1万行，那么就应该改写；如果使用了游标，就要尽量避免在游标循环中再进行表连接的操作。 <br /><br />　　　iv.　注意where字句写法，必须考虑语句顺序，应该根据索引顺序、范围大小来确定条件子句的前后顺序，尽可能的让<a class="bluekey" href="http://www.yesky.com/key/2328/42328.html" target="_blank"><font color="#002c99">字段</font></a>顺序与索引顺序相一致，范围从大到小。 <br /><br />　　　v.　不要在where子句中的“=”左边进行函数、算术运算或其他表达式运算，否则系统将可能无法正确使用索引。 <br /><br />　　　vi.　尽量使用exists代替select count(1)来判断是否存在记录，count函数只有在统计表中所有行数时使用，而且count(1)比count(*)更有效率。 <br /><br />　　　vii.　尽量使用“&gt;=”，不要使用“&gt;”。 <br /><br />　　　viii.　注意一些or子句和union子句之间的替换 <br /><br />　　　ix.　注意表之间连接的数据类型，避免不同类型数据之间的连接。 <br /><br />　　　x.　注意存储过程中参数和数据类型的关系。 <br /><br />　　　xi.　注意insert、update操作的数据量，防止与其他应用冲突。如果数据量超过200个数据页面（400k），那么系统将会进行锁升级，页级锁会升级成表级锁。 　　 <br /><br />　　b)索引的使用规范： <br /><br />　　　i.　索引的创建要与应用结合考虑，建议大的OLTP表不要超过6个索引。 <br /><br />　　　ii.　尽可能的使用索引字段作为查询条件，尤其是聚簇索引，必要时可以通过index index_name来强制指定索引 <br /><br />　　　iii.　避免对大表查询时进行table scan，必要时考虑新建索引。 <br /><br />　　　iv.　在使用索引字段作为条件时，如果该索引是联合索引，那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引，否则该索引将不会被使用。 <br /><br />　　　v.　要注意索引的维护，周期性重建索引，重新编译存储过程。　　 <br /><br />　　c)tempdb的使用规范： <br /><br />　　　i.　尽量避免使用distinct、order by、group by、having、join、cumpute，因为这些语句会加重tempdb的负担。 <br /><br />　　　ii.　避免频繁创建和删除临时表，减少系统表资源的消耗。 <br /><br />　　　iii.　在新建临时表时，如果一次性插入数据量很大，那么可以使用select into代替create table，避免log，提高速度；如果数据量不大，为了缓和系统表的资源，建议先create table，然后insert。 <br /><br />　　　iv.　如果临时表的数据量较大，需要建立索引，那么应该将创建临时表和建立索引的过程放在单独一个子存储过程中，这样才能保证系统能够很好的使用到该临时表的索引。 <br /><br />　　　 v.　如果使用到了临时表，在存储过程的最后务必将所有的临时表显式删除，先truncate table，然后drop table，这样可以避免系统表的较长时间锁定。 <br /><br />　　　 vi.　慎用大的临时表与其他大表的连接查询和修改，减低系统表负担，因为这种操作会在一条语句中多次使用tempdb的系统表。　　 <br />　　d)合理的算法使用： 　　 <br /><br />　　根据上面已提到的SQL优化技术和ASE Tuning手册中的SQL优化内容,结合实际应用,采用多种算法进行比较,以获得消耗资源最少、效率最高的方法。具体可用ASE调优命令：set statistics io on, set statistics time on , set showplan on 等。<br /><br />from: <a href="http://dev.yesky.com/251/2099251.shtml">http://dev.yesky.com/251/2099251.shtml</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/62873.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-08-10 22:33 <a href="http://www.blogjava.net/weidagang2046/articles/62873.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>优化SQL Server索引的小技巧</title><link>http://www.blogjava.net/weidagang2046/articles/62872.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Thu, 10 Aug 2006 14:32:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/62872.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/62872.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/62872.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/62872.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/62872.html</trackback:ping><description><![CDATA[SQL Server中有几个可以让你检测、调整和优化SQL Server性能的工具。在本文中，我将说明如何用SQL Server的工具来优化数据库索引的使用，本文还涉及到有关索引的一般性知识。<br /><br /><font size="2"><strong>关于索引的常识</strong></font><br /><br /><p>影响到数据库性能的最大因素就是索引。由于该问题的复杂性，我只可能简单的谈谈这个问题，不过关于这方面的问题，目前有好几本不错的书籍可供你参阅。我在这里只讨论两种SQL Server索引，即clustered索引和nonclustered索引。当考察建立什么类型的索引时，你应当考虑数据类型和保存这些数据的column。同样，你也必须考虑数据库可能用到的查询类型以及使用的最为频繁的查询类型。</p><h5>索引的类型</h5><p>如果column保存了高度相关的数据，并且常常被顺序访问时，最好使用clustered索引，这是因为如果使用clustered索引，SQL Server会在物理上按升序（默认）或者降序重排数据列，这样就可以迅速的找到被查询的数据。同样，在搜寻控制在一定范围内的情况下，对这些column也最好使用clustered索引。这是因为由于物理上重排数据，每个表格上只有一个clustered索引。</p><p>与上面情况相反，如果columns包含的数据相关性较差，你可以使用nonculstered索引。你可以在一个表格中使用高达249个nonclustered索引——尽管我想象不出实际应用场合会用的上这么多索引。</p><p>当表格使用主关键字（primary keys），默认情况下SQL Server会自动对包含该关键字的column(s)建立一个独有的cluster索引。很显然，对这些column(s)建立独有索引意味着主关键字的唯一性。当建立外关键字（foreign key）关系时，如果你打算频繁使用它，那么在外关键字cloumn上建立nonclustered索引不失为一个好的方法。如果表格有clustered索引，那么它用一个链表来维护数据页之间的关系。相反，如果表格没有clustered索引，SQL Server将在一个堆栈中保存数据页。</p><h5>数据页</h5><p>当索引建立起来的时候，SQLServer就建立数据页（datapage），数据页是用以加速搜索的指针。当索引建立起来的时候，其对应的填充因子也即被设置。设置填充因子的目的是为了指示该索引中数据页的百分比。随着时间的推移，数据库的更新会消耗掉已有的空闲空间，这就会导致页被拆分。页拆分的后果是降低了索引的性能，因而使用该索引的查询会导致数据存储的支离破碎。当建立一个索引时，该索引的填充因子即被设置好了，因此填充因子不能动态维护。</p><p>为了更新数据页中的填充因子，我们可以停止旧有索引并重建索引，并重新设置填充因子（注意：这将影响到当前数据库的运行，在重要场合请谨慎使用）。<i>DBCC INDEXDEFRAG</i>和<i>DBCC DBREINDEX</i>是清除clustered和nonculstered索引碎片的两个命令。<i>INDEXDEFRAG</i>是一种在线操作（也就是说，它不会阻塞其它表格动作，如查询），而<i>DBREINDEX</i>则在物理上重建索引。在绝大多数情况下，重建索引可以更好的消除碎片，但是这个优点是以阻塞当前发生在该索引所在表格上其它动作为代价换取来得。当出现较大的碎片索引时，<i>INDEXDEFRAG</i>会花上一段比较长的时间，这是因为该命令的运行是基于小的交互块（transactional block）。</p><h5>填充因子</h5><p>当你执行上述措施中的任何一个，数据库引擎可以更有效的返回编入索引的数据。关于填充因子（fillfactor）话题已经超出了本文的范畴，不过我还是提醒你需要注意那些打算使用填充因子建立索引的表格。</p><p>在执行查询时，SQL Server动态选择使用哪个索引。为此，SQL Server根据每个索引上分布在该关键字上的统计量来决定使用哪个索引。值得注意的是，经过日常的数据库活动（如插入、删除和更新表格），SQL Server用到的这些统计量可能已经“过期”了，需要更新。你可以通过执行<i>DBCC SHOWCONTIG</i>来查看统计量的状态。当你认为统计量已经“过期”时，你可以执行该表格的<i>UPDATE STATISTICS</i>命令，这样SQL Server就刷新了关于该索引的信息了。</p><h5>建立数据库维护计划</h5><p>SQL Server提供了一种简化并自动维护数据库的工具。这个称之为数据库维护计划向导（Database Maintenance Plan Wizard ，DMPW）的工具也包括了对索引的优化。如果你运行这个向导，你会看到关于数据库中关于索引的统计量，这些统计量作为日志工作并定时更新，这样就减轻了手工重建索引所带来的工作量。如果你不想自动定期刷新索引统计量，你还可以在DMPW中选择重新组织数据和数据页，这将停止旧有索引并按特定的填充因子重建索引。<br /><br />from: <a href="http://www.zdnet.com.cn/developer/database/story/0,3800066906,39109102,00.htm">http://www.zdnet.com.cn/developer/database/story/0,3800066906,39109102,00.htm</a></p><img src ="http://www.blogjava.net/weidagang2046/aggbug/62872.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-08-10 22:32 <a href="http://www.blogjava.net/weidagang2046/articles/62872.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>“用户登录失败。原因：未与信任 SQL Server 连接相关联”的解决方法</title><link>http://www.blogjava.net/weidagang2046/articles/62838.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Thu, 10 Aug 2006 11:49:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/62838.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/62838.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/62838.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/62838.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/62838.html</trackback:ping><description><![CDATA[
		<p>建好SQL数据库，设置好了用户名与密码，连接也好了，却出现了上述问题， 原困是未设置SQL SERVER登录认证模式为混合认证模式，因为SQL SERVER默认安装后认证模式为WINDOWS认证模式，从而导致出错。</p>
		<p>解决方法：</p>
		<ol>
				<li>启动SQLSERVER企业管理器，选择要进行认证模式设置的服务器。右击该服务器，在弹出菜单中选择属性，SQL SERVER将弹出属性对话框</li>
		</ol>
		<p>
				<img height="483" alt="sql1" src="http://www.lunji.com/faq/loginerror/sql1.jpg" width="572" />
				<br />
				<br />2. 在属性对话框中选择安全性选项，在身份验证处选择“SQL Server和Windows”，然后确定。<br /><br /><img height="488" alt="sql2" src="http://www.lunji.com/faq/loginerror/sql2.jpg" width="404" /><br /><br />from: <a href="http://www.lunji.com/faq/login_error.htm">http://www.lunji.com/faq/login_error.htm</a></p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/62838.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-08-10 19:49 <a href="http://www.blogjava.net/weidagang2046/articles/62838.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SQL Server 2000的安全配置</title><link>http://www.blogjava.net/weidagang2046/articles/62729.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Thu, 10 Aug 2006 02:21:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/62729.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/62729.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/62729.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/62729.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/62729.html</trackback:ping><description><![CDATA[SQL Server 2000的安全配置在进行SQL Server 2000数据库的安全配置之前，首先你必须对操作系统进行安全配置，保证你的操作系统处于安全状态。然后对你要使用的操作数据库软件（程序）进行必要的安全审核，比如对ASP、PHP等脚本，这是很多基于数据库的WEB应用常出现的安全隐患，对于脚本主要是一个过滤问题，需要过滤一些类似 , ‘ ; @ / 等字符，防止破坏者构造恶意的SQL语句。接着，安装SQL Server2000后请打上补丁sp1以及最新的sp2。<br /><br />　　下载地址是：<a href="http://www.microsoft.com/sql/downloads/2000/sp1.asp"><font color="#666666">http://www.microsoft.com/sql/downloads/2000/sp1.asp</font></a>　<br />和 <a href="http://www.microsoft.com/sql/downloads/2000/sp2.asp"><font color="#666666">http://www.microsoft.com/sql/downloads/2000/sp2.asp</font></a><br />在做完上面三步基础之后，我们再来讨论SQL Server的安全配置。<br /><br />　　<strong>1、使用安全的密码策略</strong><br /><br />　　我们把密码策略摆在所有安全配置的第一步，请注意，很多数据库帐号的密码过于简单，这跟系统密码过于简单是一个道理。对于sa更应该注意，同时不要让sa帐号的密码写于应用程序或者脚本中。健壮的密码是安全的第一步！SQL Server2000安装的时候，如果是使用混合模式，那么就需要输入sa的密码，除非你确认必须使用空密码。这比以前的版本有所改进。同时养成定期修改密码的好习惯。数据库管理员应该定期查看是否有不符合密码要求的帐号。<br /><br />　　比如使用下面的SQL语句：<br />　　Use master<br />　　Select name,Password from syslogins where password is null<br /><br />　　<strong>2、使用安全的帐号策略</strong><br /><br />　　由于SQL Server不能更改sa用户名称，也不能删除这个超级用户，所以，我们必须对这个帐号进行最强的保护，当然，包括使用一个非常强壮的密码，最好不要在数据库应用中使用sa帐号，只有当没有其它方法登录到 SQL Server 实例（例如，当其它系统管理员不可用或忘记了密码）时才使用 sa。建议数据库管理员新建立个拥有与sa一样权限的超级用户来管理数据库。安全的帐号策略还包括不要让管理员权限的帐号泛滥。<br /><br />　　SQL Server的认证模式有Windows身份认证和混合身份认证两种。如果数据库管理员不希望操作系统管理员来通过操作系统登陆来接触数据库的话，可以在帐号管理中把系统帐号“BUILTIN\Administrators”删除。不过这样做的结果是一旦sa帐号忘记密码的话，就没有办法来恢复了。很多主机使用数据库应用只是用来做查询、修改等简单功能的，请根据实际需要分配帐号，并赋予仅仅能够满足应用要求和需要的权限。比如，只要查询功能的，那么就使用一个简单的public帐号能够select就可以了。<br /><br />　　<strong>3、加强数据库日志的记录</strong><br /><br />　　审核数据库登录事件的“失败和成功”，在实例属性中选择“安全性”，将其中的审核级别选定为全部，这样在数据库系统和操作系统日志里面，就详细记录了所有帐号的登录事件。请定期查看SQL Server日志检查是否有可疑的登录事件发生，或者使用DOS命令。findstr /C:"登录" d:\Microsoft SQL Server\MSSQL\LOG\*.*<br /><br />　　<strong>4、管理扩展存储过程</strong><br /><br />　　对存储过程进行大手术，并且对帐号调用扩展存储过程的权限要慎重。其实在多数应用中根本用不到多少系统的存储过程，而SQL Server的这么多系统存储过程只是用来适应广大用户需求的，所以请删除不必要的存储过程，因为有些系统的存储过程能很容易地被人利用起来提升权限或进行破坏。如果你不需要扩展存储过程xp_cmdshell请把它去掉。使用这个SQL语句： <br />use master <br />sp_dropextendedproc 'xp_cmdshell'<br />xp_cmdshell是进入操作系统的最佳捷径，是数据库留给操作系统的一个大后门。如果你需要这个存储过程，请用这个语句也可以恢复过来。<br />sp_addextendedproc 'xp_cmdshell', 'xpsql70.dll'<br />如果你不需要请丢弃OLE自动存储过程（会造成管理器中的某些特征不能使用），<br />这些过程包括如下： <br />Sp_OACreate Sp_OADestroy Sp_OAGetErrorInfo Sp_OAGetProperty<br />Sp_OAMethod Sp_OASetProperty Sp_OAStop <br />去掉不需要的注册表访问的存储过程，注册表存储过程甚至能够读出操作系统管理员的密码来，如下： <br />Xp_regaddmultistring Xp_regdeletekey Xp_regdeletevalue <br />Xp_regenumvalues Xp_regread Xp_regremovemultistring <br />Xp_regwrite<br />还有一些其他的扩展存储过程，你也最好检查检查。在处理存储过程的时候，请确认一下，避免造成对数据库或应用程序的伤害。<br /><br />　　<strong>5、使用协议加密</strong><br /><br />　　SQL Server 2000使用的Tabular Data Stream协议来进行网络数据交换，如果不加密的话，所有的网络传输都是明文的，包括密码、数据库内容等等，这是一个很大的安全威胁。能被人在网络中截获到他们需要的东西，包括数据库帐号和密码。所以，在条件容许情况下，最好使用SSL来加密协议，当然，你需要一个证书来支持。<br /><br />　　<strong>6、不要让人随便探测到你的TCP/IP端口</strong><br /><br />　　默认情况下，SQL Server使用1433端口监听，很多人都说SQL Server配置的时候要把这个端口改变，这样别人就不能很容易地知道使用的什么端口了。可惜，通过微软未公开的1434端口的UDP探测可以很容易知道SQL Server使用的什么TCP/IP端口了。不过微软还是考虑到了这个问题，毕竟公开而且开放的端口会引起不必要的麻烦。在实例属性中选择TCP/IP协议的属性。选择隐藏 SQL Server 实例。如果隐藏了 SQL Server 实例，则将禁止对试图枚举网络上现有的 SQL Server 实例的客户端所发出的广播作出响应。这样，别人就不能用1434来探测你的TCP/IP端口了（除非用Port Scan）。<br /><br />　　<strong>7、修改TCP/IP使用的端口</strong><br /><br />　　请在上一步配置的基础上，更改原默认的1433端口。在实例属性中选择网络配置中的TCP/IP协议的属性，将TCP/IP使用的默认端口变为其他端口.<br /><br />　　<strong>9、拒绝来自1434端口的探测<br /></strong><br />　　由于1434端口探测没有限制，能够被别人探测到一些数据库信息，而且还可能遭到DOS攻击让数据库服务器的CPU负荷增大，所以对Windows 2000操作系统来说，在IPSec过滤拒绝掉1434端口的UDP通讯，可以尽可能地隐藏你的SQL Server。<br /><br />　　<strong>10、对网络连接进行IP限制</strong><br /><br />　　SQL Server 2000数据库系统本身没有提供网络连接的安全解决办法，但是Windows 2000提供了这样的安全机制。使用操作系统自己的IPSec可以实现IP数据包的安全性。请对IP连接进行限制，只保证自己的IP能够访问，也拒绝其他IP进行的端口连接，把来自网络上的安全威胁进行有效的控制。关于IPSec的使用请参看：<a href="http://www.microsoft.com/china/technet/security/ipsecloc.asp"><font color="#666666">http://www.microsoft.com/china/technet/security/ipsecloc.asp</font></a><br /><br />　　上面主要介绍的一些SQL Server的安全配置，经过以上的配置，可以让SQL Server本身具备足够的安全防范能力。当然，更主要的还是要加强内部的安全控制和管理员的安全培训，而且安全性问题是一个长期的解决过程，还需要以后进行更多的安全维护。<br /><br />from: <a href="http://vod.sjtu.edu.cn/help/Article_Show.asp?ArticleID=202">http://vod.sjtu.edu.cn/help/Article_Show.asp?ArticleID=202</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/62729.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-08-10 10:21 <a href="http://www.blogjava.net/weidagang2046/articles/62729.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于 PostgreSQL 备份恢复的心得</title><link>http://www.blogjava.net/weidagang2046/articles/3455.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Tue, 19 Apr 2005 05:39:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/3455.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/3455.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/3455.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/3455.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/3455.html</trackback:ping><description><![CDATA[<FONT size=2>这段时间对 PostgreSQL 的备份恢复进行了一些研究, 有一些心得和大家分享一下.<BR><BR>我们知道, PostgreSQL 拥有 WAL(预写式日志) 已经有一段时间了.<BR>WAL 的一个重要好处就是能在系统崩溃(数据库崩溃甚至操作系统崩溃)的情况下,<BR>仍然能够保证数据的安全. 理想情况下就是恢复到系统崩溃前一刻的一致状态.<BR><BR>WAL 是如何实现这一点的呢? 这里简单探讨一下.<BR><BR>PostgreSQL 数据目录中包括一个子目录叫 pg_xlog, 这里包含一些很 "整齐" 的文件<BR>(文件名全都是16进制的数, 大小都是16MB(默认情况下)). <BR>这些文件是联机重做日志文件, 也就是很多 PostgreSQL 文档中说的 XLog.<BR>预写式日志就表现在对 XLog 操作上. 当一个事务要提交, 已被修改的数据<BR>必须先被写到(严格来说应该是追加)到 XLog 中, 事务才能标识为 "已提交".<BR>这样, 就算主数据文件在崩溃中已经包含不完整的数据了, 仍然可以通过下面<BR>的方法恢复到一致状态:<BR><BR>1 找到前一个一致的数据库状态点(这称为CheckPoint)<BR>2 将 XLog 以快进的方式重新施加到主数据文件上, 直到崩溃前的时刻.<BR><BR>从这个角度来将, PostgreSQL 已经做得非常好了, 但仍然有一些问题.<BR>比如, 如果介质(磁盘)发生故障, 整个数据库文件, 包括主数据文件和日志<BR>都不能读取, 又该怎么办呢?<BR><BR>目前, 我们只能定时通过 pg_dump 等工具把整个数据库 dump 下来, 或者<BR>关闭数据库, 将数据目录整个复制到另外的地方. <BR>然后使用这样的备份来恢复由介质故障引起的数据库灾难.<BR>很显然, 这不能满足我们的要求, 通常, 我们都不可能以非常高<BR>的频率进行 pg_dump. 一旦发生灾难, 就最多恢复到上一次执行 pg_dump 的时刻了.<BR><BR>没有更好的办法了吗?<BR><BR>如果我们能对数据库的做不间断的增量备份, 不就可以到达我们的目的了吗?<BR>这个想法到是好, 可怎么捕捉对数据文件做的修改(同时还不要忘记事务的原子性)? <BR>对数据文件的修改是分布于整个数据文件各处的, 很难对它们进行<BR>所以, 这个方法是不现实的.<BR><BR>现实的方法还是要通过 WAL 系统来实现. 请注意前面我们已经讨论过的, 对 XLog <BR>的写实际上是追加. 这一点使得要增量备份 XLog 成为可能.<BR>目前, PostgreSQL 为了能限制 XLog 的大小, 采用了多个段(也就是多个文件)的方式,<BR>循环利用磁盘空间 -- 当写满前一个 XLog 文件, 就产生一个新的, 并且让<BR>文件名代表 XLog 的编号, 同时, 如果可能, 删除过期的 XLog 文件.<BR>如果我们能在 PostgreSQL 删除过期的 XLog 之前将它们复制到另外一个磁盘甚至其他<BR>计算机, 不就能够实现增量备份日志了吗? <BR>当灾难发生的时候, 就可以在一个完整备份的基础上, 连续施加备份的 XLog 进行<BR>redu, 直至恢复到最后一次归档(复制到其他目录或计算机)的日志.<BR><BR>实际上, 这种方法也正是 Oracle 数据库的归档模式所采用的方法.<BR><BR>下面这个实验可以加深理解<BR><BR>1 初始化数据库目录<BR><BR>$ initdb -D db<BR><BR>2 创建数据库<BR><BR>$ pg_ctl -D db start<BR>$ createdb test<BR>$ psql test<BR>test=# create table t(a int);<BR>test=# insert into t values(1);<BR>test=# insert into t values(2);<BR>test=# insert into t values(3);<BR>test=# \q<BR><BR><BR>3 全备份数据库: 在不关闭数据库(也就是说, 不要运行 pg_ctl -D db stop)<BR>的情况下复制数据库目录. 注意, 这里采用了一种非常规手段, 仅仅是为了实验, <BR>不要在正式应用中使用. 目的是为了让将来的恢复能自动开始.<BR><BR>$ cp -a db db.backup<BR><BR>4 继续修改数据库<BR><BR>$ psql test<BR>test=# insert into t values(100);<BR>test=# insert into t values(200);<BR>test=# insert into t values(300);<BR>test=# \q<BR><BR>5 备份日志(XLog)文件 (由于修改量很小, 实际上只有一个日志文件)<BR><BR>$ mkdir pg_xlog<BR>$ cp db/pg_xlog/* pg_xlog<BR><BR>6 模拟灾难<BR><BR>$ pg_ctl -D db stop<BR>$ rm -rf db # 可以不用真的删除, 只是认为它已经不存在了<BR><BR>7 进行灾难恢复<BR><BR>$ cp -a db.backup db.restore<BR>$ cp -f pg_xlog/* db.restore/pg_xlog<BR>$ postmaster -D db.restore # 没有用 pg_ctl 启动, <BR># 为了更清楚看到日志(此日志非彼日志)输出<BR>LOG: database system was interrupted at 2004-04-15 18:12:47 CST<BR>LOG: checkpoint record is at 0/9B1058<BR>LOG: redo record is at 0/9B1058; undo record is at 0/0; shutdown TRUE<BR>LOG: next transaction ID: 536; next OID: 17142<BR>LOG: database system was not properly shut down; automatic recovery in progress<BR>LOG: redo starts at 0/9B1098<BR>LOG: record with zero length at 0/9D4458<BR>LOG: redo done at 0/9D4434<BR>LOG: database system is ready<BR><BR><BR>$ psql test # 另外开一个控制台<BR>test=# select * from t;<BR>a<BR>-----<BR>1<BR>2<BR>3<BR>100<BR>200<BR>300<BR>(6 rows)<BR><BR>可见, 数据库已经已经恢复了到了灾难发生前的一刻.<BR><BR>当然, 这个实验数据量很小只产生并复制了一个日志文件, 而且复制的日志文件<BR>还是当前正在工作的, 和前面描述的不完全一致. 更深入的实验大家可以下来做.<BR><BR>实际的联机热备份(也叫PITR(Point In Time Recovery))还有很多细节, 不过<BR>总的来说, PostgreSQL 离实现联机热备份已经很近很近了! 也许下一个版本我们就能看到<BR>这个令人兴奋的功能了. 我们热切地期待着! <BR><BR>她已经为我们做了这么多, 我们能为她做点什么呢? <BR><BR><BR>以上内容仅代表我自己的理解, 不正之处敬请指出.<BR><BR>kernel <BR>2004.4.15<BR><BR><BR>转自: </FONT><A href="http://bbs.pgsqldb.com/index.php?t=msg&amp;th=3739&amp;start=0"><FONT size=2>http://bbs.pgsqldb.com/index.php?t=msg&amp;th=3739&amp;start=0</FONT></A><img src ="http://www.blogjava.net/weidagang2046/aggbug/3455.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-04-19 13:39 <a href="http://www.blogjava.net/weidagang2046/articles/3455.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在FAT32格式的Win2k下安装Postgresql</title><link>http://www.blogjava.net/weidagang2046/articles/3435.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Tue, 19 Apr 2005 01:26:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/3435.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/3435.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/3435.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/3435.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/3435.html</trackback:ping><description><![CDATA[<FONT size=2>Postgresql在FAT32下的安装和NTFS下有所不同，下面只讨论FAT32下Postgresql的安装：<BR><BR>(1)&nbsp; 到Postgresql的官方网站<A href="http://www.postgresql.org/">http://www.postgresql.org/</A>下载安装文件。<BR>(2)&nbsp; 运行安装程序，本文假设安装路径选C:\pgsql，data路径选D:\pgsql\data；注意安装过程中不要选install as service。<BR>(3)&nbsp; 将C:\pgsql\bin加入PATH环境变量。<BR>(4)&nbsp; 添加用户postgres，本文假设密码为1234。<BR>(5)&nbsp; 用postgres帐号初始化数据库集群。可以先注销当前用户再用postgres帐号登陆，也可以用命令runas /user:postgres cmd切换到postgres帐号；然后在命令提示符下面输入：initdb -E UNICODE --locale=C -D d:\pgsql\data。注意：要先建立d:\pgsql\data目录。<BR>(6)&nbsp; 安装服务（用Administrator帐号来做）并启动：pg_ctl register -N PostgreSQL -U postgres -P 1234 -D d:\pgsql\data<BR>(7)&nbsp; 用postgres帐号运行：createuser -a Administrator为数据库添加一个超级用户，以后的操作都可以用Administrator帐号来完成，不必切换到postgres帐号。</FONT><img src ="http://www.blogjava.net/weidagang2046/aggbug/3435.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-04-19 09:26 <a href="http://www.blogjava.net/weidagang2046/articles/3435.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>