﻿<?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-weibogao</title><link>http://www.blogjava.net/weibogao/</link><description>my second lifespace</description><language>zh-cn</language><lastBuildDate>Tue, 12 May 2026 22:58:07 GMT</lastBuildDate><pubDate>Tue, 12 May 2026 22:58:07 GMT</pubDate><ttl>60</ttl><item><title>常用的算法网站汇总</title><link>http://www.blogjava.net/weibogao/archive/2010/03/04/314514.html</link><dc:creator>weibogao</dc:creator><author>weibogao</author><pubDate>Thu, 04 Mar 2010 06:21:00 GMT</pubDate><guid>http://www.blogjava.net/weibogao/archive/2010/03/04/314514.html</guid><wfw:comment>http://www.blogjava.net/weibogao/comments/314514.html</wfw:comment><comments>http://www.blogjava.net/weibogao/archive/2010/03/04/314514.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weibogao/comments/commentRss/314514.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weibogao/services/trackbacks/314514.html</trackback:ping><description><![CDATA[1.http://caterpillar.onlyfun.net/Gossip/AlgorithmGossip/AlgorithmGossip.htm（包括c，java等的不同实现，基础练习很有用）<br />
2.http://www.javaeye.com/forums/tag/algorithm（专业java网站上的论坛，很不错）<br />
3.待续。。。。。。<br />
<img src ="http://www.blogjava.net/weibogao/aggbug/314514.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weibogao/" target="_blank">weibogao</a> 2010-03-04 14:21 <a href="http://www.blogjava.net/weibogao/archive/2010/03/04/314514.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用CVS来管理自己的程序</title><link>http://www.blogjava.net/weibogao/archive/2007/01/16/94195.html</link><dc:creator>weibogao</dc:creator><author>weibogao</author><pubDate>Tue, 16 Jan 2007 04:49:00 GMT</pubDate><guid>http://www.blogjava.net/weibogao/archive/2007/01/16/94195.html</guid><wfw:comment>http://www.blogjava.net/weibogao/comments/94195.html</wfw:comment><comments>http://www.blogjava.net/weibogao/archive/2007/01/16/94195.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weibogao/comments/commentRss/94195.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weibogao/services/trackbacks/94195.html</trackback:ping><description><![CDATA[
		<a href="http://51cmm.csai.cn/casepanel/CM/No047.htm">
				<font color="#1d58d1">http://51cmm.csai.cn/casepanel/CM/No047.htm</font>
		</a>
		<br />
		<p>每个编写程序的人也许都有过这样的体验：对编写的程序作了一些修改，几天以后，我们可能发现上次的修改导致了其它的矛盾，甚至编译不能通过，但这时我们却很难找到自己刚在哪里作了改动。一般说来，我们总是希望自己完整的记录下一个程序开发的过程，记录下这个程序的每一点改进和调整。或许我们可以用备份的方法来解决这个问题，于是我们建立一个backup的目录，定期或不定期的将自己的源程序打包放进去，直到某一天整个硬盘都被这些文件撑满。这当然有些夸张，但这种机制带给我们的不方便是显而易见的。更进一步，现在一个软件产品的开发，一个人单枪匹马很难完成。可能是包含几个、几十个乃至上百个程序员协作开发，这时的源代码又该如何管理？解决问题的方法就是使用CVS。 <br />　　CVS - Concurrent Versions System(并发版本管理系统)是一个版本控制管理系统，它是目前最为广泛使用的一个系统。从gftp到gtk到KDE，你几乎可以在每一个你熟悉的自由软件的源码里看到它的踪迹(下面我们会知道，它的踪迹指一个称为cvs的子目录)。同样，你也可以在几乎每一个Linux的发行版本里看到CVS系统。可以说，如果失去了CVS，现有的许多多人协作、自由开发的软件都会在一定程度上放慢自己发展的步伐。 　　CVS到底有哪些功能，使得它有如此强大的魅力呢？<br /><strong>　　1.CVS能做什么？</strong><br />　　　如上所说，CVS首先是一个版本管理系统，它可以保留软件开发过程中的每一个版本的信息，包括谁、在何时、作了什么样的修改以及为什么作这样的修改等。这个功能和以前流行于Linux和Unix的版本管理系统RCS(Revision Control System)和SCCS(Source Code Control System)很象。但CVS的功能远非仅此。它的最大的特点是它的并发性，即它支持分布式项目的开发。在互联网席卷一切的今天，这个功能太为重要了。小到一个办公室内部开发一个OA系统，大到KDE小组利用互联网开发新版本的KDE，CVS都可以一展身手。一个程序员开发出了自己负责模块的新版本后，迅速的通过CVS让开发组的每一个成员都分享自己的最新成果。甚至，CVS通过特定的机制允许多个程序员同时修改同一个源程序文件。<br />　　另外CVS增强的目录结构以及对二进制文件良好的处理，都使得它远远优于其它的版本管理系统。最后，必须一提的是CVS是基于RCS开发而成的。<br /><strong>　　2.如何得到CVS?<br /></strong>　　　CVS在几乎包含在所有的Linux发布版本中，如RedHat、Turbo Linux、Slackware以及国产的红旗、Xteam Linux等。你可以试着敲一下cvs命令，大多数情况下都会出现以Usage: cvs开头的一堆信息，提示你如何使用cvs，这意味着在你的机器上早已有了CVS，只是遗憾的是它一直未被你发现和利用。运气不好的话，你会看到形如cvs: Command not found.的提示，这意味着你的机器没有安装CVS。这时你有两种选择。一是找到你的Linux安装盘，从那里安装CVS。例如在使用RPM方式安装的Linux(上面介绍的几个发布版本中似乎除了Slackware，其他都是)发布版本中，找到cvs***.rpm，用rpm命令进行安装。第二种方式是到一些站点cvs的源代码，然后遵循里面附带的指导进行安装，需要的读者请访问站点www.cyclic.com/或者http://www.loria.fr/~molli/cvs-index.html。<br /><strong>　　3.CVS的基本使用方法<br /></strong>　　　在这一节里，我们来学习掌握CVS的一些基本使用方法。现在我们假定已经安装好了CVS，并且我们打算用它来管理自己正在开发的一个软件，软件名叫netants，它存放在硬盘上一个叫做netants的目录里，目前里面有了文件netants.c、netants.h、http.c、http.h和Makefile。我们并没有和它人协作开发这个软件，也没有利用Internet或者Intranet来开发这个软件。或许，它只是自己的一个业余作品，试图写出一个比Windows下的网络蚂蚁更好的下载工具出来。<br /><strong>　　3.1初始化CVS　<br /></strong>　　我们首先要使用的命令是cvs init，这个命令用来初始化CVS系统。正如我们所看到的，所有的CVS命令都以cvs开头，然后在后面紧跟命令、参数和一些选项。初始化CVS系统主要是为了创建一个为CVS所使用的源码储存库(repository)。创建的时候，需要指定在那个目录下创建这个源码储存库。有两种方法来指定目录。一是利用"-d "选项来指定，例如：-d /usr/local/cvsroot。另一种更方便的方法是在shell里设定一个名叫CVSROOT的环境变量。使用csh或者tcsh的用户可以使用命令setenv来设定，在文件.csh rc或者文件.tcshrc里添加入下的一行：　setenv CVSROOT /usr/local/cvsroot　使用sh或者bash的用户需要在文件.profile或者文件.bashrc里添加如下两行：　CVSROOT=/usr/local/cvsroot　export CVSROOT　设置了环境变量CVSROOT后，我们运行命令cvs init，CVS将在指定的目录下面建立自己所需要的一些文件，以后我们使用CVS管理的任何项目，都会被CVS储存在这个目录之下。不过千万要注意的是：永远不要去试图修改这个目录下的文件。这个目录是由CVS自己进行管理的，轻率的改动可能会导致你丢失你部分或全部的交由CVS管理的源代码或其他资源。<br /><strong>　　3.2导入项目到CVS中去<br /></strong>　　初始化结束以后，我们就要真正开始利用CVS来管理自己的程序网络蚂蚁了。第一步，我们将这个项目交由CVS管理。使用如下的CVS的import命令，将源程序导入到CVS的源码储存库中去：　cd netants　cvs import -m "start my project: Netants" netants yoyo start　这个命令看起来有些复杂，需要解释一下。import是cvs的导入命令，默认状况下，它循环的将当前目录下的所有文件(包括子目录)导入到源码库(即CVSROOT指定的目录)里去。-m "start my project: Netants"告诉CVS你对这一步操作的说明。这是CVS强制要求的，如果你没有使用这种-m "字符串"的选项，CVS将会弹出一个文本编辑器(如果自己不特别指定的话，在Linux下一般是vi，而在Windows下则是Notepad)，让你输入一些说明信息它才罢休。netants是这个项目被CVS存储时的路径名，即CVS将在创建一个$CVSROOT/netants的目录，并在此目录下存放此项目的文件，当然，它不是原封不动的存储，CVS会做一番处理。最后两个字符串设定了两个标记(tag)，现在并没有什么用处，但它们同样是CVS指定必需的，所以我们添上这两个参数。执行此命令时，CVS自动将所有的文件版本设为1.1，这是它所认为的最低版本。以下为执行上述命令后的显示信息：N netants/netants.c N netants/http.c N netants/http.h N netants/netants.h N netants/Makefile No conflicts created by this import N表示New，CVS成功的加载了这些文件，并没有发现冲突。　上面的命令稍长了一些，而且显得有些繁琐，相信我，CVS不总是这样的，这点"繁琐"相对它给我们带来的便利是完全可以忽略不计的。<br /><strong>　　3.3从CVS中导出项目　<br /></strong>　　好了，我们把自己的netants的项目交给了CVS去管理，现在，我们完全可以删除原有的存储我们代码的netants目录(当然，安全起见，你或许应该再做一次备份，并希望是最后一次)。我们要进行开发工作了，建一个目录，叫什么呢，就叫worktmp吧。我们进到此目录下，执行命令cvs checkout netants，我们将会看到如下的信息：cvs checkout: Updating netants U netants/Makefile U netants/http.c U netants/http.h U netants/netants.c U netants/netants.h　CVS在当前目录下建立一个叫做netants的目录，我们原先的代码文件都在这个目录下出现了，而且还多了一个名为CVS的目录。目录CVS下面存放的是一些文本文件，记录了CVSROOT的位置、此项目对应源码库中那个目录等一些信息。<br /><strong>　　3.4保存修改到CVS中<br /></strong>　　　现在，我们开始艰苦卓越的编程工作。经过数十分钟、数小时乃至数天的工作，我们对原有的代码做了较大的修改，现在要告一段落了。我们将修改的内容提交给CVS，于是，我们需要执行命令　cvs commit -m "Made some useful changes on some files"　这时，我们将会看到CVS给出一些提示信息，它扫描并比较此目录下的现有文件和它在源码库中保存的原有文件，做了修改的文件将被更新，并且有了新的版本号：1.2。-m参数如同前面所说，是为了不想它启动一个文本编辑器来让自己输入。如果我们仅是修改了其中一两个文件，我们可以在上面的命令的最后附上文件名，这样CVS只会比较、更新指定的文件。注意的是，和自己做备份不同，CVS只是保存了不同版本之间的差异，并没有完整的保存各个版本。现在，你是不是觉得CVS有点用处了。<br /><strong>　　3.5添加文件到项目中<br /></strong>　　有一天，我们开始考虑给我们的网络蚂蚁加上从ftp站点下载文件的功能，于是，我们需要在原有的项目里添加两个文件：ftp.c和ftp.h。首先，我们在工作目录下建立并编辑、修改、生成了这两个文件，然后我们使用命令add命令来添加。　cvs add ftp.c ftp.h　此时，文件并没有真正的被添加，只是相当于"注册"了一下，要使这个过程生效，我们仍然需要使用commit命令：　cvs commit ftp.c ftp.h -m "Add two files: ftp.c and ftp.h"　此时，CVS将把这两个文件添加到项目中去，他们的版本均为初始的1.1。　　<strong>3.6从项目中删除文件</strong><br />　　除了添加以外，我们有的时候可能需要删除某个文件，例如我们发现文件netants.h其实没有什么用。于是，我们执行下面几个命令来完成删除工作：　rm netants.h　cvs remove netants.h　cvs commit netants.h -m "Delete a file."　要注意的是，CVS只是删除了当前版本的netants.h，它以前的版本依然存在，除非它恰好仅有1.1版本。<br /><strong>　　3.7设定特定版本号　<br /></strong>　　经过一段时间的工作，程序已经初具规模，形成了较稳定的版本。这个时候，netants.c可能已经是5.4版本，而http.c可能是3.5版本，而我们希望将当前的代码作一个版本发布。此时，我们需要使用的是tag命令。这个命令赋予指定的一个或多个文件一个给定的文本形式的版本号。版本号必须以字母开始，可以包含数字、下划线和连接符号(-)。我们想给当前项目的所有文件赋予相同的版本号时，可以不指定文件或路径参数，CVS默认选择当前目录下所有在CVS中注册的文件(循环进子目录)。下面既是一个例子：　键入命令：cvs tag release0-1　提示信息：　　　　cvs tag: Tagging .　　　　T Makefile　　　　T ftp.c　　　　T ftp.h　　　　T http.c　　　　T http.h　　　　T netants.c　这样当前版本的所有文件都有了一个叫做release0-1的版本代号。当我们需要这个版本的时候，我们使用-r (版本代号)参数来得到指定的版本。例如命令：　cvs checkout -r release0-1 netants　将在当前目录下建立netants目录，并导出所有版本代号为release0-1的文件。<br /><strong>　　3.8更新当前工作目录中的文件<br /></strong>　　　这里使用的命令为update，它将比较指定的在CVS源码库中的文件和当前目录下的文件，如果CVS源码库中有更高版本的源文件，则更新当前目录下的文件。这个功能主要是多人协作开发项目时使用的，让你及时分享同伴的工作成果。但它另外一个重要的用途，同样适用于单人开发的项目。这个用途需要使用-j参数，我们看下面的例子：　cvs update -j 1.5 -j 1.3 netants.c　这个命令的功能是，在当前目录的netants.c文件中，忽略从版本1.3到版本1.5所作的修改。毫无疑问，对程序员来说，这是一个非常重要的功能。因为在某个阶段我们对程序所作的修改在现在可能会被视为是无效乃至错误的，这个功能很好的解决了这个问题。　在更新的过程中，CVS执行一个自动合并的过程。例如我们的工作目录中的netants.c文件版本是2.1，并且我们已经对此文件作了一番修改，而CVS源码库中的是版本2.2，此时我们执行update命令时，CVS并不是简单的将版本2.2覆盖版本2.1，而是试图将自版本2.1到版本2.2的修改添加到当前目录中的文件中去，如果它和我们刚刚所作的修改有冲突，则CVS会以字符串"&gt;&gt;&gt;&gt;"表示由冲突发生，期待用户去修改。CVS拒绝接受包含有上述特定字符串的文件。下面即是一个冲突的例子：netants.c:版本号2.2，保存在CVS中……getPartFile( ); showFinished(); return(A); }……<br />　　netants:版本号2.1经过我们的修改……getPartFile( ); return(B); }……我们执行命令cvs update netants.c后，将会包含如下内容的新的netants.c：<br />　　……getPartFile( ); showFinished(); &gt;&gt;&gt;&gt;&gt;&gt; 2.2 }……　除非我们做出修改并删去"&gt;&gt;&gt;&gt;&gt;&gt;"，否则在执行cvs commit的时候，netants.c将不会更新原有的2.2版本。<br /><strong>　　4.CVS的其他功能<br /></strong>　　CVS当然远不止上面所说的这些内容，这些仅是CVS的基本功能，CVS还有许多重要的功能，如上面所说的网络工作方式、支持二进制文件等。下面我们对这些功能作简单的说明。<br /><strong>　　4.1 CVS的网络工作方式　<br /></strong>　　CVS的网络功能采用client-server结构，两地均需安装CVS。CVS采用rsh方式或者口令校验方式进行工作。对client端，同前面讲过的设置环境变量CVSROOT一样，用户需要设置新的环境变量CVS_SERVER，指明CVS在server上的路径，例如：/usr/local/cvsroot1。CVS的-d参数指定路径名，它后面可以用：(local或server或ext)：来指明是在本地还是在异地服务器上，默认当然是在本地，正如我们在初始化CVS一节所使用的那样。下面的命令假定我们的CVS服务器为cvs.rdcps.ac.cn，用户名为crazyyao，CVS源码库在服务器的/usr/local/cvsroot1目录下，我们的工作项目还是netants，我们用rsh方式导出项目文件：　cvs -d : server : crazyyao@cvs.rdcps.ac.cn :/usr/local/cvsroot1 checkout netants　采用口令校验方式时，需要对修改系统文件/etc/inetd.conf，以便使inetd知道如何分配、处理CVS Server的请求和响应。CVS会在源码库所在的目录中创建一个名为passwd的口令文件，对用户进行校验。使用口令校验时，CVS支持匿名登陆，而且CVS项目超级用户可以设置项目中文件的存取权限。　关于如何配置CVS使之工作在网络方式下的详细信息请参考CVS的文档。<br /><strong>　　4.2 CVS的分支和融合功能<br /></strong>　　　CVS增强的目录工作方式使得CVS提供分支和融合功能。有的时候，当项目进展到一定程度时，可能需要暂时中断，去做另外一些修改和发展。例如，我们的软件原有版本为1.0，并已提交用户使用，现在正在开发2.0。某一天，1.0的用户发现了一个较大的bug或者需要添加某个短小的功能，这时我们不能让用户去期待2.0版本，又必须给用户满意的答复，比较理想的解决方式是把现在的工作先放到一边，另开一个分支，去满足用户的需要。当此分支完成后，程序源还可以使用CVS的融合功能将这一部分修改添加到我们开发2.0版本的主工作进程中去。　创建分支可以使用tag -b命令。例如下面的命令　　　cvs tag -b netants-1-0-patch　在当前的工作目录的基础上创建一个叫做netant-1-0-patch的分支。　融合的命令参数是-j，我们在前面已经提及它了。<br /><strong>　　4.3 CVS处理二进制文件的功能　<br /></strong>　　CVS可以保存二进制文件，但和文本文件相比，它的许多功能丧失了。对于文本文件，CVS可以辨别出文件的任何一点改动，但对于二进制文件它无能为力。但是，CVS可以区分出文件作了改动，并会提示用户自己修改、保存。与文本文件不同，CVS保存二进制文件每个版本的完整信息。在操作二进制文件时，需要添加参数-KB，以便告诉CVS不把它当作文本文件看待。<br /><strong>　　4.4 CVS比较文件的功能　<br /></strong>　　执行的命令为diff，这个功能和shell下的diff功能基本一样。例如下面的命令比较CVS源码库中的最新的netants.c文件和当前目录下netants.c文件有什么不同：　cvs diff netatns.c<br /><strong>　　5.结束语<br /></strong>　　通过上面的介绍，希望能激起大家使用CVS的兴趣，并掌握使用CVS的一些基本方法。碰到困难时，别忘了翻阅CVS附带的手册，不过，它有厚厚的172页。希望CVS能加速你的软件开发。</p>
		<br />
<img src ="http://www.blogjava.net/weibogao/aggbug/94195.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weibogao/" target="_blank">weibogao</a> 2007-01-16 12:49 <a href="http://www.blogjava.net/weibogao/archive/2007/01/16/94195.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>WinCVS与CVSNT简明使用手则(二)</title><link>http://www.blogjava.net/weibogao/archive/2007/01/16/94192.html</link><dc:creator>weibogao</dc:creator><author>weibogao</author><pubDate>Tue, 16 Jan 2007 04:43:00 GMT</pubDate><guid>http://www.blogjava.net/weibogao/archive/2007/01/16/94192.html</guid><wfw:comment>http://www.blogjava.net/weibogao/comments/94192.html</wfw:comment><comments>http://www.blogjava.net/weibogao/archive/2007/01/16/94192.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weibogao/comments/commentRss/94192.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weibogao/services/trackbacks/94192.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 5.2    						管理员进行管理演练：														1.         						登陆远程						CVSNT						：														l         						选择						Admin=&gt;Login						菜单，默认设置，						OK						。								...&nbsp;&nbsp;<a href='http://www.blogjava.net/weibogao/archive/2007/01/16/94192.html'>阅读全文</a><img src ="http://www.blogjava.net/weibogao/aggbug/94192.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weibogao/" target="_blank">weibogao</a> 2007-01-16 12:43 <a href="http://www.blogjava.net/weibogao/archive/2007/01/16/94192.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>WinCVS与CVSNT简明使用手则(一)</title><link>http://www.blogjava.net/weibogao/archive/2007/01/16/94191.html</link><dc:creator>weibogao</dc:creator><author>weibogao</author><pubDate>Tue, 16 Jan 2007 04:42:00 GMT</pubDate><guid>http://www.blogjava.net/weibogao/archive/2007/01/16/94191.html</guid><wfw:comment>http://www.blogjava.net/weibogao/comments/94191.html</wfw:comment><comments>http://www.blogjava.net/weibogao/archive/2007/01/16/94191.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.blogjava.net/weibogao/comments/commentRss/94191.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weibogao/services/trackbacks/94191.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 1       						前言：														CVS						是版本控制的利器，目前在						Linux						和						Windows						下都有不同版本；但是国内大多数应用介绍都是基于						Linux						等开放源代码的开放性软件组织，而且讲解的也不系统，让人摸不着头脑；						Windows...&nbsp;&nbsp;<a href='http://www.blogjava.net/weibogao/archive/2007/01/16/94191.html'>阅读全文</a><img src ="http://www.blogjava.net/weibogao/aggbug/94191.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weibogao/" target="_blank">weibogao</a> 2007-01-16 12:42 <a href="http://www.blogjava.net/weibogao/archive/2007/01/16/94191.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>《CVSNT用户管理方案》转载</title><link>http://www.blogjava.net/weibogao/archive/2007/01/16/94190.html</link><dc:creator>weibogao</dc:creator><author>weibogao</author><pubDate>Tue, 16 Jan 2007 04:38:00 GMT</pubDate><guid>http://www.blogjava.net/weibogao/archive/2007/01/16/94190.html</guid><wfw:comment>http://www.blogjava.net/weibogao/comments/94190.html</wfw:comment><comments>http://www.blogjava.net/weibogao/archive/2007/01/16/94190.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weibogao/comments/commentRss/94190.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weibogao/services/trackbacks/94190.html</trackback:ping><description><![CDATA[
		<font color="#333333">
				<font face="宋体">   所有这些操作基于的环境和软件版本：WINDOWS2000 Pro、CVSNT2.0.4、WinCVS1.3.9.1Beta9 在前面的介绍CVSNT配合WinCVS进行用户管理的文章中，已经对CVSNT的用户管理，权限分配进行了比较详细的叙述，但是还有一些概念没有交待清楚，在这里，我会根据项目的实际需要，以及自己的一些经验给出一套用户管理、权限管理方案，在看这篇文章之前你最好已经阅读了前面的一篇文章，对用户的增加删除，权限的修改，等等这些操作能够了解，否则，会有一点困难。<br /><b><span lang="EN-US" style="COLOR: #333333; FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体; mso-font-kerning: 0pt">  1、 CVSNT的用户验证方式</span></b></font>
		</font>
		<b>
				<span lang="EN-US" style="COLOR: #333333; FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-bidi-font-family: 宋体; mso-font-kerning: 0pt">
						<br />
				</span>
		</b>
		<span lang="EN-US" style="COLOR: #333333; FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-bidi-font-family: 宋体; mso-font-kerning: 0pt">  我们这里所讨论的是工作在pserver方式下。<br />  在CVSNT的文档中给出了两种验证方式，我总结了一下，可以这样称呼：Window和CVSNT混合验证方式，CVSNT独立验证方式。在前面的文章中，我们没有详细的给出这两种方式的内容，所讲述的启示就是混合验证方式。<br />  决定CVSNT工作于何种验证方式是由CVS的管理文件来决定的，这些管理文件处在库的目录下的CVSROOT目录中，这里可以得出结论，对于不同的库，可以给不同的验证方式。所以，在每个库建立的时候要首先设定好这些前提。<br />  下面的操作如果没有特殊指出则都是在客户端来进行管理的，下面首先是对一些控制原理和相关的文件做一些说明，如果你正在进行相关的模拟操作，请停下来暂时停止你的操作，因为这些操作的步骤是有先后的，如果你顺序不对，那么你可能就权限失效，进行不了下面的操作了。<br /></span>
		<b>
				<span lang="EN-US" style="COLOR: #333333; FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体; mso-font-kerning: 0pt">  1．1 config文件</span>
		</b>
		<b>
				<span lang="EN-US" style="COLOR: #333333; FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-bidi-font-family: 宋体; mso-font-kerning: 0pt">
						<br />
				</span>
		</b>
		<span lang="EN-US" style="COLOR: #333333; FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-bidi-font-family: 宋体; mso-font-kerning: 0pt">  在库建立好了以后，你还没有对控制文件进行修改之前，CVSNT是工作在混合验证方式之下的，这个时候，CVS服务器的管理员就是CVSNT的管理员，你以一个管理员身份登陆，检出你要操作的库的CVSROOT模块，看一下文件列表，控制CVSNT的验证工作方式的是config文件，你可以在文件列表中找到它，双击看看其中的内容，这里对我们最重要的就是第一个设置内容，你会看到下面的内容：<br />  # Set this to `no' if pserver shouldn't check system users/passwords<br />  #SystemAuth=yes<br />  第二行就是我们要修改的内容，默认状态是被注释掉的，SystemAuth有两个值yes和no<br />yes：pserver将使用系统用户数据库和passwd文件（这个文件后面会详细讲述）来共同验证（若passwd文件不存在或者文件中没有相应的资料，则用系统用户来进行验证）默认为yes<br />  no：所有的用户必须在passwd中存在，根据passwd的内容来进行用户的验证。<br />  我这里所阐述的方案就是工作在no的下面的，修改完之后提交到服务器，提交完毕服务器就处在CVSNT的独立验证模式下了。在这个工作方式下，NT本地的用户和CVSNT用户没有任何本质的联系和影响（仅仅是要建立一个别名）。<br /></span>
		<b>
				<span lang="EN-US" style="COLOR: #333333; FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体; mso-font-kerning: 0pt">  1．2 passwd文件</span>
		</b>
		<b>
				<span lang="EN-US" style="COLOR: #333333; FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-bidi-font-family: 宋体; mso-font-kerning: 0pt">
						<br />
				</span>
		</b>
		<span lang="EN-US" style="COLOR: #333333; FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-bidi-font-family: 宋体; mso-font-kerning: 0pt">  在讲述上面的时候提到了这个文件，在服务器工作在CVSNT验证模式下的时候，这个文件就可以称之为CVSNT的用户数据库，这个里面存储着用户列表，用户的密码，以及别名的一些信息。默认状态下这个文件是不存在的，所以，如果我们要在CVSNT验证模式下工作，必须建立这个文件。注意：这个文件是不能够在客户端进行修改的。这个文件的内容是相当简单的，就像下面：<br />  bach:ULtgRLXo7NRxs<br />  spwang:1sOp854gDF3DY<br />  melissa:tGX1fS8sun6rY:pubcvs<br />  qproj:XR4EZcEs0szik:pubcvs<br />  这里分别拿第一个用户bach和第三个用户melissa来进行说明，每一行代表一个用户，总共有三部分信息，用户名、密码、本地用户三部分之间使用冒号“:”来进行分割。<br />  用户名：就是登陆CVS的用户名<br />  密 码：用户的密码，这里是经过加密的，如果为空，那么就是空密码<br />  本地用户：CVS用户这个别名对应的本地用户，（跟本地用户没有任何其他关系，仅仅是别名的关系）<br />  如果在本地系统中存在一个用户名bash，那么要在CVS建立一个bach这样的用户就不需要在后面指出对应的系统用户，melissa后面的pubcvs就是系统用户，在本地系统上面存在的用户。对于要用命令增加这两种用户的格式如下：<br />  cvs passwd –a bach<br />  cvs passwd –r pubcvs –a melissa<br />  在库建立的时候可以在服务器上建立一个简单的passwd初始化文件，加一行<br />  cvsadmin:<br />  这样，就给出了一个cvsadmin这个空密码用户（本地系统中有这样的用户，就可以不加到后面去），然后在客户端来进行修改和以后的用户增加工作。注意：在客户端进行其他之前请先首先修改这个密码，以防止别人进行破坏。<br />  在服务器端建立了这个文件以后，就不用再手动进行修改了，当你在客户端进行密码或者用户的增加删除的时候，系统会自动进行这个文件的更新。这个文件是管理着CVSNT系统中的所有的用户，所以，要特别重视，不了解这个文件格式的，不要去随便修改，更加不要尝试在客户端进行修改！<br /></span>
		<b>
				<span lang="EN-US" style="COLOR: #333333; FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体; mso-font-kerning: 0pt">  1．3 admin文件</span>
		</b>
		<b>
				<span lang="EN-US" style="COLOR: #333333; FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-bidi-font-family: 宋体; mso-font-kerning: 0pt">
						<br />
				</span>
		</b>
		<span lang="EN-US" style="COLOR: #333333; FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-bidi-font-family: 宋体; mso-font-kerning: 0pt">  这个文件是指定CVSNT的管理员列表的文件，CVSNT会根据这个文件中的内容来判断一个用户是否是管理员。这个文件的内容很简单，是一个用户列表。类似下面<br />  user1<br />  user2<br />  user3<br />  这些代表user1,user2,user3都是管理员，当然，这些用户必须要存在才能够正确登陆系统来执行管理。<br />  这个文件默认状态下是没有的，但是，可以在客户端进行添加，在你的客户端进行新建这个文件然后add上去再commit一下，这个文件就可以上传到服务器，但是这个时候还没有生效，请修改checkoutlist这个文件，加入admin这一行，checkoutlist也可以在客户端进行修改再提交，这个时候admin就可以被系统自动的build了。<br />  Checkoutlist是维护的一个文件列表，可以放入系统自动build的用户自定义的系统文件列表，注意：对passwd没有用！！<br /></span>
		<b>
				<span lang="EN-US" style="COLOR: #333333; FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体; mso-font-kerning: 0pt">  1．4 group文件</span>
		</b>
		<b>
				<span lang="EN-US" style="COLOR: #333333; FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-bidi-font-family: 宋体; mso-font-kerning: 0pt">
						<br />
				</span>
		</b>
		<span lang="EN-US" style="COLOR: #333333; FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-bidi-font-family: 宋体; mso-font-kerning: 0pt">  这个文件是定义系统的组，我们可以将同样性质的用户归入一个组，然后用给用户赋权限的方式给组赋权限，这样，一个组的用户就会具有同样的权限。Group的内容如下：<br />  group1:user1 user2 user3<br />  group2:me you he<br />  group3:tom honey<br />  有上面可以看出来，这个文件的内容也是相当的简单，首先是组的名称然后是冒号，接着是用户名，多个用户名之间用空格来进行分割。<br />  Group文件可以在客户端进行新建和修改，不用修改checkoutlist这个文件，系统会自动build这个文件并且使之生效。<br />  作为组里面的特定成员还可以赋给特定的权限，权限分为两类c,w,r和n，否定权限是有高的优先级的。<br />  好，上面已经介绍了本方案所涉及到的几个重要的文件以及修改方式。这里再强调一下，passwd只能够再服务器端进行建立和修改，不能够在客户端进行操作！<br />现在根据上面介绍的内容，可以开始你的操作了，下面给出修改顺序，库刚刚建立起来的时候，使用一个服务器上的本地管理员用户进行登陆检出CVSROOT模块。<br />  1、 现在服务器端加上passwd文件，给一个初始的用户，比如cvsadmin:<br />  2、 在客户端增加admin，将cvsadmin加入admin文件，作为出是管理员，并提交加入到库中。<br />  3、 在修改checkoutlist文件，加入admin，使其能够自动build。<br />  4、 最后修改config文件的SystemAuth=no，在提交之前要确认一下你上面的修改是否正确，如果提交了这个文件，CVSNT验证模式就开始生效了！<br />  5、 好，现在请修改你的参数再重新进行的登陆吧。因为你的系统已经切换了工作模式，你当前的用户已经失效了。<br />  完成了上面的步骤，整个服务器就会有效的工作在CVSNT验证模式下了。而group文件在你需要的任何时候可以加入。<br />  在上面的文章关于CVSNT的用户的管理方案的，在这里做一点补充，在后面的操作中全部是针对在客户端使用WinCVS来进行的（出了增加passwd）文件，其实，在我的实践当中config，passwd，admin，checkoutlist，这些文件的起始修改（初始化）都可以在新建了库以后一起完成，然后再让相应的库的管理员来进行相关的操作。<br /></span>
<img src ="http://www.blogjava.net/weibogao/aggbug/94190.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weibogao/" target="_blank">weibogao</a> 2007-01-16 12:38 <a href="http://www.blogjava.net/weibogao/archive/2007/01/16/94190.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Oracle的物化视图</title><link>http://www.blogjava.net/weibogao/archive/2007/01/15/93914.html</link><dc:creator>weibogao</dc:creator><author>weibogao</author><pubDate>Mon, 15 Jan 2007 04:05:00 GMT</pubDate><guid>http://www.blogjava.net/weibogao/archive/2007/01/15/93914.html</guid><wfw:comment>http://www.blogjava.net/weibogao/comments/93914.html</wfw:comment><comments>http://www.blogjava.net/weibogao/archive/2007/01/15/93914.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weibogao/comments/commentRss/93914.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weibogao/services/trackbacks/93914.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Oracle				的物化视图提供了强大的功能，可以用在不同的环境中。在不同的环境中，物化视图的作用也不相同。																																		数据仓库中的物化视图主要用于预先计算并保存表连接或聚集等耗时较多的操作的结果，这样，在执行查询时，就可以避免进行这些耗时的操作，而从快速的得到结果。在数据仓库中，还经常使用查询重写（...&nbsp;&nbsp;<a href='http://www.blogjava.net/weibogao/archive/2007/01/15/93914.html'>阅读全文</a><img src ="http://www.blogjava.net/weibogao/aggbug/93914.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weibogao/" target="_blank">weibogao</a> 2007-01-15 12:05 <a href="http://www.blogjava.net/weibogao/archive/2007/01/15/93914.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Effective SQL</title><link>http://www.blogjava.net/weibogao/archive/2007/01/12/93392.html</link><dc:creator>weibogao</dc:creator><author>weibogao</author><pubDate>Fri, 12 Jan 2007 04:56:00 GMT</pubDate><guid>http://www.blogjava.net/weibogao/archive/2007/01/12/93392.html</guid><wfw:comment>http://www.blogjava.net/weibogao/comments/93392.html</wfw:comment><comments>http://www.blogjava.net/weibogao/archive/2007/01/12/93392.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.blogjava.net/weibogao/comments/commentRss/93392.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weibogao/services/trackbacks/93392.html</trackback:ping><description><![CDATA[
		<p>一.名词解释： <br />0。SQL 结构化查询语言(Structured Query Language) <br /><br />1。非关系型数据库系统 <br />做为第一代数据库系统的总称，其包括2种类型：“层次”数据库与“网状”数据库 <br /><br />“层次”数据库管理系统 eg:IBM&amp;IMS (Information  Management System) <br />特点:数据按层次模型组织 <br /><br />"网状"数据库 <br />特点：数据按网状模型组织 <br /><br />2。关系型数据库系统 <br />关系性数据库管理系统 (RDBMS) <br />eg:SQL/DS , DB2, Oracle ,Informix ,Unity,dBASE等 <br />特点：数据按二维的表格组织。 <br /><br />3。数据库(DataBase) <br />按一定结构存储在计算机中相互关联的数据的集合。 <br /><br />4。数据库管理系统DBMS(Database Management System) <br />一个通用的软件系统。就是让你怎么管理你的数据库。其中包括存储，安全，完整性管理<br />等。 <br /><br />5。数据库应用系统DBAS （Database Application System） <br />数据库应用程序系统，建立在DBMS基础之上的。就是一个面向用户的软件系统。 <br /><br />6。ANSI标准 （American National Standards Institute）美国国家标准委员会 <br />因为1999年第2次更新SQL，所以SQL又称为SQL99或SQL3（第3版，前2个版本分别为1986年<br />的sql ,1992 年的sql2/sql92）。 <br /><br />7。SQL语句的3种类型 <br />数据操作语句(Data Manipulation Language ) DML 关于数据操作命令的  eg:select,in<br />sert,update,delete <br />数据定义语句(Data Definition Language ) DDL     关于数据对象访问的  eg:create，<br /> drop <br />数据控制语句(Data Control Language) DCL         关于权限的  eg:grant ，revoke <br /><br /><br />8。PL/SQL Procedural Language/sql <br />用于oracle的语言 <br /><br />9.T-SQL  transact-sql <br />用于 microsoft sql server 和sybase adaptive server <br /><br />10。E.F.Codd关于关系型数据库12条检验原则（MYSQL，不支持视图和原子事物处理，所以<br />排除） <br />内容：暂略 <br /><br />11。数据库设计之新奥尔良方法。 <br />需求分析==》概念设计==》逻辑设计==》物理设计. <br />4个步骤的具体中以需求分析最重要. <br />需求分析的内容:暂略 <br />概念设计的内容:暂略 <br />逻辑设计的内容:暂略 <br />物理设计的内容:暂略 <br /><br /><br />二.数据库优化方案 <br />1.索引 <br />一 概述 <br /><br />  可以利用索引快速访问数据库表中的特定信息。索引是对数据库表中一个或多个列的值<br />进行排序的结构。 <br />  索引提供指针以指向存储在表中指定列的数据值，然后根据指定的排序次序排列这些指<br />针。 <br />  数据库使用索引的方式与使用书的目录很相似：通过搜索索引找到特定的值， <br />  然后跟随指针到达包含该值的行 <br /><br />索引是一个单独的、物理的数据库结构，它是某个表中一列或若干列值的集合和相应的指<br />向表中物理标识这些值的数据页的逻辑指针清单。 <br /><br />一个表的存储是由两部分组成的，一部分用来存放表的数据页面，另一部分存放索引页面<br />。索引就存放在索引页面上 <br /><br />二 索引的两种类型： <br /><br />聚集索引=簇集索引 <br /><br />聚集索引基于数据行的键值在表内排序和存储这些数据行。由于数据行按基于聚集索引键<br />的排序次序存储， <br />因此聚集索引对查找行很有效。每个表只能有一个聚集索引，因为数据行本身只能按一个<br />顺序存储。 <br />数据行本身构成聚集索引的最低级别。 <br /><br />只有当表包含聚集索引时，表内的数据行才按排序次序存储。如果表没有聚集索引， <br />则其数据行按堆集方式存储。 <br /><br />聚集索引对于那些经常要搜索范围值的列特别有效。使用聚集索引找到包含第一个值的行<br />后， <br />便可以确保包含后续索引值的行在物理相邻。例如，如果应用程序执行的一个查询经常检<br />索某一日期范围 <br />内的记录，则使用聚集索引可以迅速找到包含开始日期的行，然后检索表中所有相邻的行<br />， <br />直到到达结束日期。这样有助于提高此类查询的性能。同样，如果对从表中检索的数据进<br />行排序时 <br />经常要用到某一列，则可以将该表在该列上聚集（物理排序），避免每次查询该列时都进<br />行排序， <br />从而节省成本 <br /><br />非聚集索引 <br /><br />非聚集索引具有完全独立于数据行的结构。非聚集索引的最低行包含非聚集索引的键值，<br /><br />并且每个键值项都有指针指向包含该键值的数据行。数据行不按基于非聚集键的次序存储<br />。 <br /><br />在非聚集索引内，从索引行指向数据行的指针称为行定位器。 <br />行定位器的结构取决于数据页的存储方式是堆集还是聚集。对于堆集，行定位器是指向行<br />的指针。 <br />对于有聚集索引的表，行定位器是聚集索引键。 <br />只有在表上创建了聚集索引时，表内的行才按特定的顺序存储。这些行就基于聚集索引键<br />按顺序存储。 <br />如果一个表只有非聚集索引，它的数据行将按无序的堆集方式存储 <br />非聚集索引可以建多个,两者都能改善查询性能 <br /><br />非聚集索引与聚集索引一样有 B 树结构，但是有两个重大差别： <br />数据行不按非聚集索引键的顺序排序和存储。 <br />非聚集索引的叶层不包含数据页。 <br />相反，叶节点包含索引行。每个索引行包含非聚集键值以及一个或多个行定位器， <br />这些行定位器指向有该键值的数据行（如果索引不唯一，则可能是多行）。 <br />非聚集索引可以在有聚集索引的表、堆集或索引视图上定义 <br /><br /><br />聚集索引--&gt;顺序表结构.其物理数据和逻辑排序紧邻. <br />非聚集索引--&gt;单链表结构.起物理和逻辑排序不按顺序排列. <br /><br />打个比方. <br />一本字典,你现在查一个陈字.你有2种方法.首先,你在知道他念chen的情况下去按照拼音字<br />母去查找.他是排在字母A,B <br />于是你很容易的就找到"陈"字.第2种方法则是按编旁查找,先找到耳朵旁,去找到一个临时<br />的编旁表在去找"东"这个字,然后按照给出的 <br />页数找到相应的位置. <br />显然,第一种方法就是聚集索引,按照物理位置根据排序来查找. <br />第2种方法则是非聚集索引,按照一个临时索引来查找. <br /><br />另外 <br />唯一索引 <br /><br />唯一索引可以确保索引列不包含重复的值。在多列唯一索引的情况下，该索引可以确保索<br />引列中每个值组 <br />合都是唯一的。唯一索引既是索引也是约束。 <br /><br />复合索引 <br />索引项是多个的就叫组合索引，也叫复合索引。复合索引使用时需要注意索引项的次序。<br /><br /><br />二 索引的创建 <br /><br />有两种方法可以在 SQL Server 内定义索引: CREATE INDEX 语句和CREATE TABLE 语句 <br /><br /><br />CREATE TABLE支持在创建索引时使用下列约束： <br /><br />PRIMARY KEY 创建唯一索引来强制执行主键 <br />UNIQUE 创建唯一索引 <br />CLUSTERED 创建聚集索引 <br />NONCLUSTERED 创建非聚集索引 <br /><br />注: 1 定义索引时，可以指定每列的数据是按升序还是降序存储。如果不指定，则默认为<br />升序 <br />   2 支持在计算列上创建索引 <br />   3 为索引指定填充因子 <br />     可标识填充因子来指定每个索引页的填满程度。索引页上的空余空间量很重要， <br />     因为当索引页填满时，系统必须花时间拆分它以便为新行腾出空间。 <br /><br /><br />三 索引的维护语句 <br /><br />DBCC DBREINDEX    重建指定数据库中表的一个或多个索引 <br />DBCC INDEXFRAG　　整理指定的表或视图的聚集索引和辅助索引碎片 <br /><br />比较 <br /><br />            速度    兼容性     日志影响      数据访问影响       额外磁盘空间 <br /><br />DBCC        最快      最好     大,但能通过把   操作过程中数据不   需要大 <br />DBREINDEX             可以重   故障还原模型设  能访问，影响大 <br />                     建所有   为简单减少日志    <br />                     有索引 <br /><br />DBCC        慢       但可   必须分   小              数据未被锁定        需要小<br /><br />INDEXDEFRAG          随时终 别指定 <br />                    止执行   <br />                               <br /><br />drop index    中等  必须分   大,但能通过把    仅在操作执行时    中等，操作在  <br />  <br />create index        别指定   故障还原模型设   锁定数据          tempdb中进行 <br /><br />                            为简单减少日志 <br /><br /><br />四 查看索引的方法 <br /><br />sp_indexes        返回指定远程表的索引信息 <br />INDEXKEY_PROPERTY 返回有关索引键的信息 <br />sysindexes系统表  数据库中的每个索引和表在表中各占一行，该表存储在每个数据库中<br /><br /><br /><br />五 可以通过执行计划 <br />  查看sql语句执行时是否建立在索引之上 <br /><br />比如 <br />CREATE TABLE Test <br />(Field_1 int NOT NULL, <br />Field_2 int CONSTRAINT PK_Test <br />PRIMARY KEY CLUSTERED (Field_1)) <br /><br />CREATE index IX_Test ON Test (Field_2) <br /><br />1 SELECT * FROM Test WHERE Field_2 =408 <br /> 执行计划可以看出使用了IX_Test索引 <br />2 SELECT * FROM Test WHERE Field_1 =1 <br /> 执行计划可以看出使用了PK_Test <br />3 但如果是SELECT * FROM Test with (index(IX_Test)) WHERE Field_1 =1 <br /> 则指定使用索引 <br /><br /><br />六 索引的具体使用 <br /><br />1） 索引的设计 <br />A:尽量避免表扫描 <br />检查你的查询语句的where子句，因为这是优化器重要关注的地方。包含在where里面的每<br />一列（column)都是可能的侯选索引，为能达到最优的性能，考虑在下面给出的例子：对于<br />在where子句中给出了column1这个列。 <br />下面的两个条件可以提高索引的优化查询性能！ <br />第一：在表中的column1列上有一个单索引 <br />第二：在表中有多索引，但是column1是第一个索引的列 <br />避免定义多索引而column1是第二个或后面的索引，这样的索引不能优化服务器性能 <br />例如：下面的例子用了pubs数据库。 <br />SELECT au_id, au_lname, au_fname FROM authors <br />WHERE au_lname = ’White’ <br />按下面几个列上建立的索引将会是对优化器有用的索引 <br />?au_lname <br />?au_lname, au_fname <br />而在下面几个列上建立的索引将不会对优化器起到好的作用 <br />?au_address <br />?au_fname, au_lname <br />考虑使用窄的索引在一个或两个列上，窄索引比多索引和复合索引更能有效。用窄的索引<br />，在每一页上 <br />将会有更多的行和更少的索引级别（相对与多索引和复合索引而言），这将推进系统性能<br />。 <br />对于多列索引，SQL Server维持一个在所有列的索引上的密度统计（用于联合）和在第一<br />个索引上的 <br />histogram（柱状图）统计。根据统计结果，如果在复合索引上的第一个索引很少被选择使<br />用，那么优化器对很多查询请求将不会使用索引。 <br />有用的索引会提高select语句的性能，包括insert,uodate,delete。 <br />但是，由于改变一个表的内容，将会影响索引。每一个insert,update,delete语句将会使<br />性能下降一些。实验表明，不要在一个单表上用大量的索引，不要在共享的列上（指在多<br />表中用了参考约束）使用重叠的索引。 <br />在某一列上检查唯一的数据的个数，比较它与表中数据的行数做一个比较。这就是数据的<br />选择性，这比较结果将会帮助你决定是否将某一列作为侯选的索引列，如果需要，建哪一<br />种索引。你可以用下面的查询语句返回某一列的不同值的数目。 <br />select count(distinct cloumn_name) from table_name <br />假设column_name是一个10000行的表，则看column_name返回值来决定是否应该使用，及应<br />该使用什么索引。 <br />Unique values Index <br /><br />5000 Nonclustered index <br />20 Clustered index <br />3 No index <br /><br /><br />2) 镞索引和非镞索引的选择 <br /><br />&lt;1:&gt;镞索引是行的物理顺序和索引的顺序是一致的。页级，低层等索引的各个级别上都包<br />含实际的数据页。一个表只能是有一个镞索引。由于update,delete语句要求相对多一些的<br />读操作，因此镞索引常常能加速这样的操作。在至少有一个索引的表中，你应该有一个镞<br />索引。 <br />在下面的几个情况下，你可以考虑用镞索引： <br />例如： 某列包括的不同值的个数是有限的（但是不是极少的） <br />顾客表的州名列有50个左右的不同州名的缩写值，可以使用镞索引。 <br />例如： 对返回一定范围内值的列可以使用镞索引，比如用between,&gt;,&gt;=,&lt;,&lt;=等等来对列<br />进行操作的列上。 <br />select * from sales where ord_date between ’5/1/93’ and ’6/1/93’ <br />例如： 对查询时返回大量结果的列可以使用镞索引。 <br />SELECT * FROM phonebook WHERE last_name = ’Smith’ <br /><br />当有大量的行正在被插入表中时，要避免在本表一个自然增长（例如，identity列）的列<br />上建立镞索引。如果你建立了镞的索引，那么insert的性能就会大大降低。因为每一个插<br />入的行必须到表的最后，表的最后一个数据页。 <br />当一个数据正在被插入（这时这个数据页是被锁定的），所有的其他插入行必须等待直到<br />当前的插入已经结束。 <br />一个索引的叶级页中包括实际的数据页，并且在硬盘上的数据页的次序是跟镞索引的逻辑<br />次序一样的。 <br /><br />&lt;2:&gt;一个非镞的索引就是行的物理次序与索引的次序是不同的。一个非镞索引的叶级包含<br />了指向行数据页的指针。 <br />在一个表中可以有多个非镞索引，你可以在以下几个情况下考虑使用非镞索引。 <br />在有很多不同值的列上可以考虑使用非镞索引 <br />例如：一个part_id列在一个part表中 <br />select * from employee where emp_id = ’pcm9809f’ <br />查询语句中用order by 子句的列上可以考虑使用镞索引 <br /><br /><br /><br />3) 一个表列如果设为主键(primary key),它会自动生成一个聚簇索引 <br />这时不能直接使用Drop index Table1.Tableindex1语句 <br />必须删除主键约束，用语句:alter table table1 drop constraint 约束名(如pk_xxx) <br /><br /><br /><br />七.全文索引 <br />use pubs <br />　　go <br /><br />　　--打开数据库全文索引的支持 <br /><br />　execute sp_fulltext_database 'enable' <br />　go <br /><br />　　--建立全文目录ft_titles <br /><br />　　execute sp_fulltext_catalog 'ft_titles', 'create' <br />　　go <br /><br />　　--为titles表建立全文索引数据元，UPKCL_titleidind是主键所建立的唯一索引，可<br />由sp_help titles得知 <br /><br />　　execute sp_fulltext_table 'titles','create', 'ft_titles', 'UPKCL_titleidin<br />d' <br />　　go <br /><br />　　--设置全文索引列名 <br /><br />　　exec sp_fulltext_column 'titles', 'title', 'add' <br />　　go <br />　　exec sp_fulltext_column 'titles', 'notes', 'add' <br />　　go <br /><br />　　--建立全文索引 <br /><br />　　exec sp_fulltext_table 'titles', 'activate' <br />　　go <br /><br />　　--填充全文索引目录 <br /><br />　　exec sp_fulltext_catalog 'ft_titles', 'start_full' <br />　　go <br /><br />　　--使用contains和freetext <br /><br />　　select title, notes from titles <br />　　where contains(title, '"computer Cooking"') <br />　　go <br />　　select title, notes from titles <br />　　where freetext(title, 'computer Cooking') <br />　　go <br />　　select title, notes from titles <br />　　where freetext(title, '"computer Cooking"') <br />　　go <br />　　select title, notes from titles <br />　　where contains(title, 'computer') <br />　　go <br />　　select title, notes from titles <br />　　where freetext (*, 'computer') <br />　　go <br /><br />这里提一下google的搜索引擎的原理. <br />他把每个字词都做为单元去查询. <br />打个比方:我在字典里查询,现在我要搜索"树型"这个词,他会把这个树型这个词全文扫描一<br />遍,生成一个二叉树.并记下他的页数. <br />然后当我第2次查找的时候显然这个"记忆"提示,然后"提取".如果你对某一个字段做了全文<br />索引的话，他会全文扫描表一遍,然后纪录下 <br />相应的纪录,生成二叉树. <br />如果我要查找"树叶",同理也可以得出页数.但当我们去查找一下"树型结构"他则会把"树型<br />"和"树型结构"都"纪录"下来. <br /><br />八.巧妙的使用索引. <br />SELECT SUM(quantity) AS quantity FROM test WHERE... <br />1.若WHERE 里用的是字段与常量比较，MSSQL会自动引用该字段上的索引；若用的是变量，<br />MSSQL不会自动引用该字段上的索引而是根据聚集索引进行扫描 <br />2.加上with(index(索引名))指定索引，即： <br />SELECT SUM(quantity) AS quantity FROM with(index(索引名)) test WHERE... <br />指定索引后，WHERE 里不论是常量还是变量，MSSQL都根据指定的索引进行扫描 <br />3.DBCC DBREINDEX执行并不一定能优化MSSQL性能，慎用 <br />4.如果在pub_id上建立索引的话 <br />select * from titles where pub_id-500 &gt;1000   ---------(a) <br />select * from titles where pub_id &gt;1000+500  -----------(b) <br />请选用(b)语句,这样的话，他会利用索引,而(a)的话由于对字段操作了,所以不会利用索引<br />. <br />5.尽量避免用like语句, <br />如果去查找baa%,caa%的话 <br />如果是like '%aa%','_aa%','[m-z]o%'  则根本不会用到索引. <br />替换方法.columns like 'baa%' or  columns like 'caa %' <br />6什么情况下应不建或少建索引 <br />a.表记录太少 .因为索引的话，要对数据库往返2次操作,如果1个表只有几行字段的话，数<br />据库会对他的纪录一次性全部取出来,这样的效率要远远高于索引. <br />b.经常insert,delete,update的表  对一些经常处理的业务表应在查询允许的情况下尽量<br />减少索引 <br />c.数据重复且分布平均的表字段,如:性别字段,各占50%的话，你即使建了,也起不到明显的<br />作用. <br />d.经常和主字段一块查询但主字段索引值比较多的表字段 <br />表经常按收费序号、户标识编号、抄表日期、电费发生年月、操作标志来具体查询某一笔<br />收款的情况，如果将所有的字段都建在一个索引里那将会增加数据的修改、插入、删除时<br />间，从实际上分析一笔收款如果按收费序号索引就已经将记录减少到只有几条，如果再按<br />后面的几个字段索引查询将对性能不产生太大的影响。 <br />e.如果一个表的记录达到100万以上的话，要对其中一个字段建索引可能要花很长的时间，<br />甚至导致服务器数据库死机，因为在建索引的时候 ORACLE要将索引字段所有的内容取出并<br />进行全面排序，数据量大的话可能导致服务器排序内存不足而引用磁盘交换空间进行，这<br />将严重影响服务器数据库的工作。解决方法是增大数据库启动初始化中的排序内存参数，<br />如果要进行大量的索引修改可以设置10M以上的排序内存（ORACLE缺省大小为64K），在索<br />引建立完成后应将参数修改回来，因为在实际OLTP数据库应用中一般不会用到这么大的排<br />序内存。 <br /><br /><br />以下转载 <br />great_domino 的 Blog <br /><br />探讨如何在有着1000万条数据的MS SQL SERVER数据库中实现快速的数据提取和数据分页。<br />以下代码说明了我们实例中数据库的“红头文件”一表的部分数据结构： <br /><br />CREATE TABLE [dbo].[TGongwen] (    --TGongwen是红头文件表名 <br /><br />  [Gid] [int] IDENTITY (1, 1) NOT NULL , <br />--本表的id号，也是主键 <br /><br />  [title] [varchar] (80) COLLATE Chinese_PRC_CI_AS NULL , <br />--红头文件的标题 <br /><br />  [fariqi] [datetime] NULL , <br />--发布日期 <br /><br />  [neibuYonghu] [varchar] (70) COLLATE Chinese_PRC_CI_AS NULL , <br />--发布用户 <br /><br />  [reader] [varchar] (900) COLLATE Chinese_PRC_CI_AS NULL , <br /><br />--需要浏览的用户。每个用户中间用分隔符“,”分开 <br /><br />) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] <br /><br />GO <br /><br /><br />　　下面，我们来往数据库中添加1000万条数据： <br /><br />declare @i int <br /><br />set @i=1 <br /><br />while @i&lt;=250000 <br /><br />begin <br /><br />   insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-2-5','通<br />信科','通信科,办公室,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经<br />侦支队, 户政科,治安支队,外事科','这是最先的25万条记录') <br /><br />   set @i=@i+1 <br /><br />end <br /><br />GO <br /><br /><br /><br />declare @i int <br /><br />set @i=1 <br /><br />while @i&lt;=250000 <br /><br />begin <br /><br />   insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-9-16','办<br />公室','办公室,通信科,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经<br />侦支队,户政科,外事科','这是中间的25万条记录') <br /><br />   set @i=@i+1 <br /><br />end <br /><br />GO <br /><br /><br /><br />declare @h int <br /><br />set @h=1 <br /><br />while @h&lt;=100 <br /><br />begin <br /><br />declare @i int <br /><br />set @i=2002 <br /><br />while @i&lt;=2003 <br /><br />begin <br /><br />declare @j int <br /><br />       set @j=0 <br /><br />       while @j&lt;50 <br /><br />           begin <br /><br />declare @k int <br /><br />           set @k=0 <br /><br />           while @k&lt;50 <br /><br />           begin <br /><br />   insert into Tgongwen(fariqi,neibuyonghu,reader,title) values(cast(@i as var<br />char(4))+'-8-15 3:'+cast(@j as varchar(2))+':'+cast(@j as varchar(2)),'通信科'<br />,'办公室,通信科,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支<br />队,户政科,外事科','这是最后的50万条记录') <br /><br />           set @k=@k+1 <br /><br />           end <br /><br />set @j=@j+1 <br /><br />       end <br /><br />set @i=@i+1 <br /><br />end <br /><br />set @h=@h+1 <br /><br />end <br /><br />GO <br /><br /><br /><br />declare @i int <br /><br />set @i=1 <br /><br />while @i&lt;=9000000 <br /><br />begin <br /><br />   insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-5-5','通<br />信科','通信科,办公室,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经<br />侦支队, 户政科,治安支队,外事科','这是最后添加的900万条记录') <br /><br />   set @i=@i+1000000 <br /><br />end <br /><br />GO <br /><br />通过以上语句，我们创建了25万条由于2004年2月5日发布的记录，25万条由办公室于2004<br />年9月6日发布的记录，2002年和2003年各 100个2500条相同日期、不同分秒的记录（共50<br />万条），还有由通信科于2004年5月5日发布的900万条记录，合计1000万条。 <br /><br />何时使用聚集索引或非聚集索引 <br /><br />　　下面的表总结了何时使用聚集索引或非聚集索引（很重要）。 <br /><br />　　动作描述 <br />　　　使用聚集索引 <br />　　　使用非聚集索引 <br /><br />　　列经常被分组排序 <br />　　　应 <br />　　　应 <br /><br />　　返回某范围内的数据 <br />　　　应 <br />　　　不应 <br /><br />　　一个或极少不同值 <br />　　　不应 <br />　　　不应 <br /><br />　　小数目的不同值 <br />　　　应 <br />　　　不应 <br /><br />　　大数目的不同值 <br />　　　不应 <br />　　　应 <br /><br />　　频繁更新的列 <br />　　　不应 <br />　　　应 <br /><br />　　外键列 <br />　　　应 <br />　　　应 <br /><br />　　主键列 <br />　　　应 <br />　　　应 <br /><br />　　频繁修改索引列 <br />　　　不应 <br />　　　应 <br /><br /><br />　　事实上，我们可以通过前面聚集索引和非聚集索引的定义的例子来理解上表。如：返<br />回某范围内的数据一项。比如您的某个表有一个时间列，恰好您把聚合索引建立在了该列<br />，这时您查询2004年1月1日至2004年10月1日之间的全部数据时，这个速度就将是很快的，<br />因为您的这本字典正文是按日期进行排序的，聚类索引只需要找到要检索的所有数据中的<br />开头和结尾数据即可；而不像非聚集索引，必须先查到目录中查到每一项数据对应的页码<br />，然后再根据页码查到具体内容。 <br /><br />（三）结合实际，谈索引使用的误区 <br /><br />　　理论的目的是应用。虽然我们刚才列出了何时应使用聚集索引或非聚集索引，但在实<br />践中以上规则却很容易被忽视或不能根据实际情况进行综合分析。下面我们将根据在实践<br />中遇到的实际问题来谈一下索引使用的误区，以便于大家掌握索引建立的方法。 <br /><br />　　1、主键就是聚集索引 <br /><br />　　这种想法笔者认为是极端错误的，是对聚集索引的一种浪费。虽然SQL SERVER默认是<br />在主键上建立聚集索引的。 <br /><br />　　通常，我们会在每个表中都建立一个ID列，以区分每条数据，并且这个ID列是自动增<br />大的，步长一般为1。我们的这个办公自动化的实例中的列 Gid就是如此。此时，如果我们<br />将这个列设为主键，SQL SERVER会将此列默认为聚集索引。这样做有好处，就是可以让您<br />的数据在数据库中按照ID进行物理排序，但笔者认为这样做意义不大。 <br /><br />　　显而易见，聚集索引的优势是很明显的，而每个表中只能有一个聚集索引的规则，这<br />使得聚集索引变得更加珍贵。 <br /><br />　　从我们前面谈到的聚集索引的定义我们可以看出，使用聚集索引的最大好处就是能够<br />根据查询要求，迅速缩小查询范围，避免全表扫描。在实际应用中，因为ID号是自动生成<br />的，我们并不知道每条记录的ID号，所以我们很难在实践中用ID号来进行查询。这就使让<br />ID号这个主键作为聚集索引成为一种资源浪费。其次，让每个ID号都不同的字段作为聚集<br />索引也不符合“大数目的不同值情况下不应建立聚合索引”规则；当然，这种情况只是针<br />对用户经常修改记录内容，特别是索引项的时候会负作用，但对于查询速度并没有影响。<br /><br /><br />　　在办公自动化系统中，无论是系统首页显示的需要用户签收的文件、会议还是用户进<br />行文件查询等任何情况下进行数据查询都离不开字段的是“日期”还有用户本身的“用户<br />名”。 <br /><br />　　通常，办公自动化的首页会显示每个用户尚未签收的文件或会议。虽然我们的where语<br />句可以仅仅限制当前用户尚未签收的情况，但如果您的系统已建立了很长时间，并且数据<br />量很大，那么，每次每个用户打开首页的时候都进行一次全表扫描，这样做意义是不大的<br />，绝大多数的用户1个月前的文件都已经浏览过了，这样做只能徒增数据库的开销而已。事<br />实上，我们完全可以让用户打开系统首页时，数据库仅仅查询这个用户近3个月来未阅览的<br />文件，通过“日期”这个字段来限制表扫描，提高查询速度。如果您的办公自动化系统已<br />经建立的2年，那么您的首页显示速度理论上将是原来速度8倍，甚至更快。 <br /><br />　　在这里之所以提到“理论上”三字，是因为如果您的聚集索引还是盲目地建在ID这个<br />主键上时，您的查询速度是没有这么高的，即使您在“日期”这个字段上建立的索引（非<br />聚合索引）。下面我们就来看一下在1000万条数据量的情况下各种查询的速度表现（3个月<br />内的数据为25万条）： <br /><br />　　（1）仅在主键上建立聚集索引，并且不划分时间段： <br /><br />Select gid,fariqi,neibuyonghu,title from tgongwen <br /><br />　　用时：128470毫秒（即：128秒） <br /><br />　　（2）在主键上建立聚集索引，在fariq上建立非聚集索引： <br /><br />select gid,fariqi,neibuyonghu,title from Tgongwen <br /><br />where fariqi&gt; dateadd(day,-90,getdate()) <br /><br />　　用时：53763毫秒（54秒） <br /><br />　　（3）将聚合索引建立在日期列（fariqi）上： <br /><br />select gid,fariqi,neibuyonghu,title from Tgongwen <br /><br />where fariqi&gt; dateadd(day,-90,getdate()) <br /><br />　　用时：2423毫秒（2秒） <br /><br />　　虽然每条语句提取出来的都是25万条数据，各种情况的差异却是巨大的，特别是将聚<br />集索引建立在日期列时的差异。事实上，如果您的数据库真的有 1000万容量的话，把主键<br />建立在ID列上，就像以上的第1、2种情况，在网页上的表现就是超时，根本就无法显示。<br />这也是我摒弃ID列作为聚集索引的一个最重要的因素。 <br /><br />　　得出以上速度的方法是：在各个select语句前加：declare @d datetime <br /><br />set @d=getdate() <br /><br />并在select语句后加： <br /><br />select [语句执行花费时间(毫秒)]=datediff(ms,@d,getdate()) <br /><br />　　2、只要建立索引就能显著提高查询速度 <br /><br />　　事实上，我们可以发现上面的例子中，第2、3条语句完全相同，且建立索引的字段也<br />相同；不同的仅是前者在fariqi字段上建立的是非聚合索引，后者在此字段上建立的是聚<br />合索引，但查询速度却有着天壤之别。所以，并非是在任何字段上简单地建立索引就能提<br />高查询速度。 <br /><br />　　从建表的语句中，我们可以看到这个有着1000万数据的表中fariqi字段有5003个不同<br />记录。在此字段上建立聚合索引是再合适不过了。在现实中，我们每天都会发几个文件，<br />这几个文件的发文日期就相同，这完全符合建立聚集索引要求的：“既不能绝大多数都相<br />同，又不能只有极少数相同”的规则。由此看来，我们建立“适当”的聚合索引对于我们<br />提高查询速度是非常重要的。 <br /><br />　　3、把所有需要提高查询速度的字段都加进聚集索引，以提高查询速度 <br /><br />　　上面已经谈到：在进行数据查询时都离不开字段的是“日期”还有用户本身的“用户<br />名”。既然这两个字段都是如此的重要，我们可以把他们合并起来，建立一个复合索引（<br />compound index）。 <br /><br />　　很多人认为只要把任何字段加进聚集索引，就能提高查询速度，也有人感到迷惑：如<br />果把复合的聚集索引字段分开查询，那么查询速度会减慢吗？带着这个问题，我们来看一<br />下以下的查询速度（结果集都是25万条数据）：（日期列fariqi首先排在复合聚集索引的<br />起始列，用户名neibuyonghu排在后列） <br /><br />　　（1）select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi&gt;'2004-<br />5-5' <br /><br />　　查询速度：2513毫秒 <br /><br />　　（2）select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi&gt;'2004-<br />5-5' and neibuyonghu='办公室' <br /><br />　　查询速度：2516毫秒 <br /><br />　　（3）select gid,fariqi,neibuyonghu,title from Tgongwen where neibuyonghu='<br />办公室' <br /><br />　　查询速度：60280毫秒 <br /><br />　　从以上试验中，我们可以看到如果仅用聚集索引的起始列作为查询条件和同时用到复<br />合聚集索引的全部列的查询速度是几乎一样的，甚至比用上全部的复合索引列还要略快（<br />在查询结果集数目一样的情况下）；而如果仅用复合聚集索引的非起始列作为查询条件的<br />话，这个索引是不起任何作用的。当然，语句1、2的查询速度一样是因为查询的条目数一<br />样，如果复合索引的所有列都用上，而且查询结果少的话，这样就会形成“索引覆盖”，<br />因而性能可以达到最优。同时，请记住：无论您是否经常使用聚合索引的其他列，但其前<br />导列一定要是使用最频繁的列。 <br /><br />（四）其他书上没有的索引使用经验总结 <br /><br />　　1、用聚合索引比用不是聚合索引的主键速度快 <br /><br />　　下面是实例语句：（都是提取25万条数据） <br /><br />select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-<br />16' <br /><br />　　使用时间：3326毫秒 <br /><br />select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid&lt;=250000 <br /><br />　　使用时间：4470毫秒 <br /><br />　　这里，用聚合索引比用不是聚合索引的主键速度快了近1/4。 <br /><br />　　2、用聚合索引比用一般的主键作order by时速度快，特别是在小数据量情况下 <br /><br />select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by fariqi <br /><br />　　用时：12936 <br /><br />select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by gid <br /><br />　　用时：18843 <br /><br />　　这里，用聚合索引比用一般的主键作order by时，速度快了3/10。事实上，如果数据<br />量很小的话，用聚集索引作为排序列要比使用非聚集索引速度快得明显的多；而数据量如<br />果很大的话，如10万以上，则二者的速度差别不明显。 <br /><br />　　3、使用聚合索引内的时间段，搜索时间会按数据占整个数据表的百分比成比例减少，<br />而无论聚合索引使用了多少个 <br /><br />select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi&gt;'2004-1-<br />1' <br /><br />　　用时：6343毫秒（提取100万条） <br /><br />select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi&gt;'2004-6-<br />6' <br /><br />　　用时：3170毫秒（提取50万条） <br /><br />select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-<br />16' <br /><br />　　用时：3326毫秒（和上句的结果一模一样。如果采集的数量一样，那么用大于号和等<br />于号是一样的） <br /><br />select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi&gt;'2004-1-<br />1' and fariqi&lt;'2004-6-6' <br /><br />　　用时：3280毫秒 <br /><br />　　4 、日期列不会因为有分秒的输入而减慢查询速度 <br /><br />　　下面的例子中，共有100万条数据，2004年1月1日以后的数据有50万条，但只有两个不<br />同的日期，日期精确到日；之前有数据50万条，有5000个不同的日期，日期精确到秒。 <br /><br /><br />select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi&gt;'2004-1-<br />1' order by fariqi <br /><br />　　用时：6390毫秒 <br /><br />select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi&lt;'2004-1-<br />1' order by fariqi <br /><br />　　用时：6453毫秒 <br /><br />　　（五）其他注意事项 <br /><br />　　“水可载舟，亦可覆舟”，索引也一样。索引有助于提高检索性能，但过多或不当的<br />索引也会导致系统低效。因为用户在表中每加进一个索引，数据库就要做更多的工作。过<br />多的索引甚至会导致索引碎片。 <br /><br />　　所以说，我们要建立一个“适当”的索引体系，特别是对聚合索引的创建，更应精益<br />求精，以使您的数据库能得到高性能的发挥。 <br /><br />　　当然，在实践中，作为一个尽职的数据库管理员，您还要多测试一些方案，找出哪种<br />方案效率最高、最为有效。 <br /><br />二、改善SQL语句 <br /><br />　　很多人不知道SQL语句在SQL SERVER中是如何执行的，他们担心自己所写的SQL语句会<br />被SQL SERVER误解。比如： <br /><br />select * from table1 where name='zhangsan' and tID &gt; 10000 <br /><br />　　和执行: <br /><br />select * from table1 where tID &gt; 10000 and name='zhangsan' <br /><br />　　一些人不知道以上两条语句的执行效率是否一样，因为如果简单的从语句先后上看，<br />这两个语句的确是不一样，如果tID是一个聚合索引，那么后一句仅仅从表的10000条以后<br />的记录中查找就行了；而前一句则要先从全表中查找看有几个name='zhangsan'的，而后再<br />根据限制条件条件 tID&gt;10000来提出查询结果。 <br /><br />　　事实上，这样的担心是不必要的。SQL SERVER中有一个“查询分析优化器”，它可以<br />计算出where子句中的搜索条件并确定哪个索引能缩小表扫描的搜索空间，也就是说，它能<br />实现自动优化。 <br /><br />　　虽然查询优化器可以根据where子句自动的进行查询优化，但大家仍然有必要了解一下<br />“查询优化器”的工作原理，如非这样，有时查询优化器就会不按照您的本意进行快速查<br />询。 <br /><br />　　在查询分析阶段，查询优化器查看查询的每个阶段并决定限制需要扫描的数据量是否<br />有用。如果一个阶段可以被用作一个扫描参数（SARG），那么就称之为可优化的，并且可<br />以利用索引快速获得所需数据。 <br /><br />　　SARG的定义：用于限制搜索的一个操作，因为它通常是指一个特定的匹配，一个值得<br />范围内的匹配或者两个以上条件的AND连接。形式如下： <br /><br />列名 操作符 &lt;常数 或 变量&gt; <br /><br />或 <br /><br />&lt;常数 或 变量&gt; 操作符列名 <br /><br />　　列名可以出现在操作符的一边，而常数或变量出现在操作符的另一边。如： <br /><br />Name=’张三’ <br /><br />价格&gt;5000 <br /><br />5000&lt;价格 <br /><br />Name=’张三’ and 价格&gt;5000 <br /><br />　　如果一个表达式不能满足SARG的形式，那它就无法限制搜索的范围了，也就是SQL SE<br />RVER必须对每一行都判断它是否满足WHERE子句中的所有条件。所以一个索引对于不满足S<br />ARG形式的表达式来说是无用的。 <br /><br />　　介绍完SARG后，我们来总结一下使用SARG以及在实践中遇到的和某些资料上结论不同<br />的经验： <br /><br />　　1、Like语句是否属于SARG取决于所使用的通配符的类型 <br /><br />　　如：name like ‘张%’ ，这就属于SARG <br /><br />　　而：name like ‘%张’ ,就不属于SARG。 <br /><br />　　原因是通配符%在字符串的开通使得索引无法使用。 <br /><br />　　2、or 会引起全表扫描 <br /><br />Name=’张三’ and 价格&gt;5000 符号SARG，而：Name=’张三’ or 价格&gt;5000 则不符合S<br />ARG。使用or会引起全表扫描。 <br /><br />　　3、非操作符、函数引起的不满足SARG形式的语句 <br /><br />　　不满足SARG形式的语句最典型的情况就是包括非操作符的语句，如：NOT、!=、&lt;&gt;、!<br />&lt;、!&gt;、NOT EXISTS、NOT IN、NOT LIKE等，另外还有函数。下面就是几个不满足SARG形式<br />的例子： <br /><br />ABS(价格)&lt;5000 <br /><br />Name like ‘%三’ <br /><br />　　有些表达式，如： <br /><br />WHERE 价格*2&gt;5000 <br /><br />　　SQL SERVER也会认为是SARG，SQL SERVER会将此式转化为： <br /><br />WHERE 价格&gt;2500/2 <br /><br />　　但我们不推荐这样使用，因为有时SQL SERVER不能保证这种转化与原始表达式是完全<br />等价的。 <br /><br />　　4、IN 的作用相当与OR <br /><br />　　语句： <br /><br />Select * from table1 where tid in (2,3) <br /><br />　　和 <br /><br />Select * from table1 where tid=2 or tid=3 <br /><br />　　是一样的，都会引起全表扫描，如果tid上有索引，其索引也会失效。 <br /><br />　　5、尽量少用NOT <br /><br />　　6、exists 和 in 的执行效率是一样的 <br /><br />　　很多资料上都显示说，exists要比in的执行效率要高，同时应尽可能的用not exists<br />来代替not in。但事实上，我试验了一下，发现二者无论是前面带不带not，二者之间的执<br />行效率都是一样的。因为涉及子查询，我们试验这次用SQL SERVER自带的pubs数据库。运<br />行前我们可以把SQL SERVER的statistics I/O状态打开。 <br /><br />　　（1）select title,price from titles where title_id in (select title_id fro<br />m sales where qty&gt;30) <br /><br />　　该句的执行结果为： <br /><br />　　表 'sales'。扫描计数 18，逻辑读 56 次，物理读 0 次，预读 0 次。 <br /><br />　　表 'titles'。扫描计数 1，逻辑读 2 次，物理读 0 次，预读 0 次。 <br /><br />　　（2）select title,price from titles where exists (select * from sales wher<br />e sales.title_id=titles.title_id and qty&gt;30) <br /><br />　　第二句的执行结果为： <br /><br />　　表 'sales'。扫描计数 18，逻辑读 56 次，物理读 0 次，预读 0 次。 <br /><br />　　表 'titles'。扫描计数 1，逻辑读 2 次，物理读 0 次，预读 0 次。 <br /><br />　　我们从此可以看到用exists和用in的执行效率是一样的。 <br /><br />　　7、用函数charindex()和前面加通配符%的LIKE执行效率一样 <br /><br />　　前面，我们谈到，如果在LIKE前面加上通配符%，那么将会引起全表扫描，所以其执行<br />效率是低下的。但有的资料介绍说，用函数charindex()来代替LIKE速度会有大的提升，经<br />我试验，发现这种说明也是错误的： <br /><br />select gid,title,fariqi,reader from tgongwen where charindex('刑侦支队',reader<br />)&gt;0 and fariqi&gt;'2004-5-5' <br /><br />　　用时：7秒，另外：扫描计数 4，逻辑读 7155 次，物理读 0 次，预读 0 次。 <br /><br />select gid,title,fariqi,reader from tgongwen where reader like '%' + '刑侦支队<br />' + '%' and fariqi&gt;'2004-5-5' <br /><br />　　用时：7秒，另外：扫描计数 4，逻辑读 7155 次，物理读 0 次，预读 0 次。 <br /><br />　　8、union并不绝对比or的执行效率高 <br /><br />　　我们前面已经谈到了在where子句中使用or会引起全表扫描，一般的，我所见过的资料<br />都是推荐这里用union来代替or。事实证明，这种说法对于大部分都是适用的。 <br /><br />select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-<br />16' or gid&gt;9990000 <br /><br />　　用时：68秒。扫描计数 1，逻辑读 404008 次，物理读 283 次，预读 392163 次。 <br /><br /><br />select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-<br />16' <br /><br />union <br /><br />select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid&gt;9990000 <br /><br />　　用时：9秒。扫描计数 8，逻辑读 67489 次，物理读 216 次，预读 7499 次。 <br /><br />　　看来，用union在通常情况下比用or的效率要高的多。 <br /><br />　　但经过试验，笔者发现如果or两边的查询列是一样的话，那么用union则反倒和用or的<br />执行速度差很多，虽然这里union扫描的是索引，而or扫描的是全表。 <br /><br />select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-<br />16' or fariqi='2004-2-5' <br /><br />　　用时：6423毫秒。扫描计数 2，逻辑读 14726 次，物理读 1 次，预读 7176 次。 <br /><br /><br />select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-<br />16' <br /><br />union <br /><br />select gid,fariqi,neibuyonghu,reader,title from Tgongwen where  fariqi='2004-2<br />-5' <br /><br />　　用时：11640毫秒。扫描计数 8，逻辑读 14806 次，物理读 108 次，预读 1144 次。<br /><br /><br />　　9、字段提取要按照“需多少、提多少”的原则，避免“select *” <br /><br />　　我们来做一个试验： <br /><br />select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc <br /><br />　　用时：4673毫秒 <br /><br />select top 10000 gid,fariqi,title from tgongwen order by gid desc <br /><br />　　用时：1376毫秒 <br /><br />select top 10000 gid,fariqi from tgongwen order by gid desc <br /><br />　　用时：80毫秒 <br /><br />　　由此看来，我们每少提取一个字段，数据的提取速度就会有相应的提升。提升的速度<br />还要看您舍弃的字段的大小来判断。 <br /><br />　　10、count(*)不比count(字段)慢 <br /><br />　　某些资料上说：用*会统计所有列，显然要比一个世界的列名效率低。这种说法其实是<br />没有根据的。我们来看： <br /><br />select count(*) from Tgongwen <br /><br />　　用时：1500毫秒 <br /><br />select count(gid) from Tgongwen <br /><br />　　用时：1483毫秒 <br /><br />select count(fariqi) from Tgongwen <br /><br />　　用时：3140毫秒 <br /><br />select count(title) from Tgongwen <br /><br />　　用时：52050毫秒 <br /><br />　　从以上可以看出，如果用count(*)和用count(主键)的速度是相当的，而count(*)却比<br />其他任何除主键以外的字段汇总速度要快，而且字段越长，汇总的速度就越慢。我想，如<br />果用count(*)， SQL SERVER可能会自动查找最小字段来汇总的。当然，如果您直接写cou<br />nt(主键)将会来的更直接些。 <br /><br />　　11、order by按聚集索引列排序效率最高 <br /><br />　　我们来看：（gid是主键，fariqi是聚合索引列） <br /><br />select top 10000 gid,fariqi,reader,title from tgongwen <br /><br />　　用时：196 毫秒。 扫描计数 1，逻辑读 289 次，物理读 1 次，预读 1527 次。 <br /><br />select top 10000 gid,fariqi,reader,title from tgongwen order by gid asc <br /><br />　　用时：4720毫秒。 扫描计数 1，逻辑读 41956 次，物理读 0 次，预读 1287 次。 <br /><br /><br />select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc <br /><br />　　用时：4736毫秒。 扫描计数 1，逻辑读 55350 次，物理读 10 次，预读 775 次。 <br /><br /><br />select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi asc <br /><br />　　用时：173毫秒。 扫描计数 1，逻辑读 290 次，物理读 0 次，预读 0 次。 <br /><br />select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi desc <br /><br />　　用时：156毫秒。 扫描计数 1，逻辑读 289 次，物理读 0 次，预读 0 次。 <br /><br />　　从以上我们可以看出，不排序的速度以及逻辑读次数都是和“order by 聚集索引列”<br /> 的速度是相当的，但这些都比“order by 非聚集索引列”的查询速度是快得多的。 <br /><br />　　同时，按照某个字段进行排序的时候，无论是正序还是倒序，速度是基本相当的。 <br /><br /><br />　　12、高效的TOP <br /><br />　　事实上，在查询和提取超大容量的数据集时，影响数据库响应时间的最大因素不是数<br />据查找，而是物理的I/0操作。如： <br /><br />select top 10 * from ( <br /><br />select top 10000 gid,fariqi,title from tgongwen <br /><br />where neibuyonghu='办公室' <br /><br />order by gid desc) as a <br /><br />order by gid asc <br /><br />　　这条语句，从理论上讲，整条语句的执行时间应该比子句的执行时间长，但事实相反<br />。因为，子句执行后返回的是10000条记录，而整条语句仅返回 10条语句，所以影响数据<br />库响应时间最大的因素是物理I/O操作。而限制物理I/O操作此处的最有效方法之一就是使<br />用TOP关键词了。TOP关键词是 SQL SERVER中经过系统优化过的一个用来提取前几条或前几<br />个百分比数据的词。经笔者在实践中的应用，发现TOP确实很好用，效率也很高。但这个词<br />在另外一个大型数据库ORACLE中却没有，这不能说不是一个遗憾，虽然在ORACLE中可以用<br />其他方法（如：rownumber）来解决。在以后的关于“实现千万级数据的分页显示存储过程<br />”的讨论中，我们就将用到TOP这个关键词。 <br /><br />　　到此为止，我们上面讨论了如何实现从大容量的数据库中快速地查询出您所需要的数<br />据方法。当然，我们介绍的这些方法都是“软”方法，在实践中，我们还要考虑各种“硬<br />”因素，如：网络性能、服务器的性能、操作系统的性能，甚至网卡、交换机等。 <br /><br />三、实现小数据量和海量数据的通用分页显示存储过程 <br /><br />　　建立一个web 应用，分页浏览功能必不可少。这个问题是数据库处理中十分常见的问<br />题。经典的数据分页方法是:ADO 纪录集分页法，也就是利用ADO自带的分页功能（利用游<br />标）来实现分页。但这种分页方法仅适用于较小数据量的情形，因为游标本身有缺点：游<br />标是存放在内存中，很费内存。游标一建立，就将相关的记录锁住，直到取消游标。游标<br />提供了对特定集合中逐行扫描的手段，一般使用游标来逐行遍历数据，根据取出数据条件<br />的不同进行不同的操作。而对于多表和大表中定义的游标（大的数据集合）循环很容易使<br />程序进入一个漫长的等待甚至死机。 <br /><br />　　更重要的是，对于非常大的数据模型而言，分页检索时，如果按照传统的每次都加载<br />整个数据源的方法是非常浪费资源的。现在流行的分页方法一般是检索页面大小的块区的<br />数据，而非检索所有的数据，然后单步执行当前行。 <br /><br />　　最早较好地实现这种根据页面大小和页码来提取数据的方法大概就是“俄罗斯存储过<br />程”。这个存储过程用了游标，由于游标的局限性，所以这个方法并没有得到大家的普遍<br />认可。 <br /><br />　　后来，网上有人改造了此存储过程，下面的存储过程就是结合我们的办公自动化实例<br />写的分页存储过程： <br /><br />CREATE procedure pagination1 <br /><br />(@pagesize int,  --页面大小，如每页存储20条记录 <br /><br />@pageindex int   --当前页码 <br /><br />) <br /><br />as <br /><br />set nocount on <br /><br />begin <br /><br />declare @indextable table(id int identity(1,1),nid int)  --定义表变量 <br /><br />declare @PageLowerBound int  --定义此页的底码 <br /><br />declare @PageUpperBound int  --定义此页的顶码 <br /><br />set @PageLowerBound=(@pageindex-1)*@pagesize <br /><br />set @PageUpperBound=@PageLowerBound+@pagesize <br /><br />set rowcount @PageUpperBound <br /><br />insert into @indextable(nid) select gid from TGongwen where fariqi &gt;dateadd(da<br />y,-365,getdate()) order by fariqi desc <br /><br />select O.gid,O.mid,O.title,O.fadanwei,O.fariqi from TGongwen O,@indextable t w<br />here O.gid=t.nid <br /><br />and t.id&gt;@PageLowerBound and t.id&lt;=@PageUpperBound order by t.id <br /><br />end <br /><br />set nocount off <br /><br />　　以上存储过程运用了SQL SERVER的最新技术――表变量。应该说这个存储过程也是一<br />个非常优秀的分页存储过程。当然，在这个过程中，您也可以把其中的表变量写成临时表<br />： CREATE TABLE #Temp。但很明显，在SQL SERVER中，用临时表是没有用表变量快的。所<br />以笔者刚开始使用这个存储过程时，感觉非常的不错，速度也比原来的ADO的好。但后来，<br />我又发现了比此方法更好的方法。 <br /><br />　　笔者曾在网上看到了一篇小短文《从数据表中取出第n条到第m条的记录的方法》，全<br />文如下： <br /><br />从publish 表中取出第 n 条到第 m 条的记录： <br />SELECT TOP m-n+1 * <br />FROM publish <br />WHERE (id NOT IN <br />　　　　(SELECT TOP n-1 id <br />　　　　 FROM publish)) <br /><br />id 为publish 表的关键字 <br /><br />　　我当时看到这篇文章的时候，真的是精神为之一振，觉得思路非常得好。等到后来，<br />我在作办公自动化系统（ASP.NET+ C#＋SQL SERVER）的时候，忽然想起了这篇文章，我想<br />如果把这个语句改造一下，这就可能是一个非常好的分页存储过程。于是我就满网上找这<br />篇文章，没想到，文章还没找到，却找到了一篇根据此语句写的一个分页存储过程，这个<br />存储过程也是目前较为流行的一种分页存储过程，我很后悔没有争先把这段文字改造成存<br />储过程： <br /><br />CREATE PROCEDURE pagination2 <br />( <br />@SQL nVARCHAR(4000),    --不带排序语句的SQL语句 <br />@Page int,              --页码 <br />@RecsPerPage int,       --每页容纳的记录数 <br />@ID VARCHAR(255),       --需要排序的不重复的ID号 <br />@Sort VARCHAR(255)      --排序字段及规则 <br />) <br />AS <br /><br />DECLARE @Str nVARCHAR(4000) <br /><br />SET @Str='SELECT   TOP '+CAST(@RecsPerPage AS VARCHAR(20))+' * FROM ('+@SQL+')<br /> T WHERE T.'+@ID+'NOT IN <br />(SELECT   TOP '+CAST((@RecsPerPage*(@Page-1)) AS VARCHAR(20))+' '+@ID+' FROM (<br />'+@SQL+') T9 ORDER BY '+@Sort+') ORDER BY '+@Sort <br /><br />PRINT @Str <br /><br />EXEC sp_ExecuteSql @Str <br />GO <br /><br />　　其实，以上语句可以简化为： <br /><br />SELECT TOP 页大小 * <br /><br />FROM Table1 <br /><br />WHERE (ID NOT IN <br /><br />         (SELECT TOP 页大小*页数 id <br /><br />        FROM 表 <br /><br />        ORDER BY id)) <br /><br />ORDER BY ID <br /><br />　　但这个存储过程有一个致命的缺点，就是它含有NOT IN字样。虽然我可以把它改造为<br />： <br /><br />SELECT TOP 页大小 * <br /><br />FROM Table1 <br /><br />WHERE not exists <br /><br />(select * from (select top (页大小*页数) * from table1 order by id) b where b.<br />id=a.id ) <br /><br />order by id <br /><br />　　即，用not exists来代替not in，但我们前面已经谈过了，二者的执行效率实际上是<br />没有区别的。 <br /><br />　　既便如此，用TOP 结合NOT IN的这个方法还是比用游标要来得快一些。 <br /><br />　　虽然用not exists并不能挽救上个存储过程的效率，但使用SQL SERVER中的TOP关键字<br />却是一个非常明智的选择。因为分页优化的最终目的就是避免产生过大的记录集，而我们<br />在前面也已经提到了TOP的优势，通过TOP 即可实现对数据量的控制。 <br /><br />　　在分页算法中，影响我们查询速度的关键因素有两点：TOP和NOT IN。TOP可以提高我<br />们的查询速度，而NOT IN会减慢我们的查询速度，所以要提高我们整个分页算法的速度，<br />就要彻底改造NOT IN，同其他方法来替代它。 <br /><br />　　我们知道，几乎任何字段，我们都可以通过max(字段)或min(字段)来提取某个字段中<br />的最大或最小值，所以如果这个字段不重复，那么就可以利用这些不重复的字段的max或m<br />in作为分水岭，使其成为分页算法中分开每页的参照物。在这里，我们可以用操作符“&gt;”<br />或“&lt;”号来完成这个使命，使查询语句符合SARG形式。如： <br /><br />Select top 10 * from table1 where id&gt;200 <br /><br />　　于是就有了如下分页方案： <br /><br />select top 页大小 * <br /><br />from table1 <br /><br />where id&gt; <br /><br />     (select max (id) from <br /><br />     (select top ((页码-1)*页大小) id from table1 order by id) as T <br /><br />      )     <br /><br /> order by id <br /><br />　　在选择即不重复值，又容易分辨大小的列时，我们通常会选择主键。下表列出了笔者<br />用有着1000万数据的办公自动化系统中的表，在以GID （GID是主键，但并不是聚集索引。<br />）为排序列、提取gid,fariqi,title字段，分别以第1、10、100、500、1000、1万、10 万<br />、25万、50万页为例，测试以上三种分页方案的执行速度：（单位：毫秒） <br /><br />页  码 <br />方案1 <br />方案2 <br />方案3 <br /><br />1 <br />60 <br />30 <br />76 <br /><br />10 <br />46 <br />16 <br />63 <br /><br />100 <br />1076 <br />720 <br />130 <br /><br />500 <br />540 <br />12943 <br />83 <br /><br />1000 <br />17110 <br />470 <br />250 <br /><br />1万 <br />24796 <br />4500 <br />140 <br /><br />10万 <br />38326 <br />42283 <br />1553 <br /><br />25万 <br />28140 <br />128720 <br />2330 <br /><br />50万 <br />121686 <br />127846 <br />7168 <br /><br /><br />　　从上表中，我们可以看出，三种存储过程在执行100页以下的分页命令时，都是可以信<br />任的，速度都很好。但第一种方案在执行分页1000页以上后，速度就降了下来。第二种方<br />案大约是在执行分页1万页以上后速度开始降了下来。而第三种方案却始终没有大的降势，<br />后劲仍然很足。 <br /><br />　　在确定了第三种分页方案后，我们可以据此写一个存储过程。大家知道SQL SERVER的<br />存储过程是事先编译好的SQL语句，它的执行效率要比通过WEB页面传来的SQL语句的执行效<br />率要高。下面的存储过程不仅含有分页方案，还会根据页面传来的参数来确定是否进行数<br />据总数统计。 <br /><br />-- 获取指定页的数据 <br /><br />CREATE PROCEDURE pagination3 <br /><br />@tblName   varchar(255),       -- 表名 <br /><br />@strGetFields varchar(1000) = '*',  -- 需要返回的列 <br /><br />@fldName varchar(255)='',      -- 排序的字段名 <br /><br />@PageSize   int = 10,          -- 页尺寸 <br /><br />@PageIndex  int = 1,           -- 页码 <br /><br />@doCount  bit = 0,   -- 返回记录总数, 非 0 值则返回 <br /><br />@OrderType bit = 0,  -- 设置排序类型, 非 0 值则降序 <br /><br />@strWhere  varchar(1500) = ''  -- 查询条件 (注意: 不要加 where) <br /><br />AS <br /><br />declare @strSQL   varchar(5000)       -- 主语句 <br /><br />declare @strTmp   varchar(110)        -- 临时变量 <br /><br />declare @strOrder varchar(400)        -- 排序类型 <br /><br /><br /><br />if @doCount != 0 <br /><br /> begin <br /><br />   if @strWhere !='' <br /><br />   set @strSQL = "select count(*) as Total from [" + @tblName + "] where "+@st<br />rWhere <br /><br />   else <br /><br />   set @strSQL = "select count(*) as Total from [" + @tblName + "]" <br /><br />end <br /><br />--以上代码的意思是如果@doCount传递过来的不是0，就执行总数统计。以下的所有代码都<br />是@doCount为0的情况 <br /><br />else <br /><br />begin <br /><br /><br /><br />if @OrderType != 0 <br /><br />begin <br /><br />   set @strTmp = "&lt;(select min" <br /><br />set @strOrder = " order by [" + @fldName +"] desc" <br /><br />--如果@OrderType不是0，就执行降序，这句很重要！ <br /><br />end <br /><br />else <br /><br />begin <br /><br />   set @strTmp = "&gt;(select max" <br /><br />   set @strOrder = " order by [" + @fldName +"] asc" <br /><br />end <br /><br /><br /><br />if @PageIndex = 1 <br /><br />begin <br /><br />   if @strWhere != ''   <br /><br />   set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ "  from ["<br /> + @tblName + "] where " + @strWhere + " " + @strOrder <br /><br />    else <br /><br />    set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ "  from [<br />"+ @tblName + "] "+ @strOrder <br /><br />--如果是第一页就执行以上代码，这样会加快执行速度 <br /><br />end <br /><br />else <br /><br />begin <br /><br />--以下代码赋予了@strSQL以真正执行的SQL代码 <br /><br />set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ "  from [" <br /><br />   + @tblName + "] where [" + @fldName + "]" + @strTmp + "(["+ @fldName + "]) <br />from (select top " + str((@PageIndex-1)*@PageSize) + " ["+ @fldName + "] from <br />[" + @tblName + "]" + @strOrder + ") as tblTmp)"+ @strOrder <br /><br /><br /><br />if @strWhere != '' <br /><br />   set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ "  from ["<br /><br /><br />       + @tblName + "] where [" + @fldName + "]" + @strTmp + "([" <br /><br />       + @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + "<br /> [" <br /><br />       + @fldName + "] from [" + @tblName + "] where " + @strWhere + " " <br /><br />       + @strOrder + ") as tblTmp) and " + @strWhere + " " + @strOrder <br /><br />end <br /><br />end   <br /><br />exec (@strSQL) <br /><br />GO <br /><br />　　上面的这个存储过程是一个通用的存储过程，其注释已写在其中了。 <br /><br />　　在大数据量的情况下，特别是在查询最后几页的时候，查询时间一般不会超过9秒；而<br />用其他存储过程，在实践中就会导致超时，所以这个存储过程非常适用于大容量数据库的<br />查询。 <br /><br />　　笔者希望能够通过对以上存储过程的解析，能给大家带来一定的启示，并给工作带来<br />一定的效率提升，同时希望同行提出更优秀的实时数据分页算法。 <br /><br />四、聚集索引的重要性和如何选择聚集索引 <br /><br />　　在上一节的标题中，笔者写的是：实现小数据量和海量数据的通用分页显示存储过程<br />。这是因为在将本存储过程应用于“办公自动化”系统的实践中时，笔者发现这第三种存<br />储过程在小数据量的情况下，有如下现象： <br /><br />　　1、分页速度一般维持在1秒和3秒之间。 <br /><br />　　2、在查询最后一页时，速度一般为5秒至8秒，哪怕分页总数只有3页或30万页。 <br /><br />　　虽然在超大容量情况下，这个分页的实现过程是很快的，但在分前几页时，这个1－3<br />秒的速度比起第一种甚至没有经过优化的分页方法速度还要慢，借用户的话说就是“还没<br />有ACCESS数据库速度快”，这个认识足以导致用户放弃使用您开发的系统。 <br /><br />　　笔者就此分析了一下，原来产生这种现象的症结是如此的简单，但又如此的重要：排<br />序的字段不是聚集索引！ <br /><br />　　本篇文章的题目是：“查询优化及分页算法方案”。笔者只所以把“查询优化”和“<br />分页算法”这两个联系不是很大的论题放在一起，就是因为二者都需要一个非常重要的东<br />西――聚集索引。 <br /><br />　　在前面的讨论中我们已经提到了，聚集索引有两个最大的优势： <br /><br />　　1、以最快的速度缩小查询范围。 <br /><br />　　2、以最快的速度进行字段排序。 <br /><br />　　第1条多用在查询优化时，而第2条多用在进行分页时的数据排序。 <br /><br />　　而聚集索引在每个表内又只能建立一个，这使得聚集索引显得更加的重要。聚集索引<br />的挑选可以说是实现“查询优化”和“高效分页”的最关键因素。 <br /><br />　　但要既使聚集索引列既符合查询列的需要，又符合排序列的需要，这通常是一个矛盾<br />。 <br /><br />　　笔者前面“索引”的讨论中，将fariqi，即用户发文日期作为了聚集索引的起始列，<br />日期的精确度为“日”。这种作法的优点，前面已经提到了，在进行划时间段的快速查询<br />中，比用ID主键列有很大的优势。 <br /><br />　　但在分页时，由于这个聚集索引列存在着重复记录，所以无法使用max或min来最为分<br />页的参照物，进而无法实现更为高效的排序。而如果将ID主键列作为聚集索引，那么聚集<br />索引除了用以排序之外，没有任何用处，实际上是浪费了聚集索引这个宝贵的资源。 <br /><br />　　　为解决这个矛盾，笔者后来又添加了一个日期列，其默认值为getdate()。用户在写<br />入记录时，这个列自动写入当时的时间，时间精确到毫秒。即使这样，为了避免可能性很<br />小的重合，还要在此列上创建UNIQUE约束。将此日期列作为聚集索引列。 <br /><br />　　有了这个时间型聚集索引列之后，用户就既可以用这个列查找用户在插入数据时的某<br />个时间段的查询，又可以作为唯一列来实现max或min，成为分页算法的参照物。 <br /><br />　　经过这样的优化，笔者发现，无论是大数据量的情况下还是小数据量的情况下，分页<br />速度一般都是几十毫秒，甚至0毫秒。而用日期段缩小范围的查询速度比原来也没有任何迟<br />钝。 <br /><br />　　聚集索引是如此的重要和珍贵，所以笔者总结了一下，一定要将聚集索引建立在： <br /><br /><br />　　1、您最频繁使用的、用以缩小查询范围的字段上； <br /><br />　　2、您最频繁使用的、需要排序的字段上。 <br /><br />　　结束语： <br /><br />　　希望这篇文章不仅能够给大家的工作带来一定的帮助，也希望能让大家能够体会到分<br />析问题的方法；最重要的是，希望这篇文章能够抛砖引玉，掀起大家的学习和讨论的兴趣<br />，以共同促进。 <br />　　最后需要说明的是，在试验中，发现用户在进行大数据量查询的时候，对数据库速度<br />影响最大的不是内存大小，而是CPU。在我的P4 2.4机器上试验的时候，查看“资源管理器<br />”，CPU经常出现持续到100%的现象，而内存用量却并没有改变或者说没有大的改变。即使<br />在我们的HP ML 350 G3服务器上试验时，CPU峰值也能达到90%，一般持续在70%左右。 <br /><br />　　本文的试验数据都是来自我们的HP ML 350服务器。服务器配置：双Inter Xeon 超线<br />程 CPU 2.4G，内存1G，操作系统Windows Server 2003 Enterprise Edition，数据库SQL<br /> Server 2000 SP3。 <br /><br />转载完毕. <br /><br /><br />作者补充: <br />1.columns in('aa','bb') <br />他等于columns = 'aa' or columns ='bb' 他先去查询columns ='aa'放在一个临时的空间<br />里,然后等columns ='bb'查询完后,做个or查询得出结果. <br />至于效率的话，在columns建立索引的话, columns ='aa' or columns ='bb'要来的效率高<br /><br />语法分析器会将columns in('aa','bb')转化 <br />为columns ='aa' or columns ='bb'来执行。我们期望它会根据每个or子句分别查找，再<br />将结果 <br />相加，这样可以利用columns 上的索引；但实际上（根据showplan）,它却采用了"OR策略<br />" <br />，即先取出满足每个or子句的行，存入临时数据库的工作表中，再建立唯一索引以去掉 <br /><br />重复行，最后从这个临时表中计算结果。因此，实际过程没有利用columns 上索引，并且<br />完 <br />成时间还要受tempdb数据库性能的影响。 <br /><br /><br />2.效率从高到低 count(1)&gt;count(*)&gt;count([id]) <br /><br />3.select max(cols) from table1 的效率&gt;= select top 1 cols from table1 order by<br /> cols desc <br /><br />4.在where 做并列条件句时,where cols1='aa' and cols2='bb' <br />如果cols1 ='aa' 占95% cols2占5%的话，把cols2='bb'放在前面 ,因为他在检索cols ='<br />bb'的时候他只需查那5%,然后条件成立的话，去在这5%的纪录里 <br />去查找cols1 ='aa' <br /><br />5.避免用if条件句,可以用or来替代. <br />declare @vsql varchar(200) <br />set @vsql ='Renaski' <br />select * from titles where  @vsql ='Renaski' or price = 11.9500 <br /><br />如果@vsql为Renaski则把所有的纪录都选出来,如果不是的话，则只查询price = 11.9500<br /> 的纪录. <br /><br />6.任何对列的操作都将导致表扫描，它包括数据库函数、计算表达式等等，查询时 <br />要尽可能将操作移至等号右边。 <br /><br />7.尽量避免使用游标. <br />如果使用了游标，就要尽量避免在游标循环中再进行表连接的操作 <br /><br />8.取一个表的纪录数 <br />Select rows from sysindexes where id=object_id(N'titles') and indid&lt;2 <br />效率比 <br />select count(1) from titles来的高. <br /><br /><br />9.取的一个表的数据信息. <br />SELECT <br />表名=case when a.colorder=1 then d.name else '' end, <br />表说明=case when a.colorder=1 then isnull(f.value,'') else '' end, <br />字段序号=a.colorder, <br />字段名=a.name, <br />标识=case when COLUMNPROPERTY( a.id,a.name,'IsIdentity')=1 then '√'else '' en<br />d, <br />主键=case when exists(SELECT 1 FROM sysobjects where xtype='PK' and name in ( <br /><br />SELECT name FROM sysindexes WHERE indid in( <br />SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid <br />))) then '√' else '' end, <br />类型=b.name, <br />占用字节数=a.length, <br />长度=COLUMNPROPERTY(a.id,a.name,'PRECISION'), <br />小数位数=isnull(COLUMNPROPERTY(a.id,a.name,'Scale'),0), <br />允许空=case when a.isnullable=1 then '√'else '' end, <br />默认值=isnull(e.text,''), <br />字段说明=isnull(g.[value],''), <br />索引名称=isnull(h.索引名称,''), <br />索引顺序=isnull(h.排序,'') <br />FROM syscolumns a <br />left join systypes b on a.xtype=b.xusertype <br />inner join sysobjects d on a.id=d.id and d.xtype='U' and d.status&gt;=0 <br />left join syscomments e on a.cdefault=e.id <br />left join sysproperties g on a.id=g.id and a.colid=g.smallid <br />left join sysproperties f on d.id=f.id and f.smallid=0 <br />left join(--这部分是索引信息,如果要显示索引与表及字段的对应关系,可以只要此部分<br /><br />select 索引名称=a.name,c.id,d.colid <br />,排序=case indexkey_property(c.id,b.indid,b.keyno,'isdescending') <br />when 1 then '降序' when 0 then '升序' end <br />from sysindexes a <br />join sysindexkeys b on a.id=b.id and a.indid=b.indid <br />join (--这里的作用是有多个索引时,取索引号最小的那个 <br />select id,colid,indid=min(indid) from sysindexkeys <br />group by id,colid) b1 on b.id=b1.id and b.colid=b1.colid and b.indid=b1.indid <br /><br />join sysobjects c on b.id=c.id and c.xtype='U' and c.status&gt;=0 <br />join syscolumns d on b.id=d.id and b.colid=d.colid <br />where a.indid not in(0,255) <br />) h on a.id=h.id and a.colid=h.colid <br />--where d.name='要查询的表' --如果只查询指定表,加上此条件 <br />order by a.id,a.colorder <br /><br /><br />10.创建一个表结构. <br />select * into #b from authors where 1=2; <br />注意: <br />#table1 <br />##table1 <br />@table1 <br /><br />局部临时表 <br />以一个井号（#）开头的那些表名。只有在创建本地临时表的连接上才能看到这些表。 <br /><br />全局临时表 <br />以两个井号（##）开头的那些表名。在所有连接上都能看到全局临时表。如果在创建全局<br />临时表的连接断开前没有显式地除去这些表，那么只要所有其它任务停止引用它们，这些<br />表即被除去。当创建全局临时表的连接断开后，新的任务不能再引用它们。当前的语句一<br />执行完，任务与表之间的关联即被除去；因此通常情况下，只要创建全局临时表的连接断<br />开，全局临时表即被除去。 <br /><br />@和#有和不同：@@在内存，#在硬盘。我的体会是只要方便且数据量不大，使用@@。 <br /><br />11.视图 <br />他只是记住要连接,关联列的信息,他不存放任何物理数据. <br />在调用的时候他还是去取各个表中的数据. <br /><br />12.尽量不要用text属性 <br />系统为他专门开辟一个空间来存放. <br />用t-sql/varchar替代 <br />pl/sql  varchar2 替代. <br /><br />13 <br />GO语句是个命令识别并通过osql和isql和SQL 查询分析器非T-SQL语句进行识别。 <br />如果你使用查询分析器作为你的主开发工具，其他语句和库文件将不会识别GO语句作为一<br />个T-SQL命令 <br /><br />14. <br />用exec 效率来的高. <br />declare @sql nvarchar(300) <br />  set @sql='select * from titles' <br />execute sp_executesql @sql <br /><br />15,注意你的tempdb,使他自动增长. <br /><br />16 使用no_log <br />select * from titles no_logs <br /><br />17去不重复纪录时,尽量用dictinct <br /><br />18.尽量避免反复访问同一张或几张表，尤其是数据量较大的表，可以考虑先根据条件提取<br />数据到临时表中，然后再做连接。 <br /><br /><br />19 尽量使用“&gt;=”，不要使用“&gt;”。 他会找到某个确定的数字进行筛选,而&gt;则没有. <br /><br /><br />20注意表之间连接的数据类型，避免不同类型数据之间的连接。 <br /><br />21.可用ASE调优命令：set statistics io on, set statistics time on , set showpla<br />n on 等,进行优化 <br /><br />22.truncate table 删除数据 <br />而不是delete from table <br /><br /><br />三.死锁 <br /><br />像SQL server一样的关系数据库使用锁来防止用户“互相踩到对方的脚趾头”。也就是说<br />，锁可以防止用户造成修改数据时的碰撞。当一个用户锁住一段代码时候，其它的用户都<br />不能修改这段数据。另外，一个锁阻止了用户观看未被授权的数据修改。用户必须等待到<br />数据修改并保存之后才能够查看它。数据必须使用不同的方法来加锁。SQL Server 2000使<br />用锁来实现多用户同时修改数据库同一数据时的同步控制 <br />如果数据量超过200个数据页面（400k），那么系统将会进行锁升级，页级锁会升级成表级<br />锁。 　　 <br /><br /><br />死锁 <br />一个数据库的死锁是发生在两个或多于两个访问一些资源的数据库会话中的，并且这些会<br />话相互之间有依赖关系。死锁是可以在任意一个多线程的系统成出现的一个情况，不仅仅<br />局限于关系数据库管理系统。一个多线程系统中的线程可能需要一个或多个资源(例如，锁<br />)。如果申请的资源正在被另外一个线程所使用，那么第一个线程就需要等待持有该资源的<br />线程的释放它所需要的资源。假设等待线程持有一个那个正拥有线程所依赖的资源。下面<br />的这一段代码就可以造成死锁异常现象的发生： <br />System.Data.SqlClient.SqlException: Transaction (Process ID 12) was deadlocked<br /> on lock resources with another process and has been chosen as the deadlock vi<br />ctim. Rerun the transaction. <br /><br />当一个SQL Server的调用和另外一个资源发生冲突时就会抛出异常，这个资源持有一个必<br />要的资源。结果是，一个进程就被终止了。当进程的ID号成为系统的唯一标识的时候，这<br />会是一个很平常死锁的消息错误。 <br /><br /><br />锁的类型 <br />一个数据库系统在许多情况下都有可能锁数据项。其可能性包括： <br /><br />Rows—数据库表中的一整行 <br />Pages—行的集合（通常为几kb） <br />Extents—通常是几个页的集合 <br />Table—整个数据库表 <br />Database—被锁的整个数据库表 <br /><br />除非有其它的说明，数据库根据情况自己选择最好的锁方式。不过值得感谢的是，SQL Se<br />rver提供了一种避免默认行为的方法。这是由锁提示来完成的。 <br /><br /><br />提示 <br />或许你有过许多如下的经历：需要重设SQL Server的锁计划，并且加强数据库表中锁范围<br />。Tansact－SQL提供了一系列不同级别的锁提示，你可以在SELECT,INSERT, UPDATE和DEL<br />ETE中使用它们来告诉SQL Server你需要如何通过重设任何的系统或事务级别来锁表格。可<br />以实现的提示包括： <br /><br />FASTFIRSTROW—选取结果集中的第一行，并将其优化 <br />HOLDLOCK—持有一个共享锁直至事务完成 <br />NOLOCK—不允许使用共享锁或独享锁。这可能会造成数据重写或者没有被确认就返回的情<br />况；因此，就有可能使用到脏数据。这个提示只能在SELECT中使用。 <br />PAGLOCK—锁表格 <br />READCOMMITTED—只读取被事务确认的数据。这就是SQL Server的默认行为。 <br />READPAST—跳过被其它进程锁住的行，所以返回的数据可能会忽略行的内容。这也只能在<br />SELECT中使用。 <br />READUNCOMMITTED—等价于NOLOCK. <br />REPEATABLEREAD—在查询语句中，对所有数据使用锁。这可以防止其它的用户更新数据，<br />但是新的行可能被其它的用户插入到数据中，并且被最新访问该数据的用户读取。 <br />ROWLOCK—按照行的级别来对数据上锁。SQL Server通常锁到页或者表级别来修改行，所以<br />当开发者使用单行的时候，通常要重设这个设置。 <br />SERIALIZABLE—等价于HOLDLOCK. <br />TABLOCK—按照表级别上锁。在运行多个有关表级别数据操作的时候，你可能需要使用到这<br />个提示。 <br />UPDLOCK—当读取一个表的时候，使用更新锁来代替共享锁，并且保持一直拥有这个锁直至<br />事务结束。它的好处是，可以允许你在阅读数据的时候可以不需要锁，并且以最快的速度<br />更新数据。 <br />XLOCK—给所有的资源都上独享锁，直至事务结束。 <br /><br />对于数据库死锁，通常可以通过TRACE FLAG 1204、1205、1206，检查ERRORLOG里面的输出<br />，和分析SQLTRACE的执行上下文判断死锁问题的来由。 <br />TRACEON函数的第三个参数设置为-1，表示不单单针对当前connection，而是针对所有包括<br />未来建立的connection。这样，才够完全，否则只是监视当前已经建立的数据库连接了。<br /><br /><br /><br />执行下面的话可以把死锁记录到Errorlog中： <br /><br />dbcc traceon (1204, 3605, -1) <br />go <br />dbcc tracestatus(-1) <br />go <br /><br />  <br /><br />得到的输出为： <br />DBCC 执行完毕。如果 DBCC 输出了错误信息，请与系统管理员联系。 <br />TraceFlag Status <br />--------- ------ <br />1204      1 <br />1205      1 <br />3605      1 <br /><br />（所影响的行数为 3 行） <br /><br />DBCC 执行完毕。如果 DBCC 输出了错误信息，请与系统管理员联系。 <br /><br /><br />此后，你可以查看数据库的例行日志，每隔一段时间，数据库都会检查死锁 <br />2004-01-16 18:34:38.50 spid4     ---------------------------------- <br />2004-01-16 18:34:38.50 spid4     Starting deadlock search 1976 <br /><br /><br /><br />2004-01-16 18:34:38.50 spid4     Target Resource Owner: <br />2004-01-16 18:34:38.50 spid4      ResType:LockOwner Stype:'OR' Mode: U SPID:55<br /> ECID:0 Ec:(0xAA577570) Value:0x4c25cba0 <br />2004-01-16 18:34:38.50 spid4      Node:1  ResType:LockOwner Stype:'OR' Mode: U<br /> SPID:55 ECID:0 Ec:(0xAA577570) Value:0x4c25cba0 <br />2004-01-16 18:34:38.50 spid4      Node:2  ResType:LockOwner Stype:'OR' Mode: U<br /> SPID:71 ECID:0 Ec:(0xABF07570) Value:0x9bd0ba00 <br />2004-01-16 18:34:38.50 spid4     <br />2004-01-16 18:34:38.50 spid4     -- next branch -- <br />2004-01-16 18:34:38.50 spid4      Node:2  ResType:LockOwner Stype:'OR' Mode: U<br /> SPID:71 ECID:0 Ec:(0xABF07570) Value:0x9bd0ba00 <br />2004-01-16 18:34:38.50 spid4     <br />2004-01-16 18:34:38.50 spid4     <br />2004-01-16 18:34:38.50 spid4     End deadlock search 1976 ... a deadlock was n<br />ot found. <br />2004-01-16 18:34:38.50 spid4     ---------------------------------- <br /><br />DBCC TRACEON打开（启用）指定的跟踪标记。 <br /><br />注释跟踪标记用于自定义某些控制 Microsoft? SQL Server? 操作方式的特性。跟踪标记<br />在服务器中一直保持启用状态，直到通过执行 DBCC TRACEOFF 语句对其禁用为止。在发出<br /> DBCC TRACEON 语句之前，连入到服务器的新连接看不到任何跟踪标记。一旦发出该语句<br />，该连接就能看到服务器中当前启用的所有跟踪标记（即使这些标记是由其它连接启用）<br />。 <br />跟踪标记跟踪标记用于临时设置服务器的特定特征或关闭特定行为。如果启动 Microsoft<br />? SQL Server 时设置了跟踪标记 3205，将禁用磁带驱动程序的硬件压缩。跟踪标记经常<br />用于诊断性能问题，或调试存储过程或复杂的计算机系统。 <br />下列跟踪标记在 SQL Server 中可用。跟踪标记 描述 1204 返回参与死锁的锁的类型以及<br />当前受影响的命令。 <br />实际上可以在“错误 1000 -1999”中找到他们： <br />1204 19 SQL Server 此时无法获取 LOCK 资源。请在活动用户数较少时重新运行您的语句<br />，或者请求系统管理员检查 SQL Server 锁和内存配置。 <br />1205 13 事务（进程 ID %1!）与另一个进程已被死锁在资源 {%2!} 上，且该事务已被选<br />作死锁牺牲品。请重新运行该事务。 <br />1206 18 事务管理器已取消了分布式事务。 <br /><br />需要指出的是对锁的升级,完全是由系统自行判断的,而非人为.如果要避免死锁的话，其根<br />本还在与数据库的设计上<br /></p>
<img src ="http://www.blogjava.net/weibogao/aggbug/93392.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weibogao/" target="_blank">weibogao</a> 2007-01-12 12:56 <a href="http://www.blogjava.net/weibogao/archive/2007/01/12/93392.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转《数据库设计经验》</title><link>http://www.blogjava.net/weibogao/archive/2007/01/11/93125.html</link><dc:creator>weibogao</dc:creator><author>weibogao</author><pubDate>Thu, 11 Jan 2007 04:09:00 GMT</pubDate><guid>http://www.blogjava.net/weibogao/archive/2007/01/11/93125.html</guid><wfw:comment>http://www.blogjava.net/weibogao/comments/93125.html</wfw:comment><comments>http://www.blogjava.net/weibogao/archive/2007/01/11/93125.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weibogao/comments/commentRss/93125.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weibogao/services/trackbacks/93125.html</trackback:ping><description><![CDATA[
		<h3 class="title"> </h3>
		<p>2004-11-24微软技术博客 </p>
		<p>作者： wjk.net(转载)</p>
		<p>一个成功的管理系统，是由：[50% 的业务 + 50% 的软件] 所组成，而 50% 的成功软件又有 [25% 的数据库 + 25%的程序] 所组成，数据库设计的好坏是一个关键。如果把企业的数据比做生命 所必需的血液，那么数据库的设计就是应用中最重要的一部分 。有关数据库设计的材料汗牛充栋，大学学位课程里也有专门的讲述 。不过，就如我们反复强调的那样，再好的老师也比不过经验的教诲 。所以我归纳历年来所走的弯路及体会，并在网上找了些对数据库设 计颇有造诣的专业人士给大家传授一些设计数据库的技巧和经验 。精选了其中的60 个最佳技巧，并把这些技巧编写成了本文，为了方便索引其内容划分 为 5 个部分：</p>
		<p>
				<br />
		</p>
		<p>第 1 部分 - 设计数据库之前 </p>
		<p>这一部分罗列了 12 个基本技巧，包括命名规范和明确业务需求等。 </p>
		<p>第 2 部分 - 设计数据库表 </p>
		<p>总共 24 个指南性技巧，涵盖表内字段设计以及应该避免的常见问题等。 </p>
		<p>第 3 部分 - 选择键 </p>
		<p>怎么选择键呢？这里有 10 个技巧专门涉及系统生成的主键的正确用法，还有何 时以及如何索引字段以获得最佳性能等。 </p>
		<p>第 4 部分 - 保证数据完整性 </p>
		<p>讨论如何保持数据库的清晰和健壮，如何把有害数据降低到最小程度 。 </p>
		<p>第 5 部分 - 各种小技巧 </p>
		<p>不包括在以上 4 个部分中的其他技巧，五花八门，有了它们希望你的数据库开发工作 会更轻松一些。 </p>
		<p>
				<br />第 1 部分 - 设计数据库之前 </p>
		<p>
				<br />考察现有环境 </p>
		<p>在设计一个新数据库时，你不但应该仔细研究业务需求而且还要考察 现有的系统。大多数数据库项目都不是从头开始建立的；通常 ，机构内总会存在用来满足特定需求的现有系统（可能没有实现自动 计算）。显然，现有系统并不完美，否则你就不必再建立新系统了 。但是对旧系统的研究可以让你发现一些可能会忽略的细微问题 。一般来说，考察现有系统对你绝对有好处。 </p>
		<p>定义标准的对象命名规范 </p>
		<p>一定要定义数据库对象的命名规范。对数据库表来说 ，从项目一开始就要确定表名是采用复数还是单数形式 。此外还要给表的别名定义简单规则（比方说，如果表名是一个单词 ，别名就取单词的前4 个字母；如果表名是两个单词，就各取两个单词的前两个字母组成 4 个字母长的别名；如果表的名字由 3个单词组成，你不妨从头两个单词中各取一个然后从最后一个单词中 再取出两个字母，结果还是组成 4字母长的别名，其余依次类推）对工作用表来说，表名可以加上前缀 WORK_后面附上采用该表的应用程序的名字。表内的列[字段 ]要针对键采用一整套设计规则。比如，如果键是数字类型 ，你可以用 _N作为后缀；如果是字符类型则可以采用 _C后缀。对列[字段]名应该采用标准的前缀和后缀。再如 ，假如你的表里有好多"money"字段，你不妨给每个列[字段 ]增加一个 _M后缀。还有，日期列[字段]最好以 D_ 作为名字打头。</p>
		<p>检查表名、报表名和查询名之间的命名规范。你可能会很快就被这些 不同的数据库要素的名称搞糊涂了。假如你坚持统一地命名这些数据 库的不同组成部分，至少你应该在这些对象名字的开头用Table、Query 或者 Report 等前缀加以区别。</p>
		<p>如果采用了 Microsoft Access，你可以用 qry、rpt、tbl 和 mod 等符号来标识对象（比如tbl_Employees）。我在和 SQL Server 打交道的时候还用过 tbl 来索引表，但我用 sp_company （现在用 sp_feft_）标识存储过程，因为在有的时候如果我发现了更 好的处理办法往往会保存好几个拷贝。我在实现 SQL Server 2000 时用udf_ （或者类似的标记）标识我编写的函数。 </p>
		<p>
				<br />工欲善其事, 必先利其器 </p>
		<p>采用理想的数据库设计工具，比如：SyBase 公司的 PowerDesign，她支持 PB、VB、Delphe 等语言，通过 ODBC可以连接市面上流行的 30 多个数据库，包括 dBase、FoxPro、VFP、SQL Server 等，今后有机会我将着重介绍PowerDesign 的使用。 </p>
		<p>获取数据模式资源手册 </p>
		<p>正在寻求示例模式的人可以阅读《数据模式资源手册》一书，该书由 Len Silverston、W. H. Inmon 和 Kent Graziano 编写，是一本值得拥有的最佳数据建模图书。该书包括的章节涵盖多 种数据领域，比如人员、机构和工作效能等。其他的你还可以参考： [1]萨师煊王珊著数据库系统概论(第二版)高等教育出版社 1991、[2][美] Steven M.Bobrowski 著 Oracle 7<br />与客户／服务器计算技术从入门到精通刘建元等译电子工业出版社，1996、[3]周中元信息系统建模方法(下)电子与信息化1999年第3期，1999畅想未来，但不可忘了过去的教训我发现询问用户如何看待未来需求变化非常有用。这样做可以达到两 个目的：首先，你可以清楚地了解应用设计在哪个地方应该更具灵活 性以及如何避免性能瓶颈；其次，你知道发生事先没有确定的需求变 更时用户将和你一样感到吃惊。</p>
		<p>一定要记住过去的经验教训！我们开发人员还应该通过分享自己的体 会和经验互相帮助。即使用户认为他们再也不需要什么支持了 ，我们也应该对他们进行这方面的教育，我们都曾经面临过这样的时 刻"当初要是这么做了该多好.."。 </p>
		<p>在物理实践之前进行逻辑设计 </p>
		<p>在深入物理设计之前要先进行逻辑设计。随着大量的 CASE工具不断涌现出来，你的设计也可以达到相当高的逻辑水准 ，你通常可以从整体上更好地了解数据库设计所需要的方方面面。 </p>
		<p>了解你的业务 </p>
		<p>在你百分百地确定系统从客户角度满足其需求之前不要在你的 ER（实体关系）模式中加入哪怕一个数据表（怎么，你还没有模式 ？那请你参看技巧9）。了解你的企业业务可以在以后的开发阶段节约大量的时间 。一旦你明确了业务需求，你就可以自己做出许多决策了。</p>
		<p>一旦你认为你已经明确了业务内容，你最好同客户进行一次系统的交 流。采用客户的术语并且向他们解释你所想到的和你所听到的 。同时还应该用可能、将会和必须等词汇表达出系统的关系基数 。这样你就可以让你的客户纠正你自己的理解然后做好下一步的ER 设计。 </p>
		<p>创建数据字典和 ER 图表 </p>
		<p>一定要花点时间创建 ER 图表和数据字典。其中至少应该包含每个字段的数据类型和在每个表 内的主外键。创建 ER<br />图表和数据字典确实有点费时但对其他开发人员要了解整个设计却是 完全必要的。越早创建越能有助于避免今后面临的可能混乱 ，从而可以让任何了解数据库的人都明确如何从数据库中获得数据。</p>
		<p>有一份诸如 ER 图表等最新文档其重要性如何强调都不过分，这对表明表之间关系很 有用，而数据字典则说明了每个字段的用途以及任何可能存在的别名 。对SQL 表达式的文档化来说这是完全必要的。 </p>
		<p>创建模式 </p>
		<p>一张图表胜过千言万语：开发人员不仅要阅读和实现它 ，而且还要用它来帮助自己和用户对话。模式有助于提高协作效能 ，这样在先期的数据库设计中几乎不可能出现大的问题 。模式不必弄的很复杂；甚至可以简单到手写在一张纸上就可以了 。只是要保证其上的逻辑关系今后能产生效益。 </p>
		<p>从输入输出下手 </p>
		<p>在定义数据库表和字段需求（输入）时，首先应检查现有的或者已经 设计出的报表、查询和视图（输出）以决定为了支持这些输出哪些是 必要的表和字段。举个简单的例子：假如客户需要一个报表按照邮政 编码排序、分段和求和，你要保证其中包括了单独的邮政编码字段而 不要把邮政编码糅进地址字段里。 </p>
		<p>报表技巧 </p>
		<p>要了解用户通常是如何报告数据的：批处理还是在线提交报表 ？时间间隔是每天、每周、每月、每个季度还是每年 ？如果需要的话还可以考虑创建总结表。系统生成的主键在报表中很 难管理。用户在具有系统生成主键的表内用副键进行检索往往会返回 许多重复数据。这样的检索性能比较低而且容易引起混乱。 </p>
		<p>理解客户需求 </p>
		<p>看起来这应该是显而易见的事，但需求就是来自客户 （这里要从内部和外部客户的角度考虑）。不要依赖用户写下来的需 求，真正的需求在客户的脑袋里。你要让客户解释其需求 ，而且随着开发的继续，还要经常询问客户保证其需求仍然在开发的 目的之中。一个不变的真理是："只有我看见了我才知道我想要的是 什么"必然会导致大量的返工，因为数据库没有达到客户从来没有写 下来的需求标准。而更糟的是你对他们需求的解释只属于你自己 ，而且可能是完全错误的。 </p>
		<p>
				<br />第 2 部分 - 设计表和字段 </p>
		<p>
				<br />检查各种变化 </p>
		<p>我在设计数据库的时候会考虑到哪些数据字段将来可能会发生变更 。比方说，姓氏就是如此（注意是西方人的姓氏，比如女性结婚后从 夫姓等）。所以，在建立系统存储客户信息时，我倾向于在单独的一 个数据表里存储姓氏字段，而且还附加起始日和终止日等字段 ，这样就可以跟踪这一数据条目的变化。 </p>
		<p>采用有意义的字段名 </p>
		<p>有一回我参加开发过一个项目，其中有从其他程序员那里继承的程序 ，那个程序员喜欢用屏幕上显示数据指示用语命名字段，这也不赖 ，但不幸的是，她还喜欢用一些奇怪的命名法，其命名采用了匈牙利 命名和控制序号的组合形式，比如cbo1、txt2、txt2_b 等等。<br />除非你在使用只面向你的缩写字段名的系统，否则请尽可能地把字段 描述的清楚些。当然，也别做过头了，比如Customer_Shipping_Address _Street_Line_1，虽然很富有说明性 ，但没人愿意键入这么长的名字，具体尺度就在你的把握中。<br />采用前缀命名。如果多个表里有好多同一类型的字段（比如 FirstName），你不妨用特定表的前缀（比如 CusLastName）来帮助你标识字段。</p>
		<p>时效性数据应包括"最近更新日期/时间"字段。时间标记对查找数 据问题的原因、按日期重新处理/重载数据和清除旧数据特别有用。<br />标准化和数据驱动数据的标准化不仅方便了自己而且也方便了其他人。比方说 ，假如你的用户界面要访问外部数据源（文件、XML<br />文档、其他数据库等），你不妨把相应的连接和路径信息存储在用户 界面支持表里。还有，如果用户界面执行工作流之类的任务 （发送邮件、打印信笺、修改记录状态等），那么产生工作流的数据 也可以存放在数据库里。预先安排总需要付出努力，但如果这些过程 采用数据驱动而非硬编码的方式，那么策略变更和维护都会方便得多 。事实上，如果过程是数据驱动的，你就可以把相当大的责任推给用 户，由用户来维护自己的工作流过程。 </p>
		<p>标准化不能过头 </p>
		<p>对那些不熟悉标准化一词（normalization）的人而言 ，标准化可以保证表内的字段都是最基础的要素，而这一措施有助于 消除数据库中的数据冗余。标准化有好几种形式，但Third Normal Form（3NF）通常被认为在性能、扩展性和数据完整性方面达 到了最好平衡。简单来说，3NF 规定：<br />* 表内的每一个值都只能被表达一次。<br />* 表内的每一行都应该被唯一的标识（有唯一键）。<br />* 表内不应该存储依赖于其他键的非键信息。<br />遵守 3NF 标准的数据库具有以下特点：有一组表专门存放通过键连接起来的关 联数据。比方说，某个存放客户及其有关定单的 3NF<br />数据库就可能有两个表：Customer 和 Order。Order 表不包含定单关联客户的任何信息，但表内会存放一个键值 ，该键指向Customer 表里包含该客户信息的那一行。<br />更高层次的标准化也有，但更标准是否就一定更好呢？答案是不一定 。事实上，对某些项目来说，甚至就连 3NF 都可能给数据库引入太高的复杂性。</p>
		<p>为了效率的缘故，对表不进行标准化有时也是必要的 ，这样的例子很多。曾经有个开发餐饮分析软件的活就是用非标准化 表把查询时间从平均 40秒降低到了两秒左右。虽然我不得不这么做，但我绝不把数据表的非 标准化当作当然的设计理念。而具体的操作不过是一种派生 。所以如果表出了问题重新产生非标准化的表是完全可能的。 </p>
		<p>Microsoft Visual FoxPro 报表技巧 </p>
		<p>如果你正在使用 Microsoft Visual FoxPro，你可以用对用户友好的字段名来代替编号的名称 ：比如用 Customer Name 代替 txtCNaM。这样，当你用向导程序 [Wizards，台湾人称为'精灵'] 创建表单和报表时，其名字会让那些不是程序员的人更容易阅读。 </p>
		<p>不活跃或者不采用的指示符 </p>
		<p>增加一个字段表示所在记录是否在业务中不再活跃挺有用的 。不管是客户、员工还是其他什么人，这样做都能有助于再运行查询 的时候过滤活跃或者不活跃状态。同时还消除了新用户在采用数据时 所面临的一些问题，比如，某些记录可能不再为他们所用 ，再删除的时候可以起到一定的防范作用。 </p>
		<p>使用角色实体定义属于某类别的列[字段] </p>
		<p>在需要对属于特定类别或者具有特定角色的事物做定义时 ，可以用角色实体来创建特定的时间关联关系，从而可以实现自我文 档化。<br />这里的含义不是让 PERSON 实体带有 Title 字段，而是说，为什么不用 PERSON 实体和 PERSON_TYPE实体来描述人员呢？比方说，当 John Smith, Engineer 提升为 John Smith, Director 乃至最后爬到John Smith, CIO 的高位，而所有你要做的不过是改变两个表 PERSON 和 PERSON_TYPE之间关系的键值，同时增加一个日期/时间字段来知道变化是何时发 生的。这样，你的 PERSON_TYPE 表就包含了所有 PERSON的可能类型，比如 Associate、Engineer、Director 、CIO 或者 CEO 等。<br />还有个替代办法就是改变 PERSON 记录来反映新头衔的变化，不过这样一来在时间上无法跟踪个人所处 位置的具体时间。 </p>
		<p>采用常用实体命名机构数据 </p>
		<p>组织数据的最简单办法就是采用常用名字，比如：PERSON 、ORGANIZATION、ADDRESS 和 PHONE等等。当你把这些常用的一般名字组合起来或者创建特定的相应副实 体时，你就得到了自己用的特殊版本。开始的时候采用一般术语的主 要原因在于所有的具体用户都能对抽象事物具体化。<br />有了这些抽象表示，你就可以在第 2 级标识中采用自己的特殊名称，比如，PERSON 可能是Employee、Spouse、Patient、Client 、Customer、Vendor 或者 Teacher等。同样的，ORGANIZATION 也可能是<br />MyCompany、MyDepartment、Competi tor、Hospital、Warehouse、Governm ent 等。最后ADDRESS 可以具体为 Site、Location、Home、Work、Client 、Vendor、Corporate 和FieldOffice 等。<br />采用一般抽象术语来标识"事物"的类别可以让你在关联数据以满足 业务要求方面获得巨大的灵活性，同时这样做还可以显著降低数据存 储所需的冗余量。 </p>
		<p>用户来自世界各地 </p>
		<p>在设计用到网络或者具有其他国际特性的数据库时，一定要记住大多 数国家都有不同的字段格式，比如邮政编码等，有些国家 ，比如新西兰就没有邮政编码一说。 </p>
		<p>数据重复需要采用分立的数据表 </p>
		<p>如果你发现自己在重复输入数据，请创建新表和新的关系。<br />每个表中都应该添加的 3 个有用的字段<br />* dRecordCreationDate，在 VB 下默认是 Now()，而在 SQL Server 下默认为 GETDATE()<br />* sRecordCreator，在 SQL Server 下默认为 NOT NULL DEFAULT USER<br />* nRecordVersion，记录的版本标记；有助于准确说明 记录中出现 null 数据或者丢失数据的原因<br />对地址和电话采用多个字段<br />描述街道地址就短短一行记录是不够的。Address _Line1、Address_Line2 和 Address_Line3<br />可以提供更大的灵活性。还有，电话号码和邮件地址最好拥有自己的 数据表，其间具有自身的类型和标记类别。</p>
		<p>过分标准化可要小心，这样做可能会导致性能上出现问题 。虽然地址和电话表分离通常可以达到最佳状态，但是如果需要经常 访问这类信息，或许在其父表中存放"首选"信息（比如<br />Customer 等）更为妥当些。非标准化和加速访问之间的妥协是有一定意义的。 </p>
		<p>使用多个名称字段 </p>
		<p>我觉得很吃惊，许多人在数据库里就给 name留一个字段。我觉得只有刚入门的开发人员才会这么做 ，但实际上网上这种做法非常普遍。我建议应该把姓氏和名字当作两 个字段来处理，然后在查询的时候再把他们组合起来。</p>
		<p>我最常用的是在同一表中创建一个计算列[字段]，通过它可以自动 地连接标准化后的字段，这样数据变动的时候它也跟着变。不过 ，这样做在采用建模软件时得很机灵才行。总之，采用连接字段的方 式可以有效的隔离用户应用和开发人员界面。<br />提防大小写混用的对象名和特殊字符过去最令我恼火的事情之一就是数据库里有大小写混用的对象名 ，比如 CustomerData。这一问题从 Access 到 Oracle数据库都存在。我不喜欢采用这种大小写混用的对象命名方法 ，结果还不得不手工修改名字。想想看，这种数据库 /应用程序能混到采用更强大数据库的那一天吗？采用全部大写而且 包含下划符的名字具有更好的可读性（CUSTOMER_DATA ），绝对不要在对象名的字符之间留空格。 </p>
		<p>小心保留词 </p>
		<p>要保证你的字段名没有和保留词、数据库系统或者常用访问方法冲突 ，比如，最近我编写的一个 ODBC 连接程序里有个表，其中就用了 DESC作为说明字段名。后果可想而知！DESC 是 DESCENDING 缩写后的保留词。表里的一个 SELECT *语句倒是能用，但我得到的却是一大堆毫无用处的信息。 </p>
		<p>保持字段名和类型的一致性 </p>
		<p>在命名字段并为其指定数据类型的时候一定要保证一致性 。假如字段在某个表中叫做"agreement_number" ，你就别在另一个表里把名字改成"ref1"。假如数据类型在一 个表里是整数，那在另一个表里可就别变成字符型了。记住 ，你干完自己的活了，其他人还要用你的数据库呢。 </p>
		<p>仔细选择数字类型 </p>
		<p>在 SQL 中使用 smallint 和 tinyint 类型要特别小心，比如，假如你想看看月销售总额，你的总额字段类 型是smallint，那么，如果总额超过了 $32,767 你就不能进行计算操作了。 </p>
		<p>删除标记 </p>
		<p>在表中包含一个"删除标记"字段，这样就可以把行标记为删除 。在关系数据库里不要单独删除某一行；最好采用清除数据程序而且 要仔细维护索引整体性。 </p>
		<p>避免使用触发器 </p>
		<p>触发器的功能通常可以用其他方式实现。在调试程序时触发器可能成 为干扰。假如你确实需要采用触发器，你最好集中对它文档化。<br />包含版本机制建议你在数据库中引入版本控制机制来确定使用中的数据库的版本 。无论如何你都要实现这一要求。时间一长，用户的需求总是会改变 的。最终可能会要求修改数据库结构。虽然你可以通过检查新字段或 者索引来确定数据库结构的版本，但我发现把版本信息直接存放到数 据库中不更为方便吗？ </p>
		<p>给文本字段留足余量 </p>
		<p>ID 类型的文本字段，比如客户 ID或定单号等等都应该设置得比一般想象更大，因为时间不长你多半就 会因为要添加额外的字符而难堪不已。比方说，假设你的客户 ID 为 10位数长。那你应该把数据库表字段的长度设为 12 或者 13 个字符长。这算浪费空间吗？是有一点，但也没你想象的那么多 ：一个字段加长 3个字符在有 1 百万条记录，再加上一点索引的情况下才不过让整个数据库多占据 3MB的空间。但这额外占据的空间却无需将来重构整个数据库就可以实现 数据库规模的增长了。身份证的号码从 15 位变成 18<br />位就是最好和最惨痛的例子。 </p>
		<p>列[字段]命名技巧 </p>
		<p>我们发现，假如你给每个表的列[字段]名都采用统一的前缀 ，那么在编写 SQL表达式的时候会得到大大的简化。这样做也确实有缺点 ，比如破坏了自动表连接工具的作用，后者把公共列[字段 ]名同某些数据库联系起来，不过就连这些工具有时不也连接错误嘛 。举个简单的例子，假设有两个表：<br />Customer 和 Order。Customer 表的前缀是cu_，所以该表内的子段名如下：cu_name_id、cu _surname、cu_initials 和cu_address 等。Order表的前缀是 or_，所以子段名是：<br />or_order_id、or_cust_name_id、or _quantity 和 or_denoscription 等。<br />这样从数据库中选出全部数据的 SQL 语句可以写成如下所示：<br />Select * From Customer, Order Where cu_surname = "MYNAME" ;<br />and cu_name_id = or_cust_name_id and or_quantity = 1<br />在没有这些前缀的情况下则写成这个样子（用别名来区分）：<br />Select * From Customer, Order Where Customer.surname = "MYNAME" ;<br />and Customer.name_id = Order.cust_name_id and Order.quantity = 1<br />第 1 个 SQL 语句没少键入多少字符。但如果查询涉及到 5 个表乃至更多的列[字段]你就知道这个技巧多有用了。 </p>
		<p>第 3 部分 - 选择键和索引 </p>
		<p>
				<br />数据采掘要预先计划 </p>
		<p>我所在的某一客户部门一度要处理 8万多份联系方式，同时填写每个客户的必要数据（这绝对不是小活） 。我从中还要确定出一组客户作为市场目标。当我从最开始设计表和 字段的时候，我试图不在主索引里增加太多的字段以便加快数据库的 运行速度。然后我意识到特定的组查询和信息采掘既不准确速度也不 快。结果只好在主索引中重建而且合并了数据字段。我发现有一个指 示计划相当关键——当我想创建系统类型查找时为什么要采用号码作 为主索引字段呢？我可以用传真号码进行检索，但是它几乎就象系统 类型一样对我来说并不重要。采用后者作为主字段，数据库更新后重 新索引和检索就快多了。</p>
		<p>可操作数据仓库（ODS）和数据仓库（DW）这两种环境下的数据 索引是有差别的。在 DW环境下，你要考虑销售部门是如何组织销售活动的。他们并不是数据 库管理员，但是他们确定表内的键信息。这里设计人员或者数据库工 作人员应该分析数据库结构从而确定出性能和正确输出之间的最佳条 件。 </p>
		<p>使用系统生成的主键 </p>
		<p>这类同技巧 1，但我觉得有必要在这里重复提醒大家。假如你总是在设计数据库 的时候采用系统生成的键作为主键，那么你实际控制了数据库的索引 完整性。这样，数据库和非人工机制就有效地控制了对存储数据中每 一行的访问。<br />采用系统生成键作为主键还有一个优点：当你拥有一致的键结构时 ，找到逻辑缺陷很容易。 </p>
		<p>分解字段用于索引 </p>
		<p>为了分离命名字段和包含字段以支持用户定义的报表 ，请考虑分解其他字段（甚至主键）为其组成要素以便用户可以对其 进行索引。索引将加快 SQL和报表生成器脚本的执行速度。比方说，我通常在必须使用 SQL LIKE 表达式的情况下创建报表，因为 case number 字段无法分解为year、serial number、case type 和 defendant code等要素。性能也会变坏。假如年度和类型字段可以分解为索引字段那 么这些报表运行起来就会快多了。 </p>
		<p>键设计 4 原则 </p>
		<p>* 为关联字段创建外键。<br />* 所有的键都必须唯一。<br />* 避免使用复合键。<br />* 外键总是关联唯一的键字段。 </p>
		<p>别忘了索引 </p>
		<p>索引是从数据库中获取数据的最高效方式之一。95%的数据库性能问题都可以采用索引技术得到解决。作为一条规则 ，我通常对逻辑主键使用唯一的成组索引，对系统键（作为存储过程 ）采用唯一的非成组索引，对任何外键列[字段]采用非成组索引 。不过，索引就象是盐，太多了菜就咸了。你得考虑数据库的空间有 多大，表如何进行访问，还有这些访问是否主要用作读写。</p>
		<p>大多数数据库都索引自动创建的主键字段，但是可别忘了索引外键 ，它们也是经常使用的键，比如运行查询显示主表和所有关联表的某 条记录就用得上。还有，不要索引memo/note 字段，不要索引大型字段（有很多字符），这样作会让索引占用太多 的存储空间。<br />不要索引常用的小型表不要为小型数据表设置任何键，假如它们经常有插入和删除操作就更 别这样作了。对这些插入和删除操作的索引维护可能比扫描表空间消 耗更多的时间。<br />不要把社会保障号码（SSN）或身份证号码（ID）选作键。<br />永远都不要使用 SSN 或 ID 作为数据库的键。除了隐私原因以外，须知政府越来越趋向于不准许 把 SSN 或 ID<br />用作除收入相关以外的其他目的，SSN 或 ID需要手工输入。永远不要使用手工输入的键作为主键 ，因为一旦你输入错误，你唯一能做的就是删除整个记录然后从头开 始。</p>
		<p>我在破解他人的程序时候，我看到很多人把 SSN 或 ID还曾被用做系列号，当然尽管这么做是非法的。而且人们也都知道这 是非法的，但他们已经习惯了。后来，随着盗取身份犯罪案件的增加 ，我现在的同行正痛苦地从一大摊子数据中把 SSN 或 ID 删除。 </p>
		<p>不要用用户的键 </p>
		<p>在确定采用什么字段作为表的键的时候，可一定要小心用户将要编辑 的字段。通常的情况下不要选择用户可编辑的字段作为键 。这样做会迫使你采取以下两个措施：<br />* 在创建记录之后对用户编辑字段的行为施加限制。假如你这么做了 ，你可能会发现你的应用程序在商务需求突然发生变化 ，而用户需要编辑那些不可编辑的字段时缺乏足够的灵活性 。当用户在输入数据之后直到保存记录才发现系统出了问题他们该怎 么想？删除重建？假如记录不可重建是否让用户走开？<br />* 提出一些检测和纠正键冲突的方法。通常，费点精力也就搞定了 ，但是从性能上来看这样做的代价就比较大了。还有 ，键的纠正可能会迫使你突破你的数据和商业/用户界面层之间的隔 离。<br />所以还是重提一句老话：你的设计要适应用户而不是让用户来适应你 的设计。</p>
		<p>不让主键具有可更新性的原因是在关系模式下，主键实现了不同表之 间的关联。比如，Customer 表有一个主键CustomerID，而客户的定单则存放在另一个表里 。Order 表的主键可能是 OrderNo 或者 OrderNo、CustomerID和日期的组合。不管你选择哪种键设置，你都需要在 Order 表中存放 CustomerID 来保证你可以给下定单的用户找到其定单记录。<br />假如你在 Customer 表里修改了 CustomerID，那么你必须找出 Order表中的所有相关记录对其进行修改。否则，有些定单就会不属于任何 客户——数据库的完整性就算完蛋了。<br />如果索引完整性规则施加到表一级，那么在不编写大量代码和附加删 除记录的情况下几乎不可能改变某一条记录的键和数据库内所有关联 的记录。而这一过程往往错误丛生所以应该尽量避免。 </p>
		<p>可选键(候选键)有时可做主键 </p>
		<p>记住，查询数据的不是机器而是人。<br />假如你有可选键，你可能进一步把它用做主键。那样的话 ，你就拥有了建立强大索引的能力。这样可以阻止使用数据库的人不 得不连接数据库从而恰当的过滤数据。在严格控制域表的数据库上 ，这种负载是比较醒目的。如果可选键真正有用，那就是达到了主键 的水准。<br />我的看法是，假如你有可选键，比如国家表内的state_code，你不要在现有不能变动的唯一键上创建后续 的键。你要做的无非是创建毫无价值的数据。如你因为过度使用表的 后续键[别名]建立这种表的关联，操作负载真得需要考虑一下了。 </p>
		<p>别忘了外键 </p>
		<p>大多数数据库索引自动创建的主键字段。但别忘了索引外键字段 ，它们在你想查询主表中的记录及其关联记录时每次都会用到。还有 ，不要索引memo/notes 字段而且不要索引大型文本字段（许多字符），这样做会让你的索引 占据大量的数据库空间。 </p>
		<p>第 4 部分 - 保证数据的完整性 </p>
		<p>用约束而非商务规则强制数据完整性 </p>
		<p>如果你按照商务规则来处理需求，那么你应当检查商务层次 /用户界面：如果商务规则以后发生变化，那么只需要进行更新即可 。假如需求源于维护数据完整性的需要，那么在数据库层面上需要施 加限制条件。如果你在数据层确实采用了约束，你要保证有办法把更 新不能通过约束检查的原因采用用户理解的语言通知用户界面 。除非你的字段命名很冗长，否则字段名本身还不够。</p>
		<p>只要有可能，请采用数据库系统实现数据的完整性。这不但包括通过 标准化实现的完整性而且还包括数据的功能性。在写数据的时候还可 以增加触发器来保证数据的正确性。不要依赖于商务层保证数据完整 性；它不能保证表之间（外键）的完整性所以不能强加于其他完整性 规则之上。 </p>
		<p>分布式数据系统 </p>
		<p>对分布式系统而言，在你决定是否在各个站点复制所有数据还是把数 据保存在一个地方之前应该估计一下未来 5 年或者 10<br />年的数据量。当你把数据传送到其他站点的时候，最好在数据库字段 中设置一些标记。在目的站点收到你的数据之后更新你的标记 。为了进行这种数据传输，请写下你自己的批处理或者调度程序以特 定时间间隔运行而不要让用户在每天的工作后传输数据 。本地拷贝你的维护数据，比如计算常数和利息率等 ，设置版本号保证数据在每个站点都完全一致。 </p>
		<p>强制指示完整性(参照完整性?) </p>
		<p>没有好办法能在有害数据进入数据库之后消除它，所以你应该在它进 入数据库之前将其剔除。激活数据库系统的指示完整性特性 。这样可以保持数据的清洁而能迫使开发人员投入更多的时间处理错误条件。 </p>
		<p>关系 </p>
		<p>如果两个实体之间存在多对一关系，而且还有可能转化为多对多关系 ，那么你最好一开始就设置成多对多关系。从现有的多对一关系转变 为多对多关系比一开始就是多对多关系要难得多。 </p>
		<p>采用视图 </p>
		<p>为了在你的数据库和你的应用程序代码之间提供另一层抽象 ，你可以为你的应用程序建立专门的视图而不必非要应用程序直接访 问数据表。这样做还等于在处理数据库变更时给你提供了更多的自由 。 </p>
		<p>给数据保有和恢复制定计划 </p>
		<p>考虑数据保有策略并包含在设计过程中，预先设计你的数据恢复过程 。采用可以发布给用户/开发人员的数据字典实现方便的数据识别同 时保证对数据源文档化。编写在线更新来"更新查询 "供以后万一数据丢失可以重新处理更新。<br />用存储过程让系统做重活解决了许多麻烦来产生一个具有高度完整性的数据库解决方案之后 ，我决定封装一些关联表的功能组，提供一整套常规的存储过程来访 问各组以便加快速度和简化客户程序代码的开发。数据库不只是一个 存放数据的地方，它也是简化编码之地。 </p>
		<p>使用查找 </p>
		<p>控制数据完整性的最佳方式就是限制用户的选择。只要有可能都应该 提供给用户一个清晰的价值列表供其选择。这样将减少键入代码的错 误和误解同时提供数据的一致性。某些公共数据特别适合查找 ：国家代码、状态代码等。 </p>
		<p>
				<br />第 5 部分 - 各种小技巧 </p>
		<p>文档、文档、文档 </p>
		<p>对所有的快捷方式、命名规范、限制和函数都要编制文档。</p>
		<p>采用给表、列[字段]、触发器等加注释的数据库工具。是的 ，这有点费事，但从长远来看，这样做对开发、支持和跟踪修改非常 有用。</p>
		<p>取决于你使用的数据库系统，可能有一些软件会给你一些供你很快上 手的文档。你可能希望先开始在说，然后获得越来越多的细节 。或者你可能希望周期性的预排，在输入新数据同时随着你的进展对 每一部分细节化。不管你选择哪种方式，总要对你的数据库文档化 ，或者在数据库自身的内部或者单独建立文档。这样 ，当你过了一年多时间后再回过头来做第2 个版本，你犯错的机会将大大减少。 </p>
		<p>使用常用英语（或者其他任何语言）而不要使用编码 </p>
		<p>为什么我们经常采用编码（比如 9935A 可能是'青岛啤酒'的供应代码，4XF788-Q可能是帐目编码）？理由很多。但是用户通常都用英语进行思考而不 是编码。工作 5 年的会计或许知道 4XF788-Q是什么东西，但新来的可就不一定了。在创建下拉菜单、列表 、报表时最好按照英语名排序。假如你需要编码，那你可以在编码旁 附上用户知道的英语。 </p>
		<p>保存常用信息 </p>
		<p>让一个表专门存放一般数据库信息非常有用。我常在这个表里存放数 据库当前版本、最近检查/修复（对FoxPro）、关联设计文档的名称、客户等信息 。这样可以实现一种简单机制跟踪数据库，当客户抱怨他们的数据库 没有达到希望的要求而与你联系时，这样做对非客户机 /服务器环境特别有用。 </p>
		<p>测试、测试、反复测试 </p>
		<p>建立或者修订数据库之后，必须用用户新输入的数据测试数据字段 。最重要的是，让用户进行测试并且同用户一道保证你选择的数据类 型满足商业要求。测试需要在把新数据库投入实际服务之前完成。 </p>
		<p>检查设计 </p>
		<p>在开发期间检查数据库设计的常用技术是通过其所支持的应用程序原 型检查数据库。换句话说，针对每一种最终表达数据的原型应用 ，保证你检查了数据模型并且查看如何取出数据。 </p>
		<p>Microsoft Visual FoxPro 设计技巧 </p>
		<p>对复杂的 Microsoft Visual FoxPro数据库应用程序而言，可以把所有的主表放在一个数据库容器文件里 ，然后增加其他数据库表文件和装载同原有数据库有关的特殊文件 。根据需要用这些文件连接到主文件中的主表。比如数据输入 、数据索引、统计分析、向管理层或者政府部门提供报表以及各类只 读查询等。这一措施简化了用户和组权限的分配，而且有利于应用程 序函数（存储过程）的分组和划分，从而在程序必须修改的时候易于 管理。 </p>
<img src ="http://www.blogjava.net/weibogao/aggbug/93125.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weibogao/" target="_blank">weibogao</a> 2007-01-11 12:09 <a href="http://www.blogjava.net/weibogao/archive/2007/01/11/93125.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转《“数据建模”读书笔记 》</title><link>http://www.blogjava.net/weibogao/archive/2007/01/11/93122.html</link><dc:creator>weibogao</dc:creator><author>weibogao</author><pubDate>Thu, 11 Jan 2007 04:01:00 GMT</pubDate><guid>http://www.blogjava.net/weibogao/archive/2007/01/11/93122.html</guid><wfw:comment>http://www.blogjava.net/weibogao/comments/93122.html</wfw:comment><comments>http://www.blogjava.net/weibogao/archive/2007/01/11/93122.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weibogao/comments/commentRss/93122.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weibogao/services/trackbacks/93122.html</trackback:ping><description><![CDATA[
		<h3 class="title"> </h3>
		<p>         最近逛书店发现一本数据建模的好书——《数据建模：分析与设计的工具和技巧》(Data Modeler's Workbench：Tools and Techniques for Analysis and Design)，作者Steve Hoberman。粗读完一遍后，感觉这本书的确无愧于译者和国外专家们的盛赞：“这本书充满了对改进数据模型和设计有益的技术和技巧，并且它还极富阅读乐趣——一个了不起的结合！任何一个数据建模者都应该拥有一本Steve Hoberman的关于数据建模工具和技术的书。”</p>
		<p>　　尽管我对自己所掌握的数据建模知识有一定的自负，读完该书后，还是获益良多。本着好书大家一起分享的想法，我把该书的每个章节的总结和技巧建议列出来，以方便手头暂时没有该书的朋友在数据建模时参考。该书所介绍的工具和模版可在作者的Web站点下载，地址是：<br />　　www.wiley.com/compbooks/hoberman</p>
		<p>　　第一章：使用趣闻、类比和演示文稿来阐明数据建模的概念</p>
		<p>　　在一般的日常沟通中。我们可能会说出并听到许多故事、或者趣闻这些故事涉及的论题范围很大。有些例子是周末发生在我们自己身边的事情，或者是与我们的工作项目有关的经历。这些趣闻有助于加强我们和周围人们的关系，增进我们的愉悦情绪，而且对我们有教育作用。我们能够把由语言表达出来的东西形象化。有时，当故事结束时，给我们留下的是以前未曾想到的信息或更多的认识。在解释数据建模概念时，趣闻是极其有效的。原因有如下几个：<br />　　它们建立起持久的形象。<br />　　它们引人入胜、使人愉悦。<br />　　它们增经人们之间的关系。<br />　　它们减缓压力。</p>
		<p>　　成功编造并讲述一个数据建模方面的趣闻有下面三个简单的步骤：<br />　　1)定义一个论题。要在心中保证，你讲述的这个趣闻有一个特定的目标或论题，也就是说，这个故事是为了解释一个数据建模的概念或术语。<br />　　2)选择你的故事。我们可以选择的故事类型多种多样。我们要考虑选择一个有趣并有益，而且能够明白无误地传达主题意图的简短的故事。<br />　　3)演练你的故事。一旦找到了合适的故事，你要好好演练一番，直到你自信它能够在两分钟的时间内充分表达你的论题。要避免讲述拖拖拉拉的故事。</p>
		<p>　　数据模型类比<br />　　类比就是把两个或多个概念进行相互比较，以强调它们之间的相似或差异。类比是介绍外来事物或新鲜事物的一个很好的技巧，尤其是向非计算机专业的人士介绍计算机的专业知识时。Hoberman在数据建模中最常见的几个类比如下（他用这些类比轻松的打动管理层给他涨了一倍的工资^_^）：<br />　　主体域模型是一个居高临下的视点。<br />　　数据模型是一个设计图。<br />　　企业模型是一个世界地图。<br />　　标准就是城市规划。<br />　　元数据仓储库是一个图书馆。<br />　　数据仓库是“心脏”。</p>
		<p>　　第二章：元数据宾果游戏<br />　　简单来说，即通过宾果卡片游戏的方式，调动项目团队成员的积极性，来确定数据模型，并确定元数据的有效性。元数据宾果游戏强调“共赢”，如果运气好，游戏结束时每个人都能赢。</p>
		<p>　　第三章：确保高质量的定义<br />　　本章集中讨论一个被称为“定义检查单”(Definition Checklist)的工具，它包含了确保定义的质量处于最高水平的准则。</p>
		<p>　　第四章：数据建模者的项目计划<br />　　本章重点介绍确定数据建模阶段、任务、工具和时限的四个工具：<br />　　·数据建模阶段的工具：用来确定最高层次上的数据建模步骤。<br />　　·阶段—任务—工具：提取出“数据建模阶段”的各个阶段并把他们分解成数据建模任务。<br />　　·优先级三角形：你可以从以下三项中取两项极值：很高的质量、最短的时间与最低的成本，但你永远也别想三者兼得。<br />　　·可靠的估算工具：“主体域工作量时限”根据应用程序的类型，确定每个数据建模阶段应占整个项目的百分比。“任务工作量工具”提取在“阶段—任务—工具”中确定的每项任务，并列出它们应占整个数据建模工作产品的百分比。这两个工具的组合可使你向项目经理提供一份具有一定精确度的合理估算。</p>
		<p>　　第五章：主体域分析<br />　　本章主要探讨五个关键的工具，这五个工具对数据建模工作的主体域分析阶段有帮组作用。它们应该按照下面的顺序被逐个完成：<br />　　1)主体域检查单：新应用程序中的主体域的完整列表，还有各个主体域的定义和同义词（或别名）。<br />　　2)主体域CRUD(Create Read Update Delete)矩阵：包含新应用程序和现有应用程序之间的主体域方面的差别和重复之处，确定应用程序的范围。<br />　　3)In-the-Know模版：确定完成这个新应用程序的数据间模工作产品所需要的、被用作资源的人员和文档。<br />　　4)主体域家族树：包含每一个主体域的源应用程序和若干其他的关键信息，阐明主体域数据将来自哪里。<br />　　5)主体域力度矩阵：使用一个电子表格的格式，记录每一个度量和事实主体域的发布层次。</p>
		<p>　　第六章：主体域建模<br />　　本章阐述三个队主体域信息进行建模的强大工具：<br />　　·“业务清理板”模型。<br />　　·“应用程序清理板”模型。<br />　　·“早期现实性检查”模型。</p>
		<p>　　第七章：逻辑数据分析<br />　　本章关注四个逻辑数据分析工具，它们应该按照下面的次序被使用：<br />　　1)数据元素家族树：包含应用程序的数据元素的完整列表，以及每个数据元素的来源和变换信息，还有其他几个关键的数据元素元数据。<br />　　2)数据元素粒度矩阵：用一个电子表格的格式，来记录每个度量和事实的发布层次。<br />　　3)数据质量记录模板：展示每个数据元素的员数据和一些实际数据的对比。<br />　　4)数据质量确认模板：记录每个数据元素的元数据和一些实际数据的对比的结果。</p>
		<p>　　第八章：规范化之旅和反向规范化生存指南（强烈推荐：是我目前所读过最好的关系型数据库的规范化技术文档）<br />　　规范化是一个剔除冗余并应用规则的过程，它的目的是为了更好的理解和表达存在于数据元素之间的依赖性和参与性。规范化包含6个层次，最高层是第五范式(5NF)。一般的技术文档上都认为达到3NF即可，Steve Hoberman给我们指明了更高的目标：5NF。Graeme Simsion写过一本名为《Data Modeling Essentials》的书，在这本书中，他写道：“较高层次的范式常被从业者误解并因此而被忽视，或为了支持不可靠的建模时间而被引用。”但是，我们需要理解这些较高层次的规范化，因为它们体现了额外的规范化机会，并帮组我们进一步减少冗余信息、改进设计的灵活性。尽管余下的三个规范化层次有可能仅仅产生次数很少的变化，但它们仍然具有一些提高灵活性和效率的机会。下面是BCNF&amp;4NF&amp;5NF的定义(比国内教材上罗列的数学公式容易理解得多^_^)：<br />　　BCNF=3NF＋下面的规则：<br />　　每一个数据元素都完全依赖于键、整个键，而且除依赖于这个键以外，不依赖于任何其他数据元素。<br />　　4NF=3NF+下面的规则：<br />　　要把主键中拥有三个或更多外建数据元素、切割格外键之间不存在约束的那些实体分解成两个或更多个实体。<br />　　5NF=4NF+下面的规则：<br />　　把主键中拥有三个或更多的外键数据元素，且这些外键数据元素之间存在着约束的实体分解成为所有的约束都需要的多对多的关系。</p>
		<p>　　当我们攀上5NF的顶峰后，再根据实际需求情况来进行“反向规范化”增加数据冗余，从而简化开发，提高查询速度。反向规范化是这样一个过程：在定义了一个可靠的、完全规范化了的数据结构之后，你借助这个过程，有选择地引入一些重复的数据，以促进特殊性能需求的实现。Steve Hoberman的“反向规范化生存指南”给如何适当增加冗余提供了一套可计算的评分标准。通过考察每个关系的6个问题，累加各个问题的得分之后，当得分大于等于10时，我们将对该关系进行反向规范化。</p>
		<p>　　“反向规范化生存指南”的计分规则：<br />　　1.关系是什么类型的：该问题确定我们所分析的关系的类型。父实体对于子实体具有什么样的关系？<br />　　层次关系(20分)<br />　　同等关系(-10分)<br />　　确定关系(-20分)<br />　　2.参与率是多少：该问题确定一个关系中的每个实体的参与性。换句话说，对于一个给定的父实体数值，大概会有几个子实体数值？父与子的关系越接近“一对一”，我们对它进行反向规范化的机会就越大。<br />　　多达“一对五”的比率(20分)<br />　　多达“一对一百”的比率(-10分)<br />　　超过“一对一百”的比率(-20分)<br />　　3.父实体中有多少个数据元素<br />　　少于10个数据元素(20分)<br />　　数据元素的数量介于10到20之间(-10分)<br />　　多于20个数据元素(-20分)<br />　　4.使用率是多少：当用户需要来自子的信息时，通常情况下，它们是否还需要来自父的信息呢？换句话说，这两个实体的耦合或相关程度如何？<br />　　相互之间的关联很强(30分)<br />　　相互之间的关联较弱或者没有关联(-30分)<br />　　5.父实体时一个占位符吗：在不远的将来，我们是否还打算向父实体加入更多的数据元素或关系？如果答案是“不”，那么进行反向规范化的可行性就更强。<br />　　是(20分)<br />　　不(-20分)<br />　　6.变动对比率是多少：该问题是为了确定，在同一时间周期内，两个实体的插入和更新的频度是否相近。如果其中一个实体很少变化，而另一个实体却变动频繁，那么，我们就非常倾向于保持它们的规范化状态，把它们放在各自的表中。<br />　　相同(20分)<br />　　不同(-20分)</p>
		<p>　　“反向规范化生存指南”的使用方法：<br />　　1)把模型中的关系按照优先级排序<br />　　2)选择一个关系<br />　　3)对这个关系回答提问<br />　　4)如果得分等于或大于10，就进行反向规范化<br />　　5)返回步骤二，直到完成所有的关系。</p>
		<p>　　第九章：抽象化安全指南和组件<br />　　看过我的“浅谈数据库设计技巧(上) ”的朋友应该还记得我举的第二个例子：网上电子商务平台上的商品信息表的设计。本章将我在上面例子中所用的方法上升到了理论阶段，采用了面向对象的设计，将所有商品的共有属性提取出来，抽象成一个超类，再加入一个表来记录各个不同实体之间的细节来实现超类的派生，从而实现设计的灵活性。当出现下面两种条件的任何场合，抽象化都是极其有用的：<br />　　设计需要永久维持下去：要求以后尽可能的不修改数据库设计<br />　　需求可能发生变化：应用程序的需求发生变化，而要求业务流程重组或进行功能升级<br />　　数据仓库：当新的分类类型从源应用程序中传过来时，我们无须对数据仓库的设计进行任何改动，而只需在分类类型实体加入一个新行即可<br />　　元数据仓储库：和数据仓库的要求类似</p>
		<p>　　当然，抽象化会大大增加工作量和开发的复杂度，而人们通常关注的是非常短期的应用和眼前的成本，而不关心将来的高得多的成本。所以，我非常赞同敏捷软件开发这个观点：在最初几乎不进行预先设计，但是一旦需求发生变化，此时作为一名追求卓越的程序员，应该从头审查整个架构设计，在此次修改中设计出能够满足日后类似修改的系统架构。</p>
		<p>　　“抽象组件”就是小型的抽象模型片段，在许多的建模场合(无论是什么行业、组织，甚至什么主体域的建模场合)中，它们都可被反复使用。在键模阶段多次使用抽象化之后，你将开始看到出现的抽象化结构的趋势。这些“抽象组件”有如下的目的：<br />　　加快设计速度<br />　　加快开发速度<br />　　提供通用且有用的机构</p>
		<p>　　第十章：数据模型美化技巧<br />　　本章通过关注如何改进逻辑和物理数据模型的视觉外观，使我们的设计超越直接的应用程序需求。本章中讨论了五个类别的美化技巧：<br />　　逻辑数据元素排列技巧：这些技巧是一个推荐的、对你的逻辑数据模型中的每一个实体的数据元素进行排序的方法。<br />　　物理数据元素排序技巧：这些技巧关注数据模型中每一个实体的最佳布局。<br />　　实体布局技巧：这些技巧关注数据模型中的每一个实体的最佳布局<br />　　关系布局技巧：这些技巧关注如何调整重叠的关系线条以及看起来穿越（而不是绕过）无关实体的关系<br />　　吸引注意力的技巧：这些技巧关注如何在我们的涉及中突出的某些元素、实体或关系。</p>
		<p>　　第十一章：规划一个长盛不衰的数据建模生涯<br />　　对数据建模者的十大忠告清单：<br />　　1)记住：灵活性、准确性和背景<br />　　2)建模只是你的工作的一小部分<br />　　3)尝试其他角色<br />　　4)了解95/5规则：95%的时间将花费在5%的数据元素上<br />　　5)数据建模从不令人厌烦：如果你一直在做数据建模工作，而且发现自己经常感到厌烦，那么，你的确该改变一下了。这可能不是数据建模领域本身令人厌烦，而是你所在的特定的任务、公司或行业不再令人兴奋。冒险一下，尝试着道一个不同的项目或行业中进行数据建模工作吧！<br />　　6)站在技术前沿<br />　　7)尽量不要在模型上牵扯感情因素：建模者必须理解，人们在评审过程中的意见并不是针对模型的创建者，而是针对这个模型的内容。即那句老话：对事不对人。<br />　　8)让你的创造力展开翅膀：在考虑记录数据需求和改进设计的新方法时，要紧可能有创造性。有创造性也许就意味着修改本书中的某些工具。这还可能意味着提出你自己的电子表格或其他工具。<br />　　9)单纯的理论太昂贵了：在设计活动过程中，你要确保把这个观点牢记在心。为这个应用程序掏腰包的部门和组织期望看到的是能看得着的实用结果。<br />　　10)成为一个了不起的会讲故事的人：作为一名数据建模者，讲故事是工作的一个很重要的部分。为了帮组教化和影响项目经理以及对我们行业缺乏理解的其他人，我们需要讲故事或趣闻。</p>
		<p>　　最后，我个人觉得，Steve Hoberman所提出的“抽象组件”的观念和面向对象设计中的的“设计模式”非常类似。即数据库专家在多次的数据建模后，将各个项目中的类似部分抽象化，提取出特定的建模模型片段，以后只需在新的项目中对这些模型片段细化派生，即可快速构建出适合于该项目的数据库架构。不过，这些建模模型片段并没有统一，形成标准，目前也没有出版这类的书籍。本人正在陆续总结自己在这方面的经验，但是自知水平有限，不敢在高人面前班门弄斧，只希望自己日后陆续发布的相关文章能起到抛转引玉的作用，争取由中国的程序员率先统一出数据建模领域的“设计模式”。</p>
<img src ="http://www.blogjava.net/weibogao/aggbug/93122.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weibogao/" target="_blank">weibogao</a> 2007-01-11 12:01 <a href="http://www.blogjava.net/weibogao/archive/2007/01/11/93122.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转《浅谈数据库设计技巧》</title><link>http://www.blogjava.net/weibogao/archive/2007/01/11/93121.html</link><dc:creator>weibogao</dc:creator><author>weibogao</author><pubDate>Thu, 11 Jan 2007 04:00:00 GMT</pubDate><guid>http://www.blogjava.net/weibogao/archive/2007/01/11/93121.html</guid><wfw:comment>http://www.blogjava.net/weibogao/comments/93121.html</wfw:comment><comments>http://www.blogjava.net/weibogao/archive/2007/01/11/93121.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weibogao/comments/commentRss/93121.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weibogao/services/trackbacks/93121.html</trackback:ping><description><![CDATA[
		<h3 class="title"> </h3>
		<p>　　说到数据库，我认为不能不先谈数据结构。1996年，在我初入大学学习计算机编程时，当时的老师就告诉我们说：计算机程序＝数据结构＋算法。尽管现在的程序开发已由面向过程为主逐步过渡到面向对象为主，但我还是深深赞同8年前老师的告诉我们的公式：计算机程序＝数据结构＋算法。面向对象的程序开发，要做的第一件事就是，先分析整个程序中需处理的数据，从中提取出抽象模板，以这个抽象模板设计类，再在其中逐步添加处理其数据的函数(即算法)，最后，再给类中的数据成员和函数划分访问权限，从而实现封装。</p>
		<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)</p>
		<p>
				<br />　　三、多用户及其权限管理的设计<br />　　开发数据库管理类的软件，不可能不考虑多用户和用户权限设置的问题。尽管目前市面上的大、中型的后台数据库系统软件都提供了多用户，以及细至某个数据库内某张表的权限设置的功能，我个人建议：一套成熟的数据库管理软件，还是应该自行设计用户管理这块功能，原因有二：<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 </p>
		<p>　　想象一下，如果不用这个冗余表，每次统计每人每月的餐费总帐时会多麻烦，程序效率也够呛。那么，到底什么时候可以增加一定的冗余数据呢？我认为有2个原则：</p>
		<p>　　１、用户的整体需求。当用户更多的关注于，对数据库的规范记录按一定的算法进行处理后，再列出的数据。如果该算法可以直接利用后台数据库系统的内嵌函数来完成，此时可以适当的增加冗余字段，甚至冗余表来保存这些经过算法处理后的数据。要知道，对于大批量数据的查询，修改或删除，后台数据库系统的效率远远高于我们自己编写的代码。<br />　　２、简化开发的复杂度。现代软件开发，实现同样的功能，方法有很多。尽管不必要求程序员精通绝大部分的开发工具和平台，但是还是需要了解哪种方法搭配哪种开发工具的程序更简洁，效率更高一些。冗余数据的本质就是用空间换时间，尤其是目前硬件的发展远远高于软件，所以适当的冗余是可以接受的。不过我还是在最后再强调一下：不要过多的依赖平台和开发工具的特性来简化开发，这个度要是没把握好的话，后期维护升级会栽大跟头的。</p>
<img src ="http://www.blogjava.net/weibogao/aggbug/93121.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weibogao/" target="_blank">weibogao</a> 2007-01-11 12:00 <a href="http://www.blogjava.net/weibogao/archive/2007/01/11/93121.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>