第五章 詳細設計階段的數據庫結構設計
詳細設計階段包括:數據庫設計、模塊設計和界面設計。
下面我們探討一下數據庫設計的有關問題。
第一節 關係數據庫的結構設計
一、面向過程的設計與實體關係圖
關係數據庫的單元是表,在面向過程的設計中,建立一個關係數據庫的第一步,需要仔細考慮實體-關係圖(ERD),給每個實體建立一張表,每張表的數據域要與已經定義的實體相一致。然後,可以爲每個表建立一個主鍵,如果沒有合適的字段作爲主鍵,可以自己創造一個,主鍵的數據必須是唯一的。
1)實體
實體指的是某些事物,企業需要存儲有關這些事物的數據。實體實例表達的是實體的具體值。
2)屬性
實體的描述特形稱爲屬性,某些屬性可以邏輯上被組合,稱爲組合屬性,它在不同的數據建模語言中也被稱作串聯屬性、合成屬性或者數據結構。
3)域
於是屬性的一個參數,定義了這個屬性所能定義的合法值。事實上這個值和使用的數據庫特點有關。
4)標識符
然後考慮實體之間的關係,關係基數符號如下:
以此可以建立表與表之間的關係:
實體之間的關係有三種,最簡單的而且最有代表性的例子也就是學生、老師、鎖櫃和書的關係。
也就是一對一(1:1)或一對多(1:N)或多對多(N:N)關係。
比如:
Student和Locker之間可以是一個一對一關係,因爲每個學生都有一個相應的鎖櫃,而每個鎖櫃只給一個學生使用。Student和Book之間可以有一個一對多關係,因爲每個學生可以有多本書,但每本書只歸一個學生所有。Student和Teacher之間有一個多對多關係,每個學生可以有多個教師授課,而每個教師又可以爲多個學生講課。
注意,一個實體可以參與多個關係,而每個關係可以有不同的對應關係。
這裏還有一個問題,就是多對多關係需要增加一個關聯實體,比如如下的多對多關係:
我們會發現,學生某門課的成績應該放在什麼地方呢?儘管模型中表達了學生選修了某門課程,但沒有放置這門課程成績的地方,所以需要增加一個關聯實體。
這樣,我們就可以得到設計關係數據庫的一般步驟:
1,爲每個實體類型建立一張表。
2,爲每張表選擇一個主鍵(如果需要,可以定義一個)。
3,增加外部碼以建立一對多關係。
4,建立幾個新表來表示多對多關係。
5,定義參照完整性約束。
6,評價模式質量,並進行必要的改進。
7,爲每個字段選擇適當的數據類型和取值範圍。
二、執行參照完整性
建立關係主要需要建立主鍵和外鍵的關係,執行參照完整性表達了外鍵和主鍵間一致的狀態。執行參照完整性表達的是:一個一致的關係數據庫狀態,每個外鍵的值必須有一個主鍵值與之對應。
規則:
1,當建立一個包含外鍵的記錄的時候,應確保主表中相應的主鍵值要存在。
2,當刪除一條記錄的時候,要確保所有相應外鍵的記錄也被刪除。
3,當更改一個主鍵值的時候,要確保所有相應表外鍵值也跟隨改變。
三、評價模式質量
一個高質量的數據模型應該具備以下特點,
1,表中每行數據及主鍵是唯一的。
2,冗餘數據較少。
3,容易實現將來數據模型的改變
不過,提高數據庫設計質量的方法有很多,但量化方法又很少,很大程度上依賴於設計師的經驗和判斷,下面提供幾個注意點。
1,行和關鍵字的唯一性
主鍵是能夠唯一定義一行數據的一列或者多列,主鍵中的列值不能爲null,主鍵爲數據庫引擎提供了獲取使據庫表中某個特定行的方法,主鍵還用於保證引用的完整性。如果多個用戶同時插入數據,則必須保證不會出現重複的主鍵。
由於主鍵是必須存在的,而主鍵是不可重複的,顯然表中的每一行也都是唯一的,這就是所有關係數據庫模型都有一個基本要求,那就是主鍵和表中的行是唯一的。
但我們應該如何來選擇主鍵呢?
智能鍵、常規建和代理鍵
智能鍵是一種基於商業數據表示的鍵,例如SKU(Stock Keeping Unit 常用保存單元)就是智能鍵的例子。它定義一個10個字符的字段(Char(10)),它的可能分配如下,前4個字符爲供應商代號,隨後3個字符保存產品類型代號,最後3個字符保存一個序列號。
常規鍵由現有商業數據中一個或多個列組成,比如社會保險號。
儘管智能鍵和常規鍵不盡相同,但他們都是用商業相關數據組成,下面統一成爲智能鍵。
代理鍵是由系統生成的,與商業數據無關,比如自動增值列(Identity),GUID(globally unique identifier 全局唯一代碼,16字符),這是通過取值算法得到的鍵值,後面將稱之爲GUID鍵。
下面的例子包含三張表(作者Author,書籍Book,而AuthorBook是一張多對多的連接表,因爲一個書籍可能由多個作者完成)。
注意,在代理鍵完成的時候,需要多增加一個列值,這是因爲代理鍵是系統自動生成,用戶不可見的。
數據大小
使用智能鍵或代理鍵數據的大小是不一樣的,因此一定要計算鍵的使用引起數據量的變化,顯然,基於int的自動增加列數據量最小,但也要注意,int的最大值是有限的,而且不便於移動數據,這些都是考慮的因素。
鍵的可見性
智能鍵是可見的而且是有意義的,而代理鍵一般不對用戶開放而且是無意義的,從維護的角度來說,似乎智能鍵更優,因此,如果智能鍵的數據確實存在,可以考慮智能鍵。
在非連接對象確保唯一性
在非連接對象中,兩個人同時加入行智能鍵重複的機率是存在的,但代理鍵不可能重複。而自動增加列值存在着諸多限制(移動性,最大值),所以GUID鍵是最合適的。
在數據庫中移動數據
自動增加列值事實上無法在數據庫中移動數據,智能鍵除非仔細設計,逐漸重複也不是沒可能,比較好的是GUID。
使用的方便性
自動增加鍵是最方便的,但由於上面種種討論,並不推薦使用,智能鍵需要多列組成,但方便性可以接受,GUID的使用往往叫人不太習慣,但合理的設計以後,這並不是問題,所以GUID主鍵還是最常用的。
2,數據模型的靈活性
在關係型數據庫最早的規範當中,數據庫的靈活性和可維護性是最主要的目標。如果對數據庫模式進行更改,對已經存在的數據內容和結構造成的影響最小,那麼就可以認爲這個關係數據庫模型是靈活的而且是可維護的。
比如,增加一個新的實體,不需要對原有的表進行重新定義。增加一個新的一對多關係,只要求給已存在的表添加一個外部碼。增加一個新的多對多關係,只需要在模式中添加一個單獨的新表。
在判斷數據模型的靈活性的時候,要特別注意屬於冗餘的影響,一般來說,數據存在多個地方,那麼在進行增、刪、改、查操作的時候會增加額外的操作,而且維護上也更復雜和低效,在操作失敗的時候,數據不一致的危險性也會更大。
多表關聯的時候,外鍵是必須的,但這樣也會造成關鍵字段操作的複雜性。
關係型數據庫管理系統通過參照完整性的約束來保證主鍵和外鍵一致,但並沒有自動化的方法來保證冗餘數據項的一致,爲了避免關鍵字段的數據冗餘,可以採用數據庫的規範化。
數據庫規範化是用來評價關係數據庫模式質量的有效技術,它可以確定一個數據庫模式是不是包含了任何錯誤冗餘,它基本的表述如下:
第1範式(1NF):沒有重複子段和字段組的數據庫表結構。
函數相關:兩個字段值之間一一對應。如果對於任意子段B的值有而且只有一個A的值與之對應,則稱A函數相關B。
這裏主要研究的是字段內容,保證表中數據沒有允於餘。
第2範式(2NF):每個非關鍵字段對於主鍵或者主鍵組函數相關。
這裏主要研究其它字段與關鍵字段的關係
第3範式(3NF):各個非關鍵字段之間不能函數相關。
這個範式保證了數據沒有冗餘。
下面對這些概念作一些解釋。
設想有這樣的實體,並以此構造了兩張表。
第一範式:
這是對錶格行定義的一個結構限制,事實上關係數據庫管理系統本身就是在一張表中拒絕由兩個相同的字段的,所以要實現第一範式並不困難。
函數相關:
這是一個比較難以描述以及應用的概念。
比如考察“產品項目”表中“編號”和“標準價格”兩個字段,現在已知“編號”是一個內部主鍵,在表中一定是唯一的,爲了判斷“標準價格”是不是函數相關“編號”,只需要描述:對於字段“編號”的值有而且只有一個“標準價格”的值與之對應,則“標準價格”函數相關“編號”。
我們來考察一下對於“產品項目”表這個表述對不對?事實上只要字段“編號”數據是唯一的,這個表述就是正確的,也就是“標準價格”函數相關“編號”。
一個不太確切但很簡單的方式如下,“產品項目”表中對於每個產品產品價格應該是唯一的,這就是函數相關的,如果每個產品有多種價格,這就不是函數相關的。
第二範式:
爲了判斷“產品項目”表是不是屬於第二範式,我們必須首先判斷它是不是第一範式,因爲它不包括重複的字段,所以它是屬於第一範式,然後我們需要判斷每個非關鍵字段都函數相關與“標準價格”(也就是每個字段都來替換函數相關定義中的A),如果每個非關鍵字段都函數相關與“標準價格”,那“產品項目”表就是屬於第二範式。
當主鍵是由兩個或者多個字段組成的時候,判斷表是不是第二範式就比較複雜,例如考慮如下的“目錄產品”表,這個表示爲了表示“銷售目錄”表和“產品項目”表之間的多對多關係。所以,表達這個關係的表的主鍵由“銷售目錄”的主鍵(“目錄號”)和“產品項目”的主鍵(“產品號”)組成,這個表還包含了一個非關鍵字段公佈價格。受市場影響,公佈的價格往往是浮動的。
如果這張表屬於第二範式,那麼非主關鍵字段“公佈價格”必定函數相關於“目錄號”與“產品號”組合。我們可以通過替換函數相關定義中的詞語來驗證函數相關:
對於“目錄號”與“產品號”組合的值有而且只有一個“公佈價格”的值與之對應,則“公佈價格”函數相關“目錄號”與“產品號”組合。
分析這樣的語句正確性還是需要技巧的,因爲你必須考慮“產品目錄”表中所有所有可能出現的關鍵字值得組合。比較簡單的分析的方法不是機械的對照,而是考慮這個實體本身的問題。
一個產品可能在多個不同的銷售目錄中出現,如果在不同的目錄中它的價格不同,那麼上述的陳述就是正確的。如果產品不論在哪個目錄中,它的價格是相同的(或者說一個價格數據對應於多個目錄),那上述的陳述就是錯誤的,而且這個表不是第二範式。所以正確的判斷不是依賴於陳述,而往往是依賴於對問題本身的理解。
如果非關鍵字段只是函數相關於主鍵組的一部分,那麼這個非關鍵字段必須從當前表中移出去,並且放在另一個表中。
例如如下的“目錄產品”表,增加了一個目錄的“發佈日期”字段,顯然,這個字段函數相關於“目錄號”,但不函數相關於“產品號”,也就是同一個“產品號”可能在多個“發佈日期”中使用,這就會造成冗餘,這個表不屬於第二範式。
正確的做法是把“發佈日期”移出來,放在“銷售目錄”這張表中,這時候就正確了。
要判斷一張表是不是第三範式,則必須考慮每一個非關鍵字段是不是函數相關於其它的非關鍵字段,如果是,就要考慮結構上的修正。
對於一個大型表來說,這實際上是一個非常複雜的工作,而且工作量隨着字段數的增加而快速增。當非關鍵字段數是N時,要考慮的函數相關數目爲N*(N-1)。而且要注意函數相關要兩方面考慮(即A相關B,B相關A)。
我們來考慮下面一個簡單的“職工狀況”表。
這張表有三個非關鍵字段,所以需要考慮六個函數相關:
“部門”與“住址”?一個住址可能有多個部門的人,不是。
“住址”與“部門”?一個部門相同住址的可能不止一個,不是。
“部門編號”與“住址”?一個住址可能牽涉到的部門編號是多個,不是。
“住址”與“部門編號”?一個部門編號有相同住址的可能不止一個,不是。
“部門編號”與“部門”?一個部門只有一個部門編號,是。
“部門”與“部門編號”?一個部門編號只對應一個部門,是。
注意,有時候兩方面考慮只有一個是,比如“省份”和“郵政編碼”,一個省份有多個郵政編碼,而一個郵政編碼只能對應一個省份。
這樣一來,當部門有多個人的時候就會出現大量的冗餘,解決的辦法是再增加一張表,表達“部門”和“部門編號”的對應關係。
如果出現了需要幾個字段數據計算出結果的字段,也不屬於第三範式,可以把這個字段取消,由調用方通過程序來解決。
第二節 面向對象數據庫設計
一、模式:把數據對象表表示成類
把對象表示成表(Representing Objects as Tables)的模式建議,在RDB中對每一個持久化對象類定義一個表,對象的原始數據類型(數字、字符串、布爾值)的屬性映射爲列。
如果對象只有原始數據類型的屬性,就可以直接映射。但對象如果還包含了其它複雜對象的引用屬性,事情就比較複雜,因爲關係型模型需要的值是原子性的(第一範式),所以除非有充分的例有,儘可能不要這樣來做。
在UML中,雖然可以很好的表達類,但是,爲了確切的表達數據,還需要有一些擴展,這個關於數據建模的擴展標準,已經提交給了OMG組織。
比如:PK:主鍵(primary Key);
FK:外建(foreign Key)。
等。
注意,如果僅僅把對象表達成類,和基於結構的思維實際上一樣的,面向對象的數據表達最大的特點是數據具有繼承性,這是和麪向過程的設計完全不同的地方。也就是說,對象數據庫設計的時候,和關係數據庫很大的區別,在於類可以實現繼承,比如如下的例子。
這爲我們優化系統提供了更大的思維空間。
二、持久化對象
目前常用的存儲機制主要由兩種.。
對象數據庫:
如果用對象數據庫來存儲和檢索對象,就不需要第三方持久化服務,這是使用對象數據庫吸引人的地方之一。
關係數據庫:
由於RDB(關係型數據庫)的流行,通常我們遇到的數據庫都是RDB 而不是更方便的ODB,這樣一來,面向記錄和面向對象的數據表示之間會有一系列的問題。往往我們會需要一個O-R映射服務。
其它:
出RDB以外,有時候我們還會使用其它存儲機制(XML結構、層次數據庫等)來存儲數據,同樣也需要有某種服務,來使這些機制和對象一起協調工作。
絕大多數應用,都需要從一個持久化存儲機制(例如關係型數據庫)存儲和檢索信息。
通常更好的辦法,是購買或者獲得工業的持久化框架,而不是自己開發。
開發工業級的數據庫持久化O-R(對象關係映射)服務需要數人年的時間,其中許多細節問題需要專門的專家。
三、解決方案:來自持久化框架的持久化服務
持久化框架(persistentce framework)是多用途的、可重用的和可擴展的一組類型,它提供了支持持久化對象的功能。
持久化服務(persistentce service)(或子系統)實際上是提供了這種服務,而且使用持久化框架來提供這種服務。
一般來說,持久化服務是屬於技術服務層的子系統。
典型的比如:Hibernate。
這種持久化服務需要關注下面兩個問題。
1、使用外觀模式訪問持久化服務
我們已經討論過,外觀模式是爲子系統提供一個統一的接口,事實上,通過給定ID,就可以返回一個對應的(代表一行)的對象。這裏的外觀可以是一個單件模式。
2、使用模板方法設計框架
是用模板方法,也可以實現持久類根據數據變化。
至少在目前關係型數據庫還是非常流行的情況下,O-Rmaping還是非常有意義的,因爲這樣一來,設計的時候就可以專注於對象的特點,可以使用抽象和泛化的方法來處理問題。
六、數據庫的關聯設計
在設計數據庫系統的時候,必須作出如下重要決策:
應該加載多少數據?
在涉及多個關聯表的時候,如何更新數據?
實現何種類型主鍵(最重要的決策問題)?
我們下面將逐一進行討論。
設想我們有一個簡化的訂單數據庫,該數據庫包含5個關聯表。
1)應加載什麼數據
數據選擇:
應該只加載用戶需要處理的非連接數據,幾乎所有的情況下只需要獲取數據庫的一個數據子集。
數據量:
數據量的選擇會影響加載時間、更新時間、以及內存需求量。記住,非連接對象是一個基於內存的對象。因此要注意所獲取的數據量大小,如果不能確信獲取這些數據是必要的,就不要獲取它。
分割數據:
根據數據對象的使用目的,最好把數據分割成多個部分,並分別存入相應的本地數據集對象。
例如,當用一個非連接對象保留數據的時候,這個對象會包括所有5個表。
再仔細分析一下,首先假定主體是銷售部門,銷售部門的主要關心點是客戶:
針對特定客戶,一個客戶在Customer(消費者)和Address(地址)表只有一行信息,但是Order(訂單)表和OrderItem(訂單項)卻可以包含多行與該客戶相關的信息(一個客戶可以有多個訂單)。這樣,我們可以把訂單和客戶信息放在一個DataSet裏面(Customer DataSet)。
如何處理Product(產品)表呢?不同的情況可能對Product表的要求是不一樣的。
如果假設本地數據集對象包含該客戶在某個時刻的全部數據,則Product表可能只包含與OrderItem表相關的行。
同樣,如果希望能夠爲不同的產品增加更多的訂單,又可能必須保證Product表是完整的,因此,可能需要再次將該表存入原先其所屬的本地數據集對象中。這樣就能獨立的在客戶之間傳遞產品列表。
我們希望能夠刪除Product表中不再使用的產品,但不強制刪除它們在OrderItem表中的引用。
由於情況比較複雜,我們可以專門設置一個本地數據集(Product DataSet)來裝載Product表。也就是使用一個本地數據集保留客戶數據,另一個本地數據集保留產品數據。
記住,不能在不同的本地數據集對象的數據表之間建立外鍵約束。但可以很容易的通過OrderItem表的ProductId定位Product數據,如下圖所示。
在下載數據的順序上,首先決定Order的要求,再由它的Id決定下載哪些OrderItem內容,以及由CustomerId決定下載哪些Customer和Address。
由OrderItem的ProductId決定下載哪些Product。
這樣就可以免掉許多無效數據的下載。
當然,上面這些討論並不是規則,需要具體情況具體分析。
第三節 併發問題及其應對
數據訪問的一個挑戰性問題,就是多個用戶可能同時訪問數據庫同一個數據。比如一個用戶正在編輯數據,另一個用戶正在利用這個數據形成報表。
這就要注意兩個問題:
1)防止兩個用戶同時編輯數據;
2)防止報表顯示不完整的數據。
併發是使多個用戶訪問數據庫,並得到一個相互一致的數據視圖的能力。通過鎖定必須的行、表或數據庫,防止用戶訪問可能不一致的數據。
例如:
假定小張正在貸方帳戶A和借方帳戶B之間進行一項轉賬業務。
而此時小李正在帳戶A中提取資金,則小張看到的帳戶A的餘額應該是多少呢?如果小張的事務還沒有完成,如何處理小李的事務呢?
解決方案:
在一個事務還沒有完成的時候,數據庫服務器通過鎖定數據庫來接決這樣的問題,在小張的事務還沒有完成的時候,小李必須等待小張的事務完成以後才能進行操作,我們的目標是儘可能快地處理事務,並使鎖定等待的時間儘可能短。
但是,我們如果使用非連接對象,把一部分數據庫數據複製到客戶段,等修改後再發回數據庫,而把這個期間作爲一個完整的事務,就會產生嚴重的鎖定問題。
正是這種情況,迫使非連接對象獲得數據的時候不啓動一項事務(因而不會鎖定),但是在提交數據的時候,檢查和處理多個更新操作引起的衝突。
設想有一個表“賬務”,包含字段爲編號、姓名、金額。
把數據讀入DataTable表以後,DataRow對象將包括Current版本和Original版本數據。
更新命令如下:
Sql="UPDATE 賬務 SET 姓名 = @姓名, 金額 = @金額
WHERE (編號 = @Original_編號) AND (姓名 = @Original_姓名 OR @Original_姓名
IS NULL AND 姓名 IS NULL)
AND (金額 = @Original_金額 OR @Original_金額 IS NULL AND 金額 IS NULL);
SELECT 編號, 姓名, 金額 FROM 賬務 WHERE (編號 = @編號)";
這條命令的Where子句指出,只有在數據庫當前列值與原來的列值都相同的時候,纔會執行這條命令,這可能是解決併發衝突最安全、最容易的辦法。此外還要注意,在提交一行數據的時候,所有的列值都將改變。
這時,小張修改金額,提交應該沒有問題
但小李修改姓名,提交發生併發錯誤
但這樣的策略是不是正確呢?畢竟兩個人改的不是同一列數據,這樣處理起來比較簡單(一行數據全部改變),如果希望修改這個行爲,可以改變Sql語句。
解決併發問題的策略
如何解決併發衝突是一個商業決策,下面列出了一些主要決策原則:
l 時間優先:第一次更新優先,也稱爲“時間順序優先”,只保留第一次更新的結果,上面的例子實現了這個策略。
l 時間優先:最後一次更新優先,也稱爲“逆時間順序優先”,只保留最後一次更新結果,這個方法最簡單,因爲可以把Where子句的內容全部去掉。
l 角色優先:賣方人員優於買方人員,因爲賣方人員更瞭解產品。這個方式實現起來難度較大,因爲必須知道每個用戶的角色,而且如果角色相同,還是應該保留實現順序優先作爲備用機制.
l 位置優先:位置優先,總店優於分店,這個方式必須知道角色位置,事實上權限往往決定了位置,所以可以用權限優先來代替這個策略,同樣也需要用順序優先作爲備用機制。
l 用戶解決衝突:當發生衝突的時候,彈出一個衝突解決界面,由用戶決定下一步處理方式,這種方式雖然直觀,事實上並不常用,而且出現問題的機率也比較大。
一般來說,對於顧客數據可採用角色優先策略,對於賬目數據可採用位置優先策略。
第四節 處理事務
一、爲什麼要關注事務處理
執行事務事務是一組組合成邏輯工作單元的操作,雖然系統中可能會出錯,但事務將控制和維護事務中每個操作的一致性和完整性。
例如,在將資金從一個帳戶轉移到另一個帳戶的銀行應用中,一個帳戶將一定的金額貸記到一個數據庫表中,同時另一個帳戶將相同的金額借記到另一個數據庫表中。由於計算機可能會因停電、網絡中斷等而出現故障,因此有可能更新了一個表中的行,但沒有更新另一個表中的行。如果數據庫支持事務,則可以將數據庫操作組成一個事務,以防止因這些事件而使數據庫出現不一致。如果事務中的某個點發生故障,則所有更新都可以回滾到事務開始之前的狀態。如果沒有發生故障,則通過以完成狀態提交事務來完成更新。
在一個操作中,如果牽涉到多個永久存儲,而且是多步完成,並且需要修改數據的時候,就一定要考慮加上事務處理。
二、事務處理的基本概念
事務是一個原子工作單位,必須完整地完成其中的所有工作,如果提交事務,則事務執行成功,如果終止事務,則事務執行失敗。事務具備以下4個關鍵屬性:原子性、一致性、孤立性和持久性,這被稱之爲ACID屬性。
l 原子性:事務的工作不能劃分爲更小的部分,儘管其中包括了多條Sql語句,但它們要麼全部執行,要麼都不執行。這意味着出現一個錯誤的時候,將全體恢復到啓動事務前的狀態。
l 一致性:事務必須操作一致性的視圖,並且必須使數據處於一致的狀態,事務再提交之前,它的工作絕不會影響到其他的事務。
l 孤立性:事務必須是獨立運行的實體,一個事務不會影響到其它正在執行的事務。
l 持久性:在提交一個事務的時候,必須永久性的存儲它,以免發生停電或系統失敗丟失事務。在重新供電或者恢復系統以後,將只會恢復已經提交的事務,而退回沒有提交的事務。
1)併發模型和數據庫鎖定
數據庫使用數據庫鎖定機制來防止事務互相影響,以實現事務的一致性和孤立性,事物在訪問數據的時候,將強制鎖定數據,而其它要訪問的事物將強制處於等待狀態,這說明長時間的運行事務是不可取的,這會嚴重影響系統性能和可測量性。這種用鎖定來阻止訪問的做法,稱爲“悲觀的”併發模型。
在“樂觀的”併發模型中,將不使用鎖,而是檢查數據在讀取之後是不是發生了變化,如果發生了變化,則拋出一個異常,由商業邏輯進行恢復,前面DataAdapter的UpDate方法就是使用了這種模型,但它無法解決我們在前面應行的例子中出現的問題。
2)事務的孤立級別
實現完全孤立的事務當然好,但代價太高,完全孤立性要求只有在鎖定事務的情況下,才能讀寫任何數據,甚至鎖定將要讀取的數據。
根據應用程序的目的,可能並不需要實現完全的孤立性,通過調整事務的孤立級別,就可以減少使用鎖定的次數,並提高可測量性和性能,事物孤立性將影響下列操作:
l Dirty讀取操作:該操作能夠讀取還沒有提交的數據,在退回一個添加數據的事務的時候,該操作會造成大問題。
l Nonrepeatable讀取操作:該操作指一個事務多次讀取同一行數據的時候,另一個事務可以在第一個事務讀取數據期間,修改這行數據。
l Phantom讀取操作:該操作指一個事務多次讀取同一個行集的時候,另一個事務可以在第一個事務讀取數據期間,插入或刪除這個行集中的行。
下表列出了典型數據庫中的孤立級別。
級別
|
Dirty
讀取操作
|
Nonrepeatable
讀取操作
|
Phantom
讀取操作
|
併發模型
|
Read Uncommitted
|
是
|
是
|
是
|
無
|
Read committed With Locks
|
否
|
是
|
是
|
“悲觀的”
併發模型
|
Read committed With Snapshots
|
否
|
是
|
是
|
“樂觀的”
併發模型
|
Repeatable Read
|
否
|
否
|
是
|
“悲觀的”
併發模型
|
Snapshot
|
否
|
否
|
否
|
“樂觀的”
併發模型
|
Serializable
|
否
|
否
|
否
|
“悲觀的”
併發模型
|
下面對各個級別進行討論:
l Read Uncommitted級別:其它事務的修改情況將對某個事務的查詢造成影響,如果設置爲該級別,則在讀取該數據的時候,即不會獲取鎖,也不願意使用鎖。
l Read committed With Locks級別:這是Sql Server的默認設置,已提交的更新在事務間是可見的,長時間運行的查詢,不需要實時保持一致。
l Read committed With Snapshots級別:已提交的更新在事務間是可見的,如果設置爲該級別,則不會獲取鎖。但行數據的版本信息將用於跟蹤行數據的修改情況,長時間運行的查詢需要實時保持一致,該級別將帶來使用版本存儲區的開銷,版本存儲區可以提高吞吐量,並且減少對鎖的依賴。
l Repeatable Read級別:在一個事務中,所有的讀取操作都是一致的,其它事務不能影響該事務的查詢結果,因爲在完成該事務並取消鎖定之前,其它事務一直處於等待狀態。該級別主要用在讀取數據後希望同一個事物中修改數據的情況。
l Snapshot級別:在需要精確的執行長時間運行的查詢和多語句事務的時候,如果不準備更新事務,則使用該事務。使用這個級別的時候,不會獲取讀取操作的鎖,以防止其它事務修改數據,因爲在讀取快照(Snapshot)並提交修改數據的事務之前,其它事務看不到修改情況,數據可以在該級別事務中進行修改,但是在快照事務(Snapshot transaction)啓動後,可能和更新相同數據的事務發生衝突。
l Serializable級別:在所訪問的行集上放置一個範圍鎖,這是一個多行鎖,在完成事務之前,防止其它用戶更新數據集或者在數據集中插入行。在事務的生命週期中,保持數據的一致性和正確性,這是孤立級別中的最高級別,因爲這個級別要使用大量的鎖,所以只是在必要的時候才考慮使用這個級別。
在提交Update或者Delete語句後,版本存儲區將保存行版本記錄,直到提交所有的活動記錄位置,事實上,在提交或者結束下列事務類型之前,版本存儲區將一直保存行版本記錄。
l 在Snapshot孤立級別下運行的事務。
l 在Read committed With Snapshots孤立級別下運行的事務。
l 在提交事務之前啓動的所有其它事務。
三、使用容錯恢復技術
在準備產品化應用程序的時候,如何知道數據庫是不是能夠經受衆多用戶對應用程序反覆的使用呢?如果數據庫服務器關機,會出現什麼情況呢?如果數據庫服務器需要快速重啓,會出現什麼情況呢?
首先,在停止和重啓服務器的情況下,我們可以清除連接池並重新建立它。
但如何才能保證結果和服務器關閉之前完全相同呢?
這就要用到容錯恢復技術了。
請看下面的場景:
使用三臺數據庫服務器,“主服務器”、“鏡像服務器”、“觀察者服務器”,使用數據庫鏡像的時候,客戶只和主服務器聯繫,鏡像服務器處於數據恢復狀態(不能進行任何訪問),當向主服務器提交一個事務的時候,也會把這個事物發給鏡像服務器。
觀察者服務器只是觀看主服務器和鏡像服務器是不是正在正常工作。
在主服務器關機的時候,觀察者自動把鏡像服務器切換爲主服務器,見下面兩張圖。
注意,當發現主服務器有問題的時候,則自動清除連接池,並轉而使用備用服務器。
第五節數據庫結構設計案例
我們用前面討論的“電源設備銷售客戶服務子系統”作爲例子,來具體分析一下數據庫設計的有關問題,這個案例的說明見於第三章第八節綜合案例的研究,表達了TB公司電源設備銷售部爲了實現信息技術戰略規劃(ITSP)構思的銷售服務系統,我們用這個案例來具體說明一下數據庫的設計方法。
一、獲取實體
仔細研究需求分析文檔,從而可以獲取實體,注意實體要按照業務詞彙來定義,而不要使用技術詞彙來定義,例如,爲了簡化問題,這裏分析上面項目的訂單和促銷活動部分,在這一部分裏,訂單和促銷活動聯繫在一起,而其它的購買方式暫時不予考慮。在這樣的情況下,本項目定義的實體如下。
實體名稱
|
業務定義
|
合同
|
合同是記錄客戶購買產品的狀況,當購買產品達到一定數額的時候,可以得到規定比例的返點。
|
基本客戶
|
基本客戶是目前比較活躍的客戶,一是公司必須關注的客戶羣體,促銷活動主要針對的是這樣的客戶羣。
|
客戶訂單
|
基本客戶發出的訂單記錄,
|
事務
|
客戶服務系統必須響應的一個業務事件。
|
產品
|
可以用於銷售的電源產品。
|
促銷
|
由市場部組織的促銷活動,在促銷期間,對於不同類型的客戶可以提供不同的產品價格。
|
二、領域數據模型
利用我們已經討論過的領域數據模型(早期稱作上下文數據模型),我們可以根據對業務的理解,建立包括業務實體類和它們之間的自然關係,也就是構造瞭如下關係。
整個過程關鍵是對於業務的理解,沒有對於業務的理解系統的分析和設計簡直是不可能的,下面我們做一些說明:
1,合同綁定一個或多個基本客戶,反之一個基本客戶只綁定一個合同。
2,一個客戶執行零個、一個或者多個事務,反之一個給定事務只能一個客戶執行。
3,一個客戶訂單是一個事務,事實上一個客戶訂單也可能是多個事務(新增訂單、刪除訂單、修改訂單等),反之一個事物只能對於對應一個訂單。
4,一個促銷包用於打包推廣一個或者多個產品,反之一個產品可能在一個或者多個促銷包中存在,注意這是個多對多關係。
5,一個促銷包會產生多個客戶訂單,反之一個訂單爲零個或者一個促銷包。零個的原因在於,有的客戶不符合促銷打包的條件,將在另外的系統中解決。
6,如果不同的關係溝通不同的業務事件和關聯,那麼兩個實體間允許存在多個關係。比如對於促銷包所產生的訂單,一個客戶可以響應零個、一個或多個訂單。另外,客戶也可以主動發出零個、一個或者多個訂單。當然這樣的關係也可以直接寫出“發出 or 響應”來表達。
7,一個訂單銷售一個或多個產品,反之一個產品可以有零個、一個或者多個客戶,注意這是個多對多關係。
這樣一個數據模型構建過程,事實上也加深了分析人員對於業務模型的理解,這是非常有意義的。
三、基於鍵的數據模型
一旦領域模型被構造起來,我們就可以考慮給模型建立主鍵,我們已經討論了關於主鍵的有關問題,這裏需要說明的,一般來說盡可能用簡單的單一屬性作爲主鍵。
對於多對多關係,需要增加一個關聯表。
四、概化層次體系的數據模型
如果促銷政策比較複雜,可以考慮把促銷商品通過泛化實現層次體系。
五、具有完整屬性的數據模型以及規範化分析
體系結構完成以後,可以考慮寫出屬性,每個屬性對應一個字段。由於這個例子太複雜,而且詳細寫出屬性並不能給我們提供更多的知識,所以這裏就不再討論了。
然後必須進行規範化分析,也就是根據第1範式(1NF),函數相關,第2範式(2NF),第3範式(3NF)對錶結構和數據進行檢查,修改其中的不合理成分,這樣數據庫分析和設計可以告一段落。
上面的分析過程不論是面向過程還是面向對象,方法上幾乎是相同的。