文章目錄
- 1.5.1 視圖
- 1.5.3 觸發器與預存程序
- 1.5.4 約束條件
- 1.5.5 資料庫交易處理
- 1.5.6 索引與主鍵
轉自 http://www.cnblogs.com/zhenyulu/articles/204227.html
第1章 資料庫基礎 1.1 一個膚淺的定義
什麼是資料庫?這是一個很難回答的問題,經典的教科書往往都是從資訊、資料說起,直到資料庫。這裡我想直接給出一個不準確的,膚淺的定義(這也是最早的FoxBASE時代,絕大多數人對資料庫的認識):
【定義】:(1)一個庫便是一張二維表格,表由表頭(欄位)與表的內容(記錄)組成。(2)建立在該表上的操作主要包括:檢索、插入、刪除、更新。
這個定義與現有資料庫存在很大差異,但並不影響作為這部分內容的開端。從上面的定義中我們可以看到,資料庫中記錄資訊的表與建立在表上的操作是密不可分的。另外,常見的庫操作有四種:檢索、插入、刪除、更新。
1.2 遭遇異常
在這個原始的概念驅使下,很多人就開始了資料庫的設計曆程,讓我們來看這樣一個例子:
【要求】:構建一用來描述在校學生的資料庫,要求記錄的學生如下屬性:學號、姓名、年齡、系別。
很多人會覺得這太簡單了,在上面的資料庫概念的指引下,我們可以很容易的給出如下設計:
表 1-1 初始的資料庫設計
學號 |
姓名 |
年齡 |
系別 |
1 |
張三 |
20 |
經管系 |
2 |
李四 |
22 |
機械繫 |
3 |
王五 |
21 |
經管系 |
4 |
趙六 |
23 |
自動化 |
該設計很好的將學號、姓名、年齡、系別屬性記錄了下來,並且支援檢索、插入、刪除、更新操作。然而這是不是一個完美的設計呢?在討論之前讓我們先回答幾個問題(儘管很多人對以下幾個問題嗤之以鼻)。
問題1:學校有幾個系?(答:3個)
問題2:經管系有幾個學生?(答:2個)
每次講到這裡,總有很多學生對此不屑一顧,當我問到"你們是怎麼知道的?"時候,很多人只是說"看出來的唄。"其實這兩個問題不是用"看出來"就可以解釋清楚的。最好的回答應當是"數出來的"(暈!)。問題1的答案是通過去掉系別列中的重複行後,數一數剩餘的行數得到的。問題2的答案是通過數一數系別為經管系的行數得到的。那好,我們先把這兩種解題方法放在這裡以備後面查閱。
讓我們再來看看建立在該表上的幾種操作,檢索已經說過了,這裡不再提,我們看看插入、刪除以及更新操作,這裡有如下幾個要求:
1、國家剛剛審批通過允許學校開設一個新的系"藝術系",然而藝術系的學生要等到兩個月後才能招進來。
2、李四畢業了,不再是"在校學生"了,將其刪除。
3、經管系現要更名成"經濟管理學院"。
呵呵,我們已經開始遭遇"插入異常"、"刪除異常"、"更新異常"了。如何插入一個沒有學生的系呢?這是一個兩難的問題。由於學校有藝術系,為了能夠在檢索"學校有幾個系"時檢索到4,我們不得不插入一行,該行的系別欄位記錄上"藝術系",而藝術系沒有學生,所以我們還得讓學號、姓名、年齡欄位空著(如表 1 2所示)。
表 1-2 插入藝術系
學號 |
姓名 |
年齡 |
系別 |
1 |
張三 |
20 |
經管系 |
2 |
李四 |
22 |
機械繫 |
3 |
王五 |
21 |
經管系 |
4 |
趙六 |
23 |
自動化 |
(空) |
(空) |
(空) |
藝術系 |
此時如果我再問"藝術系有幾個學生"時,恐怕有些人的臉色就不那麼好看了吧。這回通過數一數系別為藝術系的行數就無法得到準確的藝術系人數答案了。因此解題邏輯不得不也發生變化:如果人數為1,判斷學號、姓名、年齡是否為空白,如果為空白則為0人,否則為1人。你覺得這回的檢索還那麼簡單嗎?
再來看刪除操作也不是想刪就刪了。如果你把李四一行刪除,你會驚奇的發現機械繫沒有了!所以也不得不修改刪除邏輯,如果某系只剩下最後一條記錄,就不能刪除了,而起清空學號、姓名、年齡欄位的內容,這麼做好嗎?
更新操作似乎也存在一些問題。在上面的設計種,經管系更名需要修改兩行資料,假設剛剛修改完第一行,正要修改下一行,停電了,死機了,反正機器無法正常運轉。當你下次開機後數一數有幾個系呢?
1.3 解決異常
上面的問題有沒有解決的辦法呢?有!資料庫正常化理論給出了我們解決的辦法(關於第一範式、第二範式、第三範式等內容可以參考《資料庫原理》),那就是"拆分"。我們可以通過將上表拆分成如下兩表的形式解除異常:
表 1-3 學生庫
學號 |
姓名 |
年齡 |
系別代號 |
1 |
張三 |
20 |
1 |
2 |
李四 |
22 |
2 |
3 |
王五 |
21 |
1 |
4 |
趙六 |
23 |
3 |
表 1-4 系別庫
系別代號 |
系別 |
1 |
經管系 |
2 |
機械繫 |
3 |
自動化 |
這兩張表通過系別代號關聯起來,保留了原有資訊,但該設計消除了上面提到的異常。讀者可以自行嘗試在當前設計下重新執行上面的檢索、插入、刪除與更新操作,看看還有沒有異常發生?
1.4 資料表述與認知矛盾
從上面的例子我們可以看到,為了消除在資料庫操作過程中出現的插入異常、刪除異常以及更新異常,我們必須利用資料庫正常化理論對資料庫的設計加以規範處理。但如果校長希望你提供一份學生名單時,你提供給校長的該是什麼呢?毫無疑問,校長最希望見到的是表 1-1,而絕非表 1-3與表 1-4。因此這就引出了新的問題:資料庫的使用者與資料庫的設計者在對資料的認知上往往是不一致的。資料庫的使用者希望看到的是直觀、易懂的資料,而資料庫的設計者希望設計出來的方案易於修改、便於擴充,容易進行系統開發。因此,真正的資料庫設計必須很好的解決這兩者之間的矛盾。現代的資料庫都提供了眾多的方法解決這個問題(將在1.1.5中給予說明)。我們這裡不妨再看一個認知矛盾的例子。
假如需要通過資料庫記錄一樹型結構的資料(就像Windows中的檔案夾),我們如何來用二維表格加以描述呢?使用者當然希望看到的是一棵樹,而資料庫設計者面臨的問題是如何將樹二維表格化。這個工作我們可以通過如下的映射關係實現:
圖 1-1 需要表述的樹型結構
表 1-5 使用二維表格表述樹形結構
ID |
ParentID |
FoderName |
1 |
1 |
Program Files |
2 |
1 |
AC3Filter |
3 |
2 |
pic |
4 |
1 |
ACD Systems |
5 |
4 |
ACDSee |
6 |
4 |
FotoCanvas |
7 |
4 |
FotoSlate |
我們可以看到,表 1-5利用ID欄位與ParentID欄位描述了檔案夾間的父子關係,進而通過二維表格記錄下了樹型結構的內容。
總之,資料認知矛盾是不可避免的,通過各種辦法,我們總能讓資料庫的使用者與資料庫的設計者在資料表述問題上相互平衡。我們至少可以通過兩種途徑解決這種認知矛盾,一方面可以藉助現有資料庫提供的功能(例如視圖等,見1.1.5)解決,另一方面我們可以設計專門的資料結構解決(例如上面樹型結構記錄的問題)。
1.5 資料庫該是什麼樣?
既然在1.1.1中給出的資料庫定義存在很大的問題,那麼一個真正的資料庫應當是什麼樣的呢?我們需要資料庫為我們提供什麼樣的服務?這些服務又是通過什麼形式表現出來呢?
要說清楚這一點,還是讓我們從"在校學生"這個例子談起。上面分析到為了消除各種異常,我們通過拆分的方式將原有設計一分為二,兩表間通過系別代號相互關聯。儘管異常沒有了,但也帶來了一系列麻煩。
1.5.1 視圖
首先,資料表述上的認知矛盾使得校長在得到他的報表前不得不對資料進行一下處理,重新將兩表"縫合"起來。如果教務處想瞭解每個系的學生人數時,就又需要另外一種資料"縫合"方式。為了讓眾口不再難調,資料庫中引入了"視圖"的概念,允許使用者從不同的角度觀察資料,得到自己想要的結果。"橫看成嶺側成峰",山(資料庫的實體設計方案)永遠不會改變,通過不同的視角(視圖),我們可以看到不同的資料表現形式。這就是視圖的一個非常重要的作用。當然視圖提供的功能遠不止這些,作為初始瞭解就先說到這種程度。
《資料庫原理》裡面提到的三級模式、兩級映象其實說的就是這層關係。我們拋開三級模式中的內模式(物理實現),單看外模式與模式。模式只有一個,那就是資料庫設計者設計出來的表,而外模式可以有多個,每個外模式可以理解成一個"視圖",想怎麼看就怎麼看( 1-2)。這麼做的同時也實現的資料的"獨立性",資料庫的設計者可以專心的設計資料庫邏輯結構,並將設計方案從不同視角的視圖中獨立出來。
圖 1-2 通過視圖解決認知矛盾
1.5.2 完整性條件約束
然而事情並不是象預期般進展順利,資料庫設計者在消除了各種異常的以及資料認知矛盾的困擾後,往往又陷入"資料不一致"的問題陷阱中。資料庫設計者會探索資料插入、刪除、更新操作不象想象中那樣隨心所欲,必須時刻小心的避開資料不一致的暗礁。
例如,一個不負責任的資料錄入人員很可能將下面兩條記錄輸入到資料庫中:系別表(系號:3,系別:藝術系);學生表(學號:4,姓名:和二,年齡25,系號:9)。
在這裡,新插入的藝術系與自動化系出現了相同的系號,而學生表與系別表靠此系號欄位進行關聯,於是問題就出來了,我們無法正常檢索藝術系有多少人,也無法判斷某系號為3的學生究竟是哪個系的。因此必須確保系別表中的系號能夠唯一標識某一行記錄。我們管這樣的欄位叫做"主鍵"(很不嚴格、不準確的定義,但我們可以暫時如此理解,詳細的定義可以參考《資料庫原理》)。主鍵的存在是為了確保實現"實體完整性" ,其取值有兩點要求:(1)不允許為空白;(2)不允許重複。因此,資料庫需要提供某種限制性策略防止違反"實體完整性"的事情發生。不同資料庫實現的手段各不相同,Visual FoxPro中我們可以在該欄位上建立一個主索引或候選索引,在SQL Server中,我們可以為索引添加唯一性限制式條件等。
再來看看插入的學生記錄,我們給該學生的系號欄位輸入了數值9,而在系別表中根本沒有系號為9的系別,於是該學生便"無家可歸"了。為了防止類似事情發生,要求資料庫還要提供某些機制,確保學生表中的系號和系別表中的系號存在某種對應關係,防止不一致現象出現。這種機制是靠實現"參照完整性" 實現的。學生表(從表)中的系號(外鍵)需要參照系別表(主表)中的系號(主鍵)。
參照完整性中所指的主表與從表不一定非是兩個不同的表,也可以是同一張表。例如表 1-6:
表 1-6一張表內的參照完整性
學號 |
姓名 |
班長學號 |
1 |
張三 |
2 |
2 |
李四 |
2 |
3 |
王五 |
2 |
4 |
趙六 |
2 |
班長學號與學號間就構成了外鍵和主鍵的關係。班長學號的取值只能有兩種情況,(1)為空白,表示該班還沒有選出班長。(2)為學號欄位中的某個值,表示該班班長取自本班學生。
另外,在1.1.4資料表述與認知矛盾一節中,我們介紹了如何用二維表來表述一樹形結構,仔細觀察表 1-5,其中的ID欄位與ParentID欄位間也構成了主鍵與外鍵的關係,ParentID的取值必須來自ID列。
在實際應用過程中,參照完整性往往可以抽象歸納成幾條原則,這裡給出Visual FoxPro中的參照完整性條件約束供參考(以學生、系別表為例):
串聯刪除:如果刪除系別表中經管系,則自動將學生表中系別為經管系的學生刪除。
限制刪除:如果發現學生表中有學生是經管系的,則禁止刪除系別表中的經管系。
串聯更新:若更改系別表中的經管系系號,則學生表中對應經管系學生的系號也一併更改。
限制更新:如果學生表中有學生是經管系的,則禁止更改系別表中經管系的系號。
限制插入:禁止在學生表中插入一條記錄,該記錄的系號在系別表中沒有。
除了瞭解參照完整性條件約束條件外,我們還應當注意應用參照完整性後資料處理的先後順序。仍然以學生表和系別表為例子,假設使用者為兩表間建立了"限制刪除、限制更新、限制插入"的參照完整性規則,那麼當刪除經管系及所有學生時,要先刪學生表,再刪系別表,更新也是相同,當插入藝術系及其學生時,要先插入系別表再插入學生表。此時兩表操作的先後順序是不同的。請大家完成【實驗 1-1 Visual FoxPro中參照完整性的設定及資料操作順序】步驟1、2、3、4以加深理解。
實驗 1-1 Visual FoxPro中參照完整性的設定及資料操作順序
1.5.3 觸發器與預存程序
在實驗 1-1中,我們實現了在Visual FoxPro中設定參照完整性,到底是什麼神奇的力量使得串聯刪除得以實現的呢?在Visual FoxPro中是靠觸發器與預存程序實現的(不同資料庫使用的技術不盡相同)。觸發器通常分為插入觸發器、刪除觸發器和更新觸發器,通過對這些觸發器的設定可以確保在資料庫表執行插入、刪除、更新時自動執行某些代碼。Visual FoxPro中,這些代碼存貯在資料庫中,我們管它們叫做"預存程序"。我們暫時可以這樣理解預存程序:它是一段儲存在資料庫中、經過預先編譯的,可被外部直接調用的程式碼。隨著我們內容的逐步深入,對預存程序的認識還會進一步加深。請大家完成【實驗 1-1 Visual FoxPro中參照完整性的設定及資料操作順序】步驟5。
1.5.4 約束條件
上面我們學習了完整性條件約束中的實體完整性約束與參照完整性條件約束,還有一項叫做使用者定義完整性條件約束,顧名思義,就是使用者自行定義的完整性條件約束條件。在資料庫中通常提供了兩類使用者定義完整性條件約束,(1)欄位級完整性條件約束,(2)記錄級完整性條件約束。
在我們進行庫表的設計過程中,我們可以定義欄位類型、欄位長度與小數位元,但這對某些應用而言是不夠的。例如某欄位"年齡"。通常我們使用整形作為該欄位的資料類型,然而"-5"、"1000"都是整數,可顯然不是年齡,如何確保在欄位層級上設定完整性呢?這就靠欄位級完整性條件約束實現了。通過欄位完整性條件約束,我們可以設定年齡欄位中的資料必須是0到150之間的整數,以確保年齡資料的正確性。請大家完成【實驗 1-2 在Visual FoxPro中實現欄位級完整性條件約束】以加深對欄位完整性的認識。
實驗 1-2 在Visual FoxPro中實現欄位級完整性條件約束
欄位完整性僅僅對某個欄位的資料進行完整性校正,如果多個欄位中存在相互關聯,欄位級約束就顯得力不從心了。例如某表中包含著"年齡"和"工齡"兩個欄位,年齡的欄位約束為0~150間的整數,工齡的約束條件是0~60間的整數。我們完全可以插入一條記錄,年齡為10而工齡為30。儘管這些資料並不違反單獨某個欄位上的約束條件,但一個10歲的小孩怎麼會有30年工齡呢?為了保證欄位間資料完整一致,就得靠記錄級完整性條件約束實現了。通過設定約束條件"工齡<=年齡-18"就可以確保這兩個欄位間資料的一致性。請大家完成【實驗 1-3 在Visual FoxPro中實現記錄級完整性條件約束】以加深對欄位完整性的認識。
實驗 1-3 在Visual FoxPro中實現記錄級完整性條件約束
1.5.5 資料庫交易處理
上面說了很多關於資料庫應該具備的功能,也許有人早就新存疑惑了:"在參照完整性中有個概念叫串聯更新,也就是如果把系別代號改了,學生表中對應的系別代號也自動改,問題是如果改到一半時停電了,那豈不是資料出現不一致問題,更新異常又來了嗎?"。
確實如此,如果沒有一套有效機制防止類似事情發生,資料庫仍然面臨很大的威脅。現有的資料庫基本都支援"事務",那麼什麼是事務呢?簡單的說,就是確保"同時成功則成功,任何一個失敗則失敗"的一種機制。一個事務往往包括三種動作行為:開始事務(Begin Transaction),提交事務(Commit)和復原(Rollback)。從開始事務到提交事務過程中所發生的一切資料庫修改要麼同時成功(被Commit,固化在資料庫中),要麼一個失敗,大家同時回複原有狀態(Rollback,資料庫回複到事務開始時的狀態)。一個典型的事務程式可能如下:
tx = BeginTransaction();
try
{
//修改系別表
//修改學生表
tx.Commit();
}
catch
{
tx.Rollback();
}
當執行BeginTransaction時,我們可以認為是給資料庫照了張快照,記錄目前狀態。然後開始更新系別表與學生表,如果都能正確執行,則提交修改,快照也就沒有什麼用處了。但如果其中任何一個表出現更新異常,程式將落入catch段中,我們在這裡對資料庫進行復原,還原到快照時的狀態。
關於事務以及資料更新的更詳細的內容將在【第2章 並行作業的一致性問題】中有更詳細的介紹。
1.5.6 索引與主鍵
索引是資料庫中一個非常重要的概念,它的作用就如同新華字典前的檢字表一樣,可以提高我們的檢索效率。而主鍵的作用是用來唯一表示表中的某一行,知道了某主鍵就能夠根據它唯一定位一條記錄,就如同一張表的社會安全號碼一樣。(待完善)