package com.infosys.lucene.code JavaSourceCodeAnalyzer.;
import java.io.Reader;
import java.util.Set;
import org.apache.lucene.analysis.*;
public class JavaSourceCodeAnalyzer extends Analyzer {
??????private Set javaStopSet;
??????private Set englishStopSet;
??????private static final String[] JAVA_STOP_WORDS = {
???????? "public","private","protected","interface",
????????????"abstract","implements","extends","null""new",
?? ????????"switch","case", "default" ,"synchronized" ,
????????????"do", "if", "else", "break","continue","this",
?? ????????"assert" ,"for","instanceof", "transient",
????????????"final", "static" ,"void","catch","try",
????????????"throws","throw","class", "finally","return",
????????????"const" , "native", "super","while", "import",
????????????"package" ,"true", "false" };
???? private static final String[] ENGLISH_STOP_WORDS ={
????????????"a", "an", "and", "are","as","at","be" "but",
????????????"by", "for", "if", "in", "into", "is", "it",
????????????"no", "not", "of", "on", "or", "s", "such",
????????????"that", "the", "their", "then", "there","these",
????????????"they", "this", "to", "was", "will", "with" };
???? public SourceCodeAnalyzer(){
????????????super();
????????????javaStopSet = StopFilter.makeStopSet(JAVA_STOP_WORDS);
????????????englishStopSet = StopFilter.makeStopSet(ENGLISH_STOP_WORDS);
???? }
???? public TokenStream tokenStream(String fieldName, Reader reader) {
????????????if (fieldName.equals("comment"))
???????????????????? return?? new PorterStemFilter(new StopFilter(
????????????????????????new LowerCaseTokenizer(reader),englishStopSet));
????????????else
???????????????????? return?? new StopFilter(
?????????????????? new LowerCaseTokenizer(reader),javaStopSet);
???? }
}
import org.apache.lucene.document.*;
import org.apache.lucene.index.*;
import com.infosys.lucene.code.JavaParser.*;
public class JavaSourceCodeIndexer {
????private static JavaParser parser = new JavaParser();
????????private static final String IMPLEMENTS = "implements";
????????private static final String IMPORT = "import";
????????...
????????public static void main(String[] args) {
????????????????File indexDir = new File("C:\\Lucene\\Java");
????????????????File dataDir = new File("C:\\JavaSourceCode ");
????????????????IndexWriter writer = new IndexWriter(indexDir,
????????????????????new JavaSourceCodeAnalyzer(), true);
????????????????indexDirectory(writer, dataDir);
????????????????writer.close();
????????}
????????public static void indexDirectory(IndexWriter writer, File dir){
????????????File[] files = dir.listFiles();
????????????for (int i = 0; i < files.length; i++) {
????????????????????File f = files[i];
????????????????// Create a Lucene Document
????????????????Document doc = new Document();
????????????????//??Use JavaParser to parse file
????????????????parser.setSource(f);
????????????????addImportDeclarations(doc, parser);
?????? ???????? ????????addComments(doc, parser);
???????? ????????// Extract Class elements Using Parser
????????????????JClass cls = parser.getDeclaredClass();
????????????????addClass(doc, cls);
???????? ????????// Add field to the Lucene Document
?????? ????????????????doc.add(Field.UnIndexed(FILENAME, f.getName()));
????????????????writer.addDocument(doc);
?? ???????? ????????}
????????}
????????private static void addClass(Document doc, JClass cls) {
?? ????????????????//For each class add Class Name field
????????????doc.add(Field.Text(CLASS, cls.className));
????????????String superCls = cls.superClass;
????????????if (superCls != null)
?? ????????????????//Add the class it extends as extends field
????????doc.add(Field.Text(EXTENDS, superCls));
????????????// Add interfaces it implements
????????????ArrayList interfaces = cls.interfaces;
????????????for (int i = 0; i < interfaces.size(); i++)
????????????????doc.add(Field.Text(IMPLEMENTS, (String) interfaces.get(i)));
?? ???????? ????????//Add details??on methods declared
????????????addMethods(cls, doc);
????????????ArrayList innerCls = cls.innerClasses;
?? ????????????????for (int i = 0; i < innerCls.size(); i++)
????????????????addClass(doc, (JClass) innerCls.get(i));
????????}
????????private static void addMethods(JClass cls, Document doc) {
????????????ArrayList methods = cls.methodDeclarations;
????????????for (int i = 0; i < methods.size(); i++) {
?????? ????????????????JMethod method = (JMethod) methods.get(i);
????????????????// Add method name field
????????????????doc.add(Field.Text(METHOD, method.methodName));
????????????????// Add return type field
????????????????doc.add(Field.Text(RETURN, method.returnType));
????????????????ArrayList params = method.parameters;
????????????????for (int k = 0; k < params.size(); k++)
????????????????// For each method add parameter types
????????????????????doc.add(Field.Text(PARAMETER, (String)params.get(k)));
????????????????String code = method.codeBlock;
????????????????if (code != null)
????????????????//add the method code block
????????????????????doc.add(Field.UnStored(CODE, code));
????????????}
????????}
????????private static void addImportDeclarations(Document doc, JavaParser parser) {
?? ????????????????ArrayList imports = parser.getImportDeclarations();
????????????if (imports == null)???? return;
????????????for (int i = 0; i < imports.size(); i++)
????????????????????//add import declarations as keyword
????????????????doc.add(Field.Keyword(IMPORT, (String) imports.get(i)));
????????}
}
public class JavaCodeSearch {
public static void main(String[] args) throws Exception{
????File indexDir = new File(args[0]);
????String q =??args[1]; //parameter:JGraph code:insert
????Directory fsDir = FSDirectory.getDirectory(indexDir,false);
????IndexSearcher is = new IndexSearcher(fsDir);
????PerFieldAnalyzerWrapper analyzer = new
????????PerFieldAnalyzerWrapper( new
????????????????JavaSourceCodeAnalyzer());
????analyzer.addAnalyzer("import", new KeywordAnalyzer());
????Query query = QueryParser.parse(q, "code", analyzer);
????long start = System.currentTimeMillis();
????Hits hits = is.search(query);
????long end = System.currentTimeMillis();
????System.err.println("Found " + hits.length() +
????????????????" docs in " + (end-start) + " millisec");
????for(int i = 0; i < hits.length(); i++){
????Document doc = hits.doc(i);
????????System.out.println(doc.get("filename")
????????????????+ " with a score of " + hits.score(i));
????}
????is.close();
}
}
|
作者:未知 來源:未知 加入時間:2004-11-1 天新軟件園
|
在平時工作中,難免會遇到把 XML 作為數據存儲格式。面對目前種類繁多的解決方案,哪個最適合我們呢?在這篇文章中,我對這四種主流方案做一個不完全評測,僅僅針對遍歷 XML 這塊來測試,因為遍歷 XML 是工作中使用最多的(至少我認為)。 預 備 測試環境: AMD 毒龍1.4G OC 1.5G、256M DDR333、Windows2000 Server SP4、Sun JDK 1.4.1+Eclipse 2.1+Resin 2.1.8,在 Debug 模式下測試。 XML 文件格式如下: <?xml version="1.0" encoding="GB2312"?> <RESULT> <VALUE> <NO>A1234</NO> <ADDR>四川省XX縣XX鎮XX路X段XX號</ADDR> </VALUE> <VALUE> <NO>B1234</NO> <ADDR>四川省XX市XX鄉XX村XX組</ADDR> </VALUE> </RESULT> 測試方法: 采用 JSP 端調用Bean(至于為什么采用JSP來調用,請參考:http://blog.csdn.net/rosen/archive/2004/10/15/138324.aspx),讓每一種方案分別解析10K、100K、1000K、10000K的 XML 文件,計算其消耗時間(單位:毫秒)。 JSP 文件: <%@ page contentType="text/html; charset=gb2312" %> <%@ page import="com.test.*"%> <html> <body> <% String args[]={""}; MyXMLReader.main(args); %> </body> </html> 測 試 首先出場的是 DOM(JAXP Crimson 解析器) DOM 是用與平臺和語言無關的方式表示 XML 文檔的官方 W3C 標準。DOM 是以層次結構組織的節點或信息片斷的集合。這個層次結構允許開發人員在樹中尋找特定信息。分析該結構通常需要加載整個文檔和構造層次結構,然后才能做任何工作。由于它是基于信息層次的,因而 DOM 被認為是基于樹或基于對象的。DOM 以及廣義的基于樹的處理具有幾個優點。首先,由于樹在內存中是持久的,因此可以修改它以便應用程序能對數據和結構作出更改。它還可以在任何時候在樹中上下導航,而不是像 SAX 那樣是一次性的處理。DOM 使用起來也要簡單得多。 另一方面,對于特別大的文檔,解析和加載整個文檔可能很慢且很耗資源,因此使用其他手段來處理這樣的數據會更好。這些基于事件的模型,比如 SAX。 Bean文件: package com.test; import java.io.*; import java.util.*; import org.w3c.dom.*; import javax.xml.parsers.*; public class MyXMLReader{ public static void main(String arge[]){ long lasting =System.currentTimeMillis(); try{ File f=new File("data_10k.xml"); DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance(); DocumentBuilder builder=factory.newDocumentBuilder(); Document doc = builder.parse(f); NodeList nl = doc.getElementsByTagName("VALUE"); for (int i=0;i<nl.getLength();i++){ System.out.print("車牌號碼:" + doc.getElementsByTagName("NO").item(i).getFirstChild().getNodeValue()); System.out.println(" 車主地址:" + doc.getElementsByTagName("ADDR").item(i).getFirstChild().getNodeValue()); } }catch(Exception e){ e.printStackTrace(); } System.out.println("運行時間:"+(System.currentTimeMillis() - lasting)+" 毫秒"); } } 10k消耗時間:265 203 219 172 100k消耗時間:9172 9016 8891 9000 1000k消耗時間:691719 675407 708375 739656 10000k消耗時間:OutOfMemoryError 接著是 SAX 這種處理的優點非常類似于流媒體的優點。分析能夠立即開始,而不是等待所有的數據被處理。而且,由于應用程序只是在讀取數據時檢查數據,因此不需要將數據存儲在內存中。這對于大型文檔來說是個巨大的優點。事實上,應用程序甚至不必解析整個文檔;它可以在某個條件得到滿足時停止解析。一般來說,SAX 還比它的替代者 DOM 快許多。 選擇 DOM 還是選擇 SAX ? 對于需要自己編寫代碼來處理 XML 文檔的開發人員來說,?? ??? 選擇 DOM 還是 SAX 解析模型是一個非常重要的設計決策。 DOM 采用建立樹形結構的方式訪問 XML 文檔,而 SAX 采用的事件模型。 DOM 解析器把 XML 文檔轉化為一個包含其內容的樹,并可以對樹進行遍歷。用 DOM 解析模型的優點是編程容易,開發人員只需要調用建樹的指令,然后利用navigation APIs訪問所需的樹節點來完成任務。可以很容易的添加和修改樹中的元素。然而由于使用 DOM 解析器的時候需要處理整個 XML 文檔,所以對性能和內存的要求比較高,尤其是遇到很大的 XML 文件的時候。由于它的遍歷能力,DOM 解析器常用于 XML 文檔需要頻繁的改變的服務中。 SAX 解析器采用了基于事件的模型,它在解析 XML 文檔的時候可以觸發一系列的事件,當發現給定的tag的時候,它可以激活一個回調方法,告訴該方法制定的標簽已經找到。SAX 對內存的要求通常會比較低,因為它讓開發人員自己來決定所要處理的tag。特別是當開發人員只需要處理文檔中所包含的部分數據時,SAX 這種擴展能力得到了更好的體現。但用 SAX 解析器的時候編碼工作會比較困難,而且很難同時訪問同一個文檔中的多處不同數據。 Bean文件: package com.test; import org.xml.sax.*; import org.xml.sax.helpers.*; import javax.xml.parsers.*; public class MyXMLReader extends DefaultHandler { java.util.Stack tags = new java.util.Stack(); public MyXMLReader() { super(); } public static void main(String args[]) { long lasting = System.currentTimeMillis(); try { SAXParserFactory sf = SAXParserFactory.newInstance(); SAXParser sp = sf.newSAXParser(); MyXMLReader reader = new MyXMLReader(); sp.parse(new InputSource("data_10k.xml"), reader); } catch (Exception e) { e.printStackTrace(); } System.out.println("運行時間:" + (System.currentTimeMillis() - lasting) + " 毫秒"); } public void characters(char ch[], int start, int length) throws SAXException { String tag = (String) tags.peek(); if (tag.equals("NO")) { System.out.print("車牌號碼:" + new String(ch, start, length)); } if (tag.equals("ADDR")) { System.out.println(" 地址:" + new String(ch, start, length)); } } public void startElement( String uri, String localName, String qName, Attributes attrs) { tags.push(qName); } } 10k消耗時間:110 47 109 78 100k消耗時間:344 406 375 422 1000k消耗時間:3234 3281 3688 3312 10000k消耗時間:32578 34313 31797 31890 30328 然后是 JDOM http://www.jdom.org/ JDOM 的目的是成為 Java 特定文檔模型,它簡化與 XML 的交互并且比使用 DOM 實現更快。由于是第一個 Java 特定模型,JDOM 一直得到大力推廣和促進。正在考慮通過“Java 規范請求 JSR-102”將它最終用作“Java 標準擴展”。從 2000 年初就已經開始了 JDOM 開發。 JDOM 與 DOM 主要有兩方面不同。首先,JDOM 僅使用具體類而不使用接口。這在某些方面簡化了 API,但是也限制了靈活性。第二,API 大量使用了 Collections 類,簡化了那些已經熟悉這些類的 Java 開發者的使用。 JDOM 文檔聲明其目的是“使用 20%(或更少)的精力解決 80%(或更多)Java/XML 問題”(根據學習曲線假定為 20%)。JDOM 對于大多數 Java/XML 應用程序來說當然是有用的,并且大多數開發者發現 API 比 DOM 容易理解得多。JDOM 還包括對程序行為的相當廣泛檢查以防止用戶做任何在 XML 中無意義的事。然而,它仍需要您充分理解 XML 以便做一些超出基本的工作(或者甚至理解某些情況下的錯誤)。這也許是比學習 DOM 或 JDOM 接口都更有意義的工作。 JDOM 自身不包含解析器。它通常使用 SAX2 解析器來解析和驗證輸入 XML 文檔(盡管它還可以將以前構造的 DOM 表示作為輸入)。它包含一些轉換器以將 JDOM 表示輸出成 SAX2 事件流、DOM 模型或 XML 文本文檔。JDOM 是在 Apache 許可證變體下發布的開放源碼。 Bean文件: package com.test; import java.io.*; import java.util.*; import org.jdom.*; import org.jdom.input.*; public class MyXMLReader { public static void main(String arge[]) { long lasting = System.currentTimeMillis(); try { SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(new File("data_10k.xml")); Element foo = doc.getRootElement(); List allChildren = foo.getChildren(); for(int i=0;i<allChildren.size();i++) { System.out.print("車牌號碼:" + ((Element)allChildren.get(i)).getChild("NO").getText()); System.out.println(" 車主地址:" + ((Element)allChildren.get(i)).getChild("ADDR").getText()); } } catch (Exception e) { e.printStackTrace(); } System.out.println("運行時間:" + (System.currentTimeMillis() - lasting) + " 毫秒"); } } 10k消耗時間:125 62 187 94 100k消耗時間:704 625 640 766 1000k消耗時間:27984 30750 27859 30656 10000k消耗時間:OutOfMemoryError 最后是 DOM4J http://dom4j.sourceforge.net/ 雖然 DOM4J 代表了完全獨立的開發結果,但最初,它是 JDOM 的一種智能分支。它合并了許多超出基本 XML 文檔表示的功能,包括集成的 XPath 支持、XML Schema 支持以及用于大文檔或流化文檔的基于事件的處理。它還提供了構建文檔表示的選項,它通過 DOM4J API 和標準 DOM 接口具有并行訪問功能。從 2000 下半年開始,它就一直處于開發之中。 為支持所有這些功能,DOM4J 使用接口和抽象基本類方法。DOM4J 大量使用了 API 中的 Collections 類,但是在許多情況下,它還提供一些替代方法以允許更好的性能或更直接的編碼方法。直接好處是,雖然 DOM4J 付出了更復雜的 API 的代價,但是它提供了比 JDOM 大得多的靈活性。 在添加靈活性、XPath 集成和對大文檔處理的目標時,DOM4J 的目標與 JDOM 是一樣的:針對 Java 開發者的易用性和直觀操作。它還致力于成為比 JDOM 更完整的解決方案,實現在本質上處理所有 Java/XML 問題的目標。在完成該目標時,它比 JDOM 更少強調防止不正確的應用程序行為。 DOM4J 是一個非常非常優秀的Java XML API,具有性能優異、功能強大和極端易用使用的特點,同時它也是一個開放源代碼的軟件。如今你可以看到越來越多的 Java 軟件都在使用 DOM4J 來讀寫 XML,特別值得一提的是連 Sun 的 JAXM 也在用 DOM4J。 Bean文件: package com.test; import java.io.*; import java.util.*; import org.dom4j.*; import org.dom4j.io.*; public class MyXMLReader { public static void main(String arge[]) { long lasting = System.currentTimeMillis(); try { File f = new File("data_10k.xml"); SAXReader reader = new SAXReader(); Document doc = reader.read(f); Element root = doc.getRootElement(); Element foo; for (Iterator i = root.elementIterator("VALUE"); i.hasNext();) { foo = (Element) i.next(); System.out.print("車牌號碼:" + foo.elementText("NO")); System.out.println(" 車主地址:" + foo.elementText("ADDR")); } } catch (Exception e) { e.printStackTrace(); } System.out.println("運行時間:" + (System.currentTimeMillis() - lasting) + " 毫秒"); } } 10k消耗時間:109 78 109 31 100k消耗時間:297 359 172 312 1000k消耗時間:2281 2359 2344 2469 10000k消耗時間:20938 19922 20031 21078 JDOM 和 DOM 在性能測試時表現不佳,在測試 10M 文檔時內存溢出。在小文檔情況下還值得考慮使用 DOM 和 JDOM。雖然 JDOM 的開發者已經說明他們期望在正式發行版前專注性能問題,但是從性能觀點來看,它確實沒有值得推薦之處。另外,DOM 仍是一個非常好的選擇。DOM 實現廣泛應用于多種編程語言。它還是許多其它與 XML 相關的標準的基礎,因為它正式獲得 W3C 推薦(與基于非標準的 Java 模型相對),所以在某些類型的項目中可能也需要它(如在 JavaScript 中使用 DOM)。 SAX表現較好,這要依賴于它特定的解析方式。一個 SAX 檢測即將到來的XML流,但并沒有載入到內存(當然當XML流被讀入時,會有部分文檔暫時隱藏在內存中)。 無疑,DOM4J是這場測試的獲勝者,目前許多開源項目中大量采用 DOM4J,例如大名鼎鼎的 Hibernate 也用 DOM4J 來讀取 XML 配置文件。如果不考慮可移植性,那就采用DOM4J吧! |
最近要從網頁中提取信息,想先把html轉換成標準的xml格式,然后方便使用dom4j進行后續的分析,試用了不少現成的類庫,JTidy、NekoHTML、HTML Parser、Jericho,最后還是使用了JTidy。
只是r8 snapshot還只是一個nightly builds,前面的r7版更已經是4年前的事了,這個項目就這樣荒廢了?可能是參與的人太少又或者是覺得已經成熟?
JTidy提供了一個語法檢查器和標簽補償器,能夠對各種亂七八糟的html進行修復,使之符合xhtml標準。
r8 snapshot相對于r7改變了一些參數的設置方法,特別是在字符編碼處理上,用setInputEncoding和setOutputEncoding方法來確定輸入和輸出文件的字符編碼,可以使用任何有效的Java編碼名稱,這就比以前的強多了。
總體上解析的結果還不錯,只是有些地方還需要對生成文件進行手工調整,或者自己再編一段代碼處理一下,已經不是大問題了。
一些常用的參數設置:
setAltText(java.lang.String altText)?
加上默認的alt屬性值
setBreakBeforeBR(boolean breakBeforeBR)
在換行<br />之前加一空行
setCharEncoding(int charencoding)
已廢棄
setConfigurationFromFile(java.lang.String filename)
從文件中讀取配置信息
setConfigurationFromProps(java.util.Properties props)
從properties中讀取配置信息
setErrfile(java.lang.String errfile)
錯誤輸出文件
setFixBackslash(boolean fixBackslash)
URL中用/取代\
setForceOutput(boolean forceOutput)
不管生成的xml是否有錯,強制輸出。
setHideComments(boolean hideComments)?
結果中不生成注釋
setInputEncoding(java.lang.String encoding)
輸入編碼
setLogicalEmphasis(boolean logicalEmphasis)
用em替代i,strong替代b
setMessageListener(TidyMessageListener listener)
加入一個TidyMessageListener監聽器
setOnlyErrors(boolean onlyErrors)?
只輸出錯誤文件
setOutputEncoding(java.lang.String encoding)?
輸出編碼
setPrintBodyOnly(boolean bodyOnly)?
只輸出body中的部分
setRepeatedAttributes(int repeatedAttributes)?
重復屬性的處理
setSpaces(int spaces)?
每行前的空格數,就是縮進格式
setTidyMark(boolean tidyMark)?
是否生成tidy標記
setTrimEmptyElements(boolean trimEmpty)?
不輸出空元素
setUpperCaseAttrs(boolean upperCaseAttrs)?
屬性變大寫
setUpperCaseTags(boolean upperCaseTags)?
標記變大寫
setWraplen(int wraplen)?
多長換行
setXHTML(boolean xhtml)?
輸出xhtml
setXmlOut(boolean xmlOut)
輸出xml
setXmlPi(boolean xmlPi)?
文件頭輸出xml標記
setXmlSpace(boolean xmlSpace)
加入xml名字空間屬性
使用方法極為easy,定義輸入輸出流,用tidy.parse()轉換就行了:
BufferedInputStream in;
FileOutputStream out;
Tidy tidy = new Tidy();
tidy.setConfigurationFromFile(configFileName);//配置文件,寫入上面的設置參數
try {
in = new BufferedInputStream(new FileInputStream(srcFileName));
out = new FileOutputStream(outFileName);
tidy.parse(in, out);
} catch (IOException e) {
System.out.println(e);
}
數據庫設計經驗談(夜來香)
一個成功的管理系統,是由:[50% 的業務 + 50% 的軟件] 所組成,而 50% 的成功軟件又有 [25% 的數據庫 + 25% 的程序] 所組成,數據庫設計的好壞是一個關鍵。如果把企業的數據比做生命所必需的血液,那么數據庫的設計就是應用中最重要的一部分。有關數據庫設計的材料汗牛充棟,大學學位課程里也有專門的講述。不過,就如我們反復強調的那樣,再好的老師也比不過經驗的教誨。所以我歸納歷年來所走的彎路及體會,并在網上找了些對數據庫設計頗有造詣的專業人士給大家傳授一些設計數據庫的技巧和經驗。精選了其中的 60 個最佳技巧,并把這些技巧編寫成了本文,為了方便索引其內容劃分為 5 個部分:
第 1 部分 - 設計數據庫之前
這一部分羅列了 12 個基本技巧,包括命名規范和明確業務需求等。
第 2 部分 - 設計數據庫表
總共 24 個指南性技巧,涵蓋表內字段設計以及應該避免的常見問題等。
第 3 部分 - 選擇鍵
怎么選擇鍵呢?這里有 10 個技巧專門涉及系統生成的主鍵的正確用法,還有何 時以及如何索引字段以獲得最佳性能等。
第 4 部分 - 保證數據完整性
討論如何保持數據庫的清晰和健壯,如何把有害數據降低到最小程度。
第 5 部分 - 各種小技巧
不包括在以上 4 個部分中的其他技巧,五花八門,有了它們希望你的數據庫開發工作會更輕松一些。
第 1 部分 - 設計數據庫之前
考察現有環境
在設計一個新數據庫時,你不但應該仔細研究業務需求而且還要考察現有的系統。大多數數據庫項目都不是從頭開始建立的;通常,機構內總會存在用來滿足特定需求的現有系統(可能沒有實現自動計算)。顯然,現有系統并不完美,否則你就不必再建立新系統了。但是對舊系統的研究可以讓你發現一些可能會忽略的細微問題。一般來說,考察現有系統對你絕對有好處。
定義標準的對象命名規范
一定要定義數據庫對象的命名規范。對數據庫表來說,從項目一開始就要確定表名是采用復數還是單數形式。此外還要給表的別名定義簡單規則(比方說,如果表名是一個單詞,別名就取單詞的前 4 個字母;如果表名是兩個單詞,就各取兩個單詞的前兩個字母組成 4 個字母長的別名;如果表的名字由 3 個單詞組成,你不妨從頭兩個單詞中各取一個然后從最后一個單詞中再取出兩個字母,結果還是組成 4 字母長的別名,其余依次類推)對工作用表來說,表名可以加上前綴 WORK_ 后面附上采用該表的應用程序的名字。表內的列[字段]要針對鍵采用一整套設計規則。比如,如果鍵是數字類型,你可以用 _N 作為后綴;如果是字符類型則可以采用 _C 后綴。對列[字段]名應該采用標準的前綴和后綴。再如,假如你的表里有好多“money”字段,你不妨給每個列[字段]增加一個 _M 后綴。還有,日期列[字段]最好以 D_ 作為名字打頭。
檢查表名、報表名和查詢名之間的命名規范。你可能會很快就被這些不同的數據庫要素的名稱搞糊涂了。假如你堅持統一地命名這些數據庫的不同組成部分,至少你應該在這些對象名字的開頭用 Table、Query 或者 Report 等前綴加以區別。
如果采用了 Microsoft Access,你可以用 qry、rpt、tbl 和 mod 等符號來標識對象(比如 tbl_Employees)。我在和 SQL Server 打交道的時候還用過 tbl 來索引表,但我用 sp_company (現在用 sp_feft_)標識存儲過程,因為在有的時候如果我發現了更好的處理辦法往往會保存好幾個拷貝。我在實現 SQL Server 2000 時用 udf_ (或者類似的標記)標識我編寫的函數。
工欲善其事, 必先利其器
采用理想的數據庫設計工具,比如:SyBase 公司的 PowerDesign,她支持 PB、VB、Delphe 等語言,通過 ODBC 可以連接市面上流行的 30 多個數據庫,包括 dBase、FoxPro、VFP、SQL Server 等,今后有機會我將著重介紹 PowerDesign 的使用。
獲取數據模式資源手冊
正在尋求示例模式的人可以閱讀《數據模式資源手冊》一書,該書由 Len Silverston、W. H. Inmon 和 Kent Graziano 編寫,是一本值得擁有的最佳數據建模圖書。該書包括的章節涵蓋多種數據領域,比如人員、機構和工作效能等。其他的你還可以參考:[1]薩師煊 王珊著 數據庫系統概論(第二版)高等教育出版社 1991、[2][美] Steven M.Bobrowski 著 Oracle 7 與客戶/服務器計算技術從入門到精通 劉建元等譯 電子工業出版社,1996、[3]周中元 信息系統建模方法(下) 電子與信息化 1999年第3期,1999
暢想未來,但不可忘了過去的教訓
我發現詢問用戶如何看待未來需求變化非常有用。這樣做可以達到兩個目的:首先,你可以清楚地了解應用設計在哪個地方應該更具靈活性以及如何避免性能瓶頸;其次,你知道發生事先沒有確定的需求變更時用戶將和你一樣感到吃驚。
一定要記住過去的經驗教訓!我們開發人員還應該通過分享自己的體會和經驗互相幫助。即使用戶認為他們再也不需要什么支持了,我們也應該對他們進行這方面的教育,我們都曾經面臨過這樣的時刻“當初要是這么做了該多好..”。
在物理實踐之前進行邏輯設計
在深入物理設計之前要先進行邏輯設計。隨著大量的 CASE 工具不斷涌現出來,你的設計也可以達到相當高的邏輯水準,你通常可以從整體上更好地了解數據庫設計所需要的方方面面。
了解你的業務
在你百分百地確定系統從客戶角度滿足其需求之前不要在你的 ER(實體關系)模式中加入哪怕一個數據表(怎么,你還沒有模式?那請你參看技巧 9)。了解你的企業業務可以在以后的開發階段節約大量的時間。一旦你明確了業務需求,你就可以自己做出許多決策了。
一旦你認為你已經明確了業務內容,你最好同客戶進行一次系統的交流。采用客戶的術語并且向他們解釋你所想到的和你所聽到的。同時還應該用可能、將會和必須等詞匯表達出系統的關系基數。這樣你就可以讓你的客戶糾正你自己的理解然后做好下一步的 ER 設計。
創建數據字典和 ER 圖表
一定要花點時間創建 ER 圖表和數據字典。其中至少應該包含每個字段的數據類型和在每個表內的主外鍵。創建 ER 圖表和數據字典確實有點費時但對其他開發人員要了解整個設計卻是完全必要的。越早創建越能有助于避免今后面臨的可能混亂,從而可以讓任何了解數據庫的人都明確如何從數據庫中獲得數據。
有一份諸如 ER 圖表等最新文檔其重要性如何強調都不過分,這對表明表之間關系很有用,而數據字典則說明了每個字段的用途以及任何可能存在的別名。對 SQL 表達式的文檔化來說這是完全必要的。
創建模式
一張圖表勝過千言萬語:開發人員不僅要閱讀和實現它,而且還要用它來幫助自己和用戶對話。模式有助于提高協作效能,這樣在先期的數據庫設計中幾乎不可能出現大的問題。模式不必弄的很復雜;甚至可以簡單到手寫在一張紙上就可以了。只是要保證其上的邏輯關系今后能產生效益。
從輸入輸出下手
在定義數據庫表和字段需求(輸入)時,首先應檢查現有的或者已經設計出的報表、查詢和視圖(輸出)以決定為了支持這些輸出哪些是必要的表和字段。舉個簡單的例子:假如客戶需要一個報表按照郵政編碼排序、分段和求和,你要保證其中包括了單獨的郵政編碼字段而不要把郵政編碼糅進地址字段里。
報表技巧
要了解用戶通常是如何報告數據的:批處理還是在線提交報表?時間間隔是每天、每周、每月、每個季度還是每年?如果需要的話還可以考慮創建總結表。系統生成的主鍵在報表中很難管理。用戶在具有系統生成主鍵的表內用副鍵進行檢索往往會返回許多重復數據。這樣的檢索性能比較低而且容易引起混亂。
理解客戶需求
看起來這應該是顯而易見的事,但需求就是來自客戶(這里要從內部和外部客戶的角度考慮)。不要依賴用戶寫下來的需求,真正的需求在客戶的腦袋里。你要讓客戶解釋其需求,而且隨著開發的繼續,還要經常詢問客戶保證其需求仍然在開發的目的之中。一個不變的真理是:“只有我看見了我才知道我想要的是什么”必然會導致大量的返工,因為數據庫沒有達到客戶從來沒有寫下來的需求標準。而更糟的是你對他們需求的解釋只屬于你自己,而且可能是完全錯誤的。
第 2 部分 - 設計表和字段
檢查各種變化
我在設計數據庫的時候會考慮到哪些數據字段將來可能會發生變更。比方說,姓氏就是如此(注意是西方人的姓氏,比如女性結婚后從夫姓等)。所以,在建立系統存儲客戶信息時,我傾向于在單獨的一個數據表里存儲姓氏字段,而且還附加起始日和終止日等字段,這樣就可以跟蹤這一數據條目的變化。
采用有意義的字段名
有一回我參加開發過一個項目,其中有從其他程序員那里繼承的程序,那個程序員喜歡用屏幕上顯示數據指示用語命名字段,這也不賴,但不幸的是,她還喜歡用一些奇怪的命名法,其命名采用了匈牙利命名和控制序號的組合形式,比如 cbo1、txt2、txt2_b 等等。
除非你在使用只面向你的縮寫字段名的系統,否則請盡可能地把字段描述的清楚些。當然,也別做過頭了,比如 Customer_Shipping_Address_Street_Line_1,雖然很富有說明性,但沒人愿意鍵入這么長的名字,具體尺度就在你的把握中。
采用前綴命名
如果多個表里有好多同一類型的字段(比如 FirstName),你不妨用特定表的前綴(比如 CusLastName)來幫助你標識字段。
時效性數據應包括“最近更新日期/時間”字段。時間標記對查找數據問題的原因、按日期重新處理/重載數據和清除舊數據特別有用。
標準化和數據驅動
數據的標準化不僅方便了自己而且也方便了其他人。比方說,假如你的用戶界面要訪問外部數據源(文件、XML 文檔、其他數據庫等),你不妨把相應的連接和路徑信息存儲在用戶界面支持表里。還有,如果用戶界面執行工作流之類的任務(發送郵件、打印信箋、修改記錄狀態等),那么產生工作流的數據也可以存放在數據庫里。預先安排總需要付出努力,但如果這些過程采用數據驅動而非硬編碼的方式,那么策略變更和維護都會方便得多。事實上,如果過程是數據驅動的,你就可以把相當大的責任推給用戶,由用戶來維護自己的工作流過程。
標準化不能過頭
對那些不熟悉標準化一詞(normalization)的人而言,標準化可以保證表內的字段都是最基礎的要素,而這一措施有助于消除數據庫中的數據冗余。標準化有好幾種形式,但 Third Normal Form(3NF)通常被認為在性能、擴展性和數據完整性方面達到了最好平衡。簡單來說,3NF 規定:
* 表內的每一個值都只能被表達一次。
* 表內的每一行都應該被唯一的標識(有唯一鍵)。
* 表內不應該存儲依賴于其他鍵的非鍵信息。
遵守 3NF 標準的數據庫具有以下特點:有一組表專門存放通過鍵連接起來的關聯數據。比方說,某個存放客戶及其有關定單的 3NF 數據庫就可能有兩個表:Customer 和 Order。Order 表不包含定單關聯客戶的任何信息,但表內會存放一個鍵值,該鍵指向 Customer 表里包含該客戶信息的那一行。
更高層次的標準化也有,但更標準是否就一定更好呢?答案是不一定。事實上,對某些項目來說,甚至就連 3NF 都可能給數據庫引入太高的復雜性。
為了效率的緣故,對表不進行標準化有時也是必要的,這樣的例子很多。曾經有個開發餐飲分析軟件的活就是用非標準化表把查詢時間從平均 40 秒降低到了兩秒左右。雖然我不得不這么做,但我絕不把數據表的非標準化當作當然的設計理念。而具體的操作不過是一種派生。所以如果表出了問題重新產生非標準化的表是完全可能的。
Microsoft Visual FoxPro 報表技巧
如果你正在使用 Microsoft Visual FoxPro,你可以用對用戶友好的字段名來代替編號的名稱:比如用 Customer Name 代替 txtCNaM。這樣,當你用向導程序 [Wizards,臺灣人稱為‘精靈’] 創建表單和報表時,其名字會讓那些不是程序員的人更容易閱讀。
不活躍或者不采用的指示符
增加一個字段表示所在記錄是否在業務中不再活躍挺有用的。不管是客戶、員工還是其他什么人,這樣做都能有助于再運行查詢的時候過濾活躍或者不活躍狀態。同時還消除了新用戶在采用數據時所面臨的一些問題,比如,某些記錄可能不再為他們所用,再刪除的時候可以起到一定的防范作用。
使用角色實體定義屬于某類別的列[字段]
在需要對屬于特定類別或者具有特定角色的事物做定義時,可以用角色實體來創建特定的時間關聯關系,從而可以實現自我文檔化。
這里的含義不是讓 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 等。
還有個替代辦法就是改變 PERSON 記錄來反映新頭銜的變化,不過這樣一來在時間上無法跟蹤個人所處位置的具體時間。
采用常用實體命名機構數據
組織數據的最簡單辦法就是采用常用名字,比如:PERSON、ORGANIZATION、ADDRESS 和 PHONE 等等。當你把這些常用的一般名字組合起來或者創建特定的相應副實體時,你就得到了自己用的特殊版本。開始的時候采用一般術語的主要原因在于所有的具體用戶都能對抽象事物具體化。
有了這些抽象表示,你就可以在第 2 級標識中采用自己的特殊名稱,比如,PERSON 可能是 Employee、Spouse、Patient、Client、Customer、Vendor 或者 Teacher 等。同樣的,ORGANIZATION 也可能是 MyCompany、MyDepartment、Competitor、Hospital、Warehouse、Government 等。最后 ADDRESS 可以具體為 Site、Location、Home、Work、Client、Vendor、Corporate 和 FieldOffice 等。
采用一般抽象術語來標識“事物”的類別可以讓你在關聯數據以滿足業務要求方面獲得巨大的靈活性,同時這樣做還可以顯著降低數據存儲所需的冗余量。
用戶來自世界各地
在設計用到網絡或者具有其他國際特性的數據庫時,一定要記住大多數國家都有不同的字段格式,比如郵政編碼等,有些國家,比如新西蘭就沒有郵政編碼一說。
數據重復需要采用分立的數據表
如果你發現自己在重復輸入數據,請創建新表和新的關系。
每個表中都應該添加的 3 個有用的字段
* dRecordCreationDate,在 VB 下默認是 Now(),而在 SQL Server 下默認為 GETDATE()
* sRecordCreator,在 SQL Server 下默認為 NOT NULL DEFAULT USER
* nRecordVersion,記錄的版本標記;有助于準確說明記錄中出現 null 數據或者丟失數據的原因
對地址和電話采用多個字段
描述街道地址就短短一行記錄是不夠的。Address_Line1、Address_Line2 和 Address_Line3 可以提供更大的靈活性。還有,電話號碼和郵件地址最好擁有自己的數據表,其間具有自身的類型和標記類別。
過分標準化可要小心,這樣做可能會導致性能上出現問題。雖然地址和電話表分離通常可以達到最佳狀態,但是如果需要經常訪問這類信息,或許在其父表中存放“首選”信息(比如 Customer 等)更為妥當些。非標準化和加速訪問之間的妥協是有一定意義的。
使用多個名稱字段
我覺得很吃驚,許多人在數據庫里就給 name 留一個字段。我覺得只有剛入門的開發人員才會這么做,但實際上網上這種做法非常普遍。我建議應該把姓氏和名字當作兩個字段來處理,然后在查詢的時候再把他們組合起來。
我最常用的是在同一表中創建一個計算列[字段],通過它可以自動地連接標準化后的字段,這樣數據變動的時候它也跟著變。不過,這樣做在采用建模軟件時得很機靈才行。總之,采用連接字段的方式可以有效的隔離用戶應用和開發人員界面。
提防大小寫混用的對象名和特殊字符
過去最令我惱火的事情之一就是數據庫里有大小寫混用的對象名,比如 CustomerData。這一問題從 Access 到 Oracle 數據庫都存在。我不喜歡采用這種大小寫混用的對象命名方法,結果還不得不手工修改名字。想想看,這種數據庫/應用程序能混到采用更強大數據庫的那一天嗎?采用全部大寫而且包含下劃符的名字具有更好的可讀性(CUSTOMER_DATA),絕對不要在對象名的字符之間留空格。
小心保留詞
要保證你的字段名沒有和保留詞、數據庫系統或者常用訪問方法沖突,比如,最近我編寫的一個 ODBC 連接程序里有個表,其中就用了 DESC 作為說明字段名。后果可想而知!DESC 是 DESCENDING 縮寫后的保留詞。表里的一個 SELECT * 語句倒是能用,但我得到的卻是一大堆毫無用處的信息。
保持字段名和類型的一致性
在命名字段并為其指定數據類型的時候一定要保證一致性。假如字段在某個表中叫做“agreement_number”,你就別在另一個表里把名字改成“ref1”。假如數據類型在一個表里是整數,那在另一個表里可就別變成字符型了。記住,你干完自己的活了,其他人還要用你的數據庫呢。
仔細選擇數字類型
在 SQL 中使用 smallint 和 tinyint 類型要特別小心,比如,假如你想看看月銷售總額,你的總額字段類型是 smallint,那么,如果總額超過了 $32,767 你就不能進行計算操作了。
刪除標記
在表中包含一個“刪除標記”字段,這樣就可以把行標記為刪除。在關系數據庫里不要單獨刪除某一行;最好采用清除數據程序而且要仔細維護索引整體性。
避免使用觸發器
觸發器的功能通常可以用其他方式實現。在調試程序時觸發器可能成為干擾。假如你確實需要采用觸發器,你最好集中對它文檔化。
包含版本機制
建議你在數據庫中引入版本控制機制來確定使用中的數據庫的版本。無論如何你都要實現這一要求。時間一長,用戶的需求總是會改變的。最終可能會要求修改數據庫結構。雖然你可以通過檢查新字段或者索引來確定數據庫結構的版本,但我發現把版本信息直接存放到數據庫中不更為方便嗎?。
給文本字段留足余量
ID 類型的文本字段,比如客戶 ID 或定單號等等都應該設置得比一般想象更大,因為時間不長你多半就會因為要添加額外的字符而難堪不已。比方說,假設你的客戶 ID 為 10 位數長。那你應該把數據庫表字段的長度設為 12 或者 13 個字符長。這算浪費空間嗎?是有一點,但也沒你想象的那么多:一個字段加長 3 個字符在有 1 百萬條記錄,再加上一點索引的情況下才不過讓整個數據庫多占據 3MB 的空間。但這額外占據的空間卻無需將來重構整個數據庫就可以實現數據庫規模的增長了。身份證的號碼從 15 位變成 18 位就是最好和最慘痛的例子。
列[字段]命名技巧
我們發現,假如你給每個表的列[字段]名都采用統一的前綴,那么在編寫 SQL 表達式的時候會得到大大的簡化。這樣做也確實有缺點,比如破壞了自動表連接工具的作用,后者把公共列[字段]名同某些數據庫聯系起來,不過就連這些工具有時不也連接錯誤嘛。舉個簡單的例子,假設有兩個表:
Customer 和 Order。Customer 表的前綴是 cu_,所以該表內的子段名如下:cu_name_id、cu_surname、cu_initials 和cu_address 等。Order 表的前綴是 or_,所以子段名是:
or_order_id、or_cust_name_id、or_quantity 和 or_description 等。
這樣從數據庫中選出全部數據的 SQL 語句可以寫成如下所示:
Select * From Customer, Order Where cu_surname = "MYNAME" ;
and cu_name_id = or_cust_name_id and or_quantity = 1
在沒有這些前綴的情況下則寫成這個樣子(用別名來區分):
Select * From Customer, Order Where Customer.surname = "MYNAME" ;
and Customer.name_id = Order.cust_name_id and Order.quantity = 1
第 1 個 SQL 語句沒少鍵入多少字符。但如果查詢涉及到 5 個表乃至更多的列[字段]你就知道這個技巧多有用了。
第 3 部分 - 選擇鍵和索引
數據采掘要預先計劃
我所在的某一客戶部門一度要處理 8 萬多份聯系方式,同時填寫每個客戶的必要數據(這絕對不是小活)。我從中還要確定出一組客戶作為市場目標。當我從最開始設計表和字段的時候,我試圖不在主索引里增加太多的字段以便加快數據庫的運行速度。然后我意識到特定的組查詢和信息采掘既不準確速度也不快。結果只好在主索引中重建而且合并了數據字段。我發現有一個指示計劃相當關鍵——當我想創建系統類型查找時為什么要采用號碼作為主索引字段呢?我可以用傳真號碼進行檢索,但是它幾乎就象系統類型一樣對我來說并不重要。采用后者作為主字段,數據庫更新后重新索引和檢索就快多了。
可操作數據倉庫(ODS)和數據倉庫(DW)這兩種環境下的數據索引是有差別的。在 DW 環境下,你要考慮銷售部門是如何組織銷售活動的。他們并不是數據庫管理員,但是他們確定表內的鍵信息。這里設計人員或者數據庫工作人員應該分析數據庫結構從而確定出性能和正確輸出之間的最佳條件。
使用系統生成的主鍵
這類同技巧 1,但我覺得有必要在這里重復提醒大家。假如你總是在設計數據庫的時候采用系統生成的鍵作為主鍵,那么你實際控制了數據庫的索引完整性。這樣,數據庫和非人工機制就有效地控制了對存儲數據中每一行的訪問。
采用系統生成鍵作為主鍵還有一個優點:當你擁有一致的鍵結構時,找到邏輯缺陷很容易。
分解字段用于索引
為了分離命名字段和包含字段以支持用戶定義的報表,請考慮分解其他字段(甚至主鍵)為其組成要素以便用戶可以對其進行索引。索引將加快 SQL 和報表生成器腳本的執行速度。比方說,我通常在必須使用 SQL LIKE 表達式的情況下創建報表,因為 case number 字段無法分解為 year、serial number、case type 和 defendant code 等要素。性能也會變壞。假如年度和類型字段可以分解為索引字段那么這些報表運行起來就會快多了。
鍵設計 4 原則
* 為關聯字段創建外鍵。
* 所有的鍵都必須唯一。
* 避免使用復合鍵。
* 外鍵總是關聯唯一的鍵字段。
別忘了索引
索引是從數據庫中獲取數據的最高效方式之一。95% 的數據庫性能問題都可以采用索引技術得到解決。作為一條規則,我通常對邏輯主鍵使用唯一的成組索引,對系統鍵(作為存儲過程)采用唯一的非成組索引,對任何外鍵列[字段]采用非成組索引。不過,索引就象是鹽,太多了菜就咸了。你得考慮數據庫的空間有多大,表如何進行訪問,還有這些訪問是否主要用作讀寫。
大多數數據庫都索引自動創建的主鍵字段,但是可別忘了索引外鍵,它們也是經常使用的鍵,比如運行查詢顯示主表和所有關聯表的某條記錄就用得上。還有,不要索引 memo/note 字段,不要索引大型字段(有很多字符),這樣作會讓索引占用太多的存儲空間。
不要索引常用的小型表
不要為小型數據表設置任何鍵,假如它們經常有插入和刪除操作就更別這樣作了。對這些插入和刪除操作的索引維護可能比掃描表空間消耗更多的時間。
不要把社會保障號碼(SSN)或身份證號碼(ID)選作鍵
永遠都不要使用 SSN 或 ID 作為數據庫的鍵。除了隱私原因以外,須知政府越來越趨向于不準許把 SSN 或 ID 用作除收入相關以外的其他目的,SSN 或 ID 需要手工輸入。永遠不要使用手工輸入的鍵作為主鍵,因為一旦你輸入錯誤,你唯一能做的就是刪除整個記錄然后從頭開始。
我在破解他人的程序時候,我看到很多人把 SSN 或 ID 還曾被用做系列號,當然盡管這么做是非法的。而且人們也都知道這是非法的,但他們已經習慣了。后來,隨著盜取身份犯罪案件的增加,我現在的同行正痛苦地從一大攤子數據中把 SSN 或 ID 刪除。
不要用用戶的鍵
在確定采用什么字段作為表的鍵的時候,可一定要小心用戶將要編輯的字段。通常的情況下不要選擇用戶可編輯的字段作為鍵。這樣做會迫使你采取以下兩個措施:
* 在創建記錄之后對用戶編輯字段的行為施加限制。假如你這么做了,你可能會發現你的應用程序在商務需求突然發生變化,而用戶需要編輯那些不可編輯的字段時缺乏足夠的靈活性。當用戶在輸入數據之后直到保存記錄才發現系統出了問題他們該怎么想?刪除重建?假如記錄不可重建是否讓用戶走開?
* 提出一些檢測和糾正鍵沖突的方法。通常,費點精力也就搞定了,但是從性能上來看這樣做的代價就比較大了。還有,鍵的糾正可能會迫使你突破你的數據和商業/用戶界面層之間的隔離。
所以還是重提一句老話:你的設計要適應用戶而不是讓用戶來適應你的設計。
不讓主鍵具有可更新性的原因是在關系模式下,主鍵實現了不同表之間的關聯。比如,Customer 表有一個主鍵 CustomerID,而客戶的定單則存放在另一個表里。Order 表的主鍵可能是 OrderNo 或者 OrderNo、CustomerID 和日期的組合。不管你選擇哪種鍵設置,你都需要在 Order 表中存放 CustomerID 來保證你可以給下定單的用戶找到其定單記錄。
假如你在 Customer 表里修改了 CustomerID,那么你必須找出 Order 表中的所有相關記錄對其進行修改。否則,有些定單就會不屬于任何客戶——數據庫的完整性就算完蛋了。
如果索引完整性規則施加到表一級,那么在不編寫大量代碼和附加刪除記錄的情況下幾乎不可能改變某一條記錄的鍵和數據庫內所有關聯的記錄。而這一過程往往錯誤叢生所以應該盡量避免。
可選鍵(候選鍵)有時可做主鍵
記住,查詢數據的不是機器而是人。
假如你有可選鍵,你可能進一步把它用做主鍵。那樣的話,你就擁有了建立強大索引的能力。這樣可以阻止使用數據庫的人不得不連接數據庫從而恰當的過濾數據。在嚴格控制域表的數據庫上,這種負載是比較醒目的。如果可選鍵真正有用,那就是達到了主鍵的水準。
我的看法是,假如你有可選鍵,比如國家表內的 state_code,你不要在現有不能變動的唯一鍵上創建后續的鍵。你要做的無非是創建毫無價值的數據。如你因為過度使用表的后續鍵[別名]建立這種表的關聯,操作負載真得需要考慮一下了。
別忘了外鍵
大多數數據庫索引自動創建的主鍵字段。但別忘了索引外鍵字段,它們在你想查詢主表中的記錄及其關聯記錄時每次都會用到。還有,不要索引 memo/notes 字段而且不要索引大型文本字段(許多字符),這樣做會讓你的索引占據大量的數據庫空間。
第 4 部分 - 保證數據的完整性
用約束而非商務規則強制數據完整性
如果你按照商務規則來處理需求,那么你應當檢查商務層次/用戶界面:如果商務規則以后發生變化,那么只需要進行更新即可。假如需求源于維護數據完整性的需要,那么在數據庫層面上需要施加限制條件。如果你在數據層確實采用了約束,你要保證有辦法把更新不能通過約束檢查的原因采用用戶理解的語言通知用戶界面。除非你的字段命名很冗長,否則字段名本身還不夠。
只要有可能,請采用數據庫系統實現數據的完整性。這不但包括通過標準化實現的完整性而且還包括數據的功能性。在寫數據的時候還可以增加觸發器來保證數據的正確性。不要依賴于商務層保證數據完整性;它不能保證表之間(外鍵)的完整性所以不能強加于其他完整性規則之上。
分布式數據系統
對分布式系統而言,在你決定是否在各個站點復制所有數據還是把數據保存在一個地方之前應該估計一下未來 5 年或者 10 年的數據量。當你把數據傳送到其他站點的時候,最好在數據庫字段中設置一些標記。在目的站點收到你的數據之后更新你的標記。為了進行這種數據傳輸,請寫下你自己的批處理或者調度程序以特定時間間隔運行而不要讓用戶在每天的工作后傳輸數據。本地拷貝你的維護數據,比如計算常數和利息率等,設置版本號保證數據在每個站點都完全一致。
強制指示完整性(參照完整性?)
沒有好辦法能在有害數據進入數據庫之后消除它,所以你應該在它進入數據庫之前將其剔除。激活數據庫系統的指示完整性特性。這樣可以保持數據的清潔而能迫使開發人員投入更多的時間處理錯誤條件。
關系
如果兩個實體之間存在多對一關系,而且還有可能轉化為多對多關系,那么你最好一開始就設置成多對多關系。從現有的多對一關系轉變為多對多關系比一開始就是多對多關系要難得多。
采用視圖
為了在你的數據庫和你的應用程序代碼之間提供另一層抽象,你可以為你的應用程序建立專門的視圖而不必非要應用程序直接訪問數據表。這樣做還等于在處理數據庫變更時給你提供了更多的自由。
給數據保有和恢復制定計劃
考慮數據保有策略并包含在設計過程中,預先設計你的數據恢復過程。采用可以發布給用戶/開發人員的數據字典實現方便的數據識別同時保證對數據源文檔化。編寫在線更新來“更新查詢”供以后萬一數據丟失可以重新處理更新。
用存儲過程讓系統做重活
解決了許多麻煩來產生一個具有高度完整性的數據庫解決方案之后,我決定封裝一些關聯表的功能組,提供一整套常規的存儲過程來訪問各組以便加快速度和簡化客戶程序代碼的開發。數據庫不只是一個存放數據的地方,它也是簡化編碼之地。
使用查找
控制數據完整性的最佳方式就是限制用戶的選擇。只要有可能都應該提供給用戶一個清晰的價值列表供其選擇。這樣將減少鍵入代碼的錯誤和誤解同時提供數據的一致性。某些公共數據特別適合查找:國家代碼、狀態代碼等。
第 5 部分 - 各種小技巧
文檔、文檔、文檔
對所有的快捷方式、命名規范、限制和函數都要編制文檔。
采用給表、列[字段]、觸發器等加注釋的數據庫工具。是的,這有點費事,但從長遠來看,這樣做對開發、支持和跟蹤修改非常有用。
取決于你使用的數據庫系統,可能有一些軟件會給你一些供你很快上手的文檔。你可能希望先開始在說,然后獲得越來越多的細節。或者你可能希望周期性的預排,在輸入新數據同時隨著你的進展對每一部分細節化。不管你選擇哪種方式,總要對你的數據庫文檔化,或者在數據庫自身的內部或者單獨建立文檔。這樣,當你過了一年多時間后再回過頭來做第 2 個版本,你犯錯的機會將大大減少。
使用常用英語(或者其他任何語言)而不要使用編碼
為什么我們經常采用編碼(比如 9935A 可能是‘青島啤酒’的供應代碼,4XF788-Q 可能是帳目編碼)?理由很多。但是用戶通常都用英語進行思考而不是編碼。工作 5 年的會計或許知道 4XF788-Q 是什么東西,但新來的可就不一定了。在創建下拉菜單、列表、報表時最好按照英語名排序。假如你需要編碼,那你可以在編碼旁附上用戶知道的英語。
保存常用信息
讓一個表專門存放一般數據庫信息非常有用。我常在這個表里存放數據庫當前版本、最近檢查/修復(對 FoxPro)、關聯設計文檔的名稱、客戶等信息。這樣可以實現一種簡單機制跟蹤數據庫,當客戶抱怨他們的數據庫沒有達到希望的要求而與你聯系時,這樣做對非客戶機/服務器環境特別有用。
測試、測試、反復測試
建立或者修訂數據庫之后,必須用用戶新輸入的數據測試數據字段。最重要的是,讓用戶進行測試并且同用戶一道保證你選擇的數據類型滿足商業要求。測試需要在把新數據庫投入實際服務之前完成。
檢查設計
在開發期間檢查數據庫設計的常用技術是通過其所支持的應用程序原型檢查數據庫。換句話說,針對每一種最終表達數據的原型應用,保證你檢查了數據模型并且查看如何取出數據。
Microsoft Visual FoxPro 設計技巧
對復雜的 Microsoft Visual FoxPro 數據庫應用程序而言,可以把所有的主表放在一個數據庫容器文件里,然后增加其他數據庫表文件和裝載同原有數據庫有關的特殊文件。根據需要用這些文件連接到主文件中的主表。比如數據輸入、數據索引、統計分析、向管理層或者政府部門提供報表以及各類只讀查詢等。這一措施簡化了用戶和組權限的分配,而且有利于應用程序函數(存儲過程)的分組和劃分,從而在程序必須修改的時候易于管理。
謹把本文獻給戰斗在狐貍戰線上的同志們!!!
-- 夜來香
2002/05/30
?
March 5th, 2006
原文:Thinking in Web 2.0: Sixteen Ways
作者:Dion Hinchcliffe
1、在你開始之前,先定一個簡單的目標。無論你是一個Web 2.0應用的創建者還是用戶,請清晰的構思你的目標。就像“我需要保存一個書簽”或者“我準備幫助人們創建可編輯的、共享的頁面”這樣的目標,讓你保持最基礎的需求。很多Web 2.0應用的最初吸引之處就是它的簡單,避免并隱藏了那些多余的復雜性。站在創建者的立場,可以想象Google的幾乎沒有內容的主頁,還有del.icio.us的簡單的線條。從最終用戶的角度來看,與之齊名的就是Diggdot.us所提供的初始化頁面。你能夠并且希望加入更多功能,但是先做好最開始的。在一個時候只做一個特性,完成一個目標。這聽起來很太過于單純化了,但它將使你更加專注,而且你也會明白我的意思。
2、鏈接是最基礎的思想。這就是我們稱之為Web的一個理由。鏈接是把Web中各種實體連接起來的最基本的元素。你的信息、你的關系、你的導航,甚至是能夠被寫成URL的任何內容。這里有一個鏈接應該遵循的規則(其實你也不必嚴格的遵守):
1. Web上的任何東西都是可以被URI或者是URL所連接的。
2. 把所有的鏈接都保存為他的原始出處,這樣可以讓你與任何人、在任何地方、任何時候都能分享它。
3. 第二條中任何時候的前提是鏈接必須是持久的,它不會在沒有任何緣由的情況下被改變或者是消失。
4. 鏈接應該是人類可讀的、穩定的、并且能夠自我詮釋的。
3、數據應該屬于創建它的人。是的,你聽我的。任何用戶創建的、貢獻的或分享的都是他們自己的,除非他們很明顯的放棄這個權力來讓你自由處置。他們貢獻到Web上的任何信息都應該是可編輯的、能被刪除的、并且能夠取消共享,無論在任何時候,只要用戶愿意。這也包含了那些間接的數據,像他們所關心的記錄、日志、瀏覽歷史、網站訪問信息,或者是任何可以被跟蹤的信息。所有的網站必須清晰簡單的陳訴那些信息是用戶創建的,并且提供他們停止創建的方法,甚至是清除的方法。
4、數據優先,體驗與功能其次。無論它是文本、圖片、音頻還是視頻,Web最終還是把這些解析為數據。換句話說,你無法脫離數據去呈現內容。所有這些數據都通過那些易于發現的URL來定位(參見第2條)。通過另一種形式來看待這些,Web最終是名詞優先,動詞其次,雖然最近正在向動詞偏移。來看看名詞的例子:日歷的條目、家庭照片、股票價格。還有一些動詞的例子:定一個約會、共享一張圖片、買一份股票。
5、做好積極分享一切的準備。盡可能的分享一切,你所擁有的所有數據,你所提供的所有服務。鼓勵不遵循原有意圖的使用,提倡貢獻,不要那些需要分享的內容堅持設置為私有的。在分享與發現之后,提供易于使用的瀏覽方式是顯而易見的需求。為什么呢:話說回來,你會從別人的共享之中受益匪淺。注意:這里沒有許可讓你去侵犯版權保護的法律,你不能夠去分享你刻錄的DVD或者是擁有商業版權音樂,因為你已經同意不會去分享這些東西。但是你可以發現并分享那些完全開放的媒體內容。一個小小的建議,你可以學習一下Creative Commons license(共創協議).
6、Web是一個平臺;要讓它成長。當然,我們還有很多其他的平臺(Windows、Linux、Mac),但是那些已經不是重點了。換句話說,Web是無法脫離的平臺,不會中斷的平臺,你可以通過各種方式去擴展的平臺。你在Web上提供的數據與服務將會成為Web一部分,最終你會在Web平臺的某一處扮演你的角色。扮演好你的角色并照顧好后來者。
7、理解與信奉“階梯性”。現在的Web越來越大,幾乎蔓延到了全世界的所有國家,并且已經擁有了10億用戶。我的觀點是Web的各個組成部分存在著細微的區別和不同,就像不同地方的用戶那樣。例如Web的設計部分:易用性永遠優先于速度、可靠性、重用性與可集成性。你也應該提供同樣的體驗給你的用戶。它已經被一次又一次的被人們在文檔中強調,忠誠的用戶很快會成為專業的用戶,他們期待更快的速度還有更多。退一步支持他們。同樣,也有很多很多的用戶會進入這個階梯的底端,如你所期待的那樣。他們可能不會說你的語言,不熟悉你的文化,甚至不知道是如何到這里的。所以你需要向他們表達清楚。
8、任何東西都是可編輯的。或者是它應該被編織的更好。要確定的是,只有很少的東西是不能被編輯的,剩下的都可以,這是一個可寫的Web。這并不意味著原始內容的丟失,而通常被理解為用戶能夠很容易的對內容加以評論,或者評注內容是在那里發現的。如果你對此應用的好,他們能夠比你所想象的做的更多(把內容串起來并且給予原始內容來創建自己的,等等)。
9、Web上的身份是神圣的。不幸的是,這并不意味著你能夠得到更多的隱私(這完全是上個世紀的想法)。但對身份的驗證是必要的,你應該感謝那些只需一個郵件地址就能確定你身份的服務。這意味只要你對你的用戶承諾了,你就必須保證他們的隱私安全。必要的時候,在這個世界的某處你還得為你的用戶挺身而出,向當地的權威挑戰。如果你沒有打算那樣做,你就得把實際情況告訴你的用戶。另一方面,如果身份是必須的,不要試圖偽裝它,不然在某一天我們將會在Web上放棄我們的最后一點點隱私的權利。
10、了解流行的標準并且使用他們。從一個消費者或者是創作者的立場來看,數據將會以不同的格式與任何一個人交換。同時這樣的數據也會反過來促進標準的完善與采納。這通常意味像RSS、 OPML、XHTML、Simple XML、JSON等簡單標準的流行,而避免SOAP、XSD,還有RDF、ATOM也一樣,使用它們會給我的內心帶來痛苦。請你也為你所鐘愛的標準投上一票來支持它們。
11、遵循無意使用的規律。如果你把非常有趣的數據和服務用廣泛使用的格式開放和共享出去,你將會得到你所應得的,其他人也將會基于你的那一塊Web平臺來構建。或許還會從別人那里得到更多,所以為這個做一下準備比較好。我已記不清有多少次我看到一個播客(podcasting)服務因為流行過渡而導致服務垮掉,就是因為他們被 Slashdot和del.icio.us給收錄了。這一點要知道:網絡上的大量化意味著如果一個內容非常有趣,即使是一個很小的角落也會得到驚人的訪問量。鼓勵使用這種方式,它還是非常有價值的,前提是你要有所準備。
12、粒化你的數據與服務。我們應該在很早以前就明白這些,大規模集成的數據僅僅適用于無需管理的下載與批量操作。分解你的數據,讓他們獨立成可描述的URL地址,對你的服務也一樣。反過來說,你不要創建一些巨大的、復雜的、像圣誕樹那樣的數據結構和服務。保持簡單,要非常的簡單。讓這些分離的片斷能夠容易的被重組和發現。
13、提供用戶能夠單獨受益的數據和服務。漸漸依賴于這種社會化參與是存在風險的,你需要讓你的用戶有一點點動機來貢獻時間、熱情和信息,除非他們能夠直接受益。社會化分享比個體行為的利益大很多,除非你能夠激發用戶的個人動機,否這你將無法享受這份厚禮。
14、讓用戶組織并過濾信息。不一定是必須的,但卻是非常重要的。讓用戶以他們自己的方式來標注和組織數據,因為你自己是永遠無法及時的處理他們的。用戶會按照他們自己理解的最佳方式來處理并構建。要保證你的Web服務能夠按照用戶所需所想的方式來工作。這也是標簽(tagging)和通俗分類(folksonomies )的方式如此成功的主要因素。
15、提供豐富的用戶體驗。Web一直都在和本地的應用程序進行著激烈的競爭。為什么?因為本地程序還是感覺上好一些,速度也快一些。但是這不會長久的(確信在5年或者15年后,這種競爭就不存在了)。是的,我在談論Rich Internet Applications, Ajax, 還有那些不可思議的交互應用。他們讓Web成為了一個真正的“無平臺”的平臺,如果你知道我是怎么想的。
16、信奉并支持快速改進和反饋。這個通常意味著加快步伐,但也意味著使用輕量級的工具、技術和不要做出那些適得其反的痛苦決定(例如使用一個被層層環繞的Ajax框架來代替可以通過混合來實現的,或者用C++來構建所有的東西,其實使用Ruby會更好一些)。這同時也意味著需要一個非常快速的方式來處理錯誤報告,修復Bug,釋放新版本。從一個用戶的角度來看,報告你所發現的任何問題,還有那些你經常抱怨的地方,甚至那些都不是一個Bug。
當然,Web 2.0是一個極其廣泛和深奧的話題,沒有一個人能夠列舉出它的所有重點和特征。如果你對此充滿了興趣,請花一點時間來補充我沒有提到的地方。我想這就是Web 2.0的參與性吧!
原作者的這個標題借鑒了Bruce Eckel的兩本暢銷書的名字:《Thinking in C++》和《Thinking in Java》,《C++編程思想》與《Java編程思想》,在此說明一下為什么要這樣翻譯這個題目:)
indigo 翻譯整理