第五章 詳細設計階段的資料庫結構設計 詳細設計階段包括:資料庫設計、子產品設計和界面設計。 下面我們探讨一下資料庫設計的有關問題。 第一節 關系資料庫的結構設計 一、面向過程的設計與 實體關系圖 關系資料庫的單元是表,在面向過程的設計中,建立一個關系資料庫的第一步,需要仔細考慮實體-關系圖(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)對表結構和資料進行檢查,修改其中的不合理成分,這樣資料庫分析和設計可以告一段落。 上面的分析過程不論是面向過程還是面向對象,方法上幾乎是相同的。