在企業數據庫設計中,常常會遇到一個需求,就是但願把操做以前的數據保留下來,可以看到操做以前是什麼數據,操做以後是什麼數據。對於這種需求,咱們可使用保留歷史數據或者使用版原本實現。數據庫
爲了可以保留歷史數據,在版本設計時有如下方案:併發
版本號是一種常見的版本設計方案,就是在要進行歷史數據保留的表上面增長一個版本號字段,該字段能夠是DateTime類型,也能夠是int類型,每進行數據操做時,都是建立一個新的版本,版本是隻增不減的,因此只須要拿到最大一個版本號,就能獲得最新的業務數據。數據庫設計
版本號除了可以用於留存歷史數據外,還有一個功能就是避免併發編輯操做。好比咱們有一個對象A,當前的版本是1,兩個用戶同時打開了該對象的編輯頁面,進行數據更改。先是甲用戶提交更改,這個時候系統把對象的ID和版本進行查詢,發現要修改的數據最新版本是1,因此成功修改,保存了對象A的新版本2。這個時候用戶乙也提交了修改。系統把對象的ID和版本1進行查詢,發現要修改的數據最新版本是2,不符合要求,因此拒絕用戶乙的修改。用戶乙只有刷新界面,拿到最新的版本2,再進行修改。spa
ID | 單號 | 金額 | 版本號 |
1 | EXP123 | 100 | 1 |
在使用版本號的狀況下,對單據的金額進行修改,修改後建立新的版本號2:設計
ID | 單號 | 金額 | 版本號 |
1 | EXP123 | 100 | 1 |
2 | EXP123 | 120 | 2 |
保存歷史數據的第二辦法是使用生效失效時間來表示一個版本。要進行歷史數據記錄的表增長「生效時間」「失效時間」兩個字段,兩個字段不容許爲空。對於剛建立的數據,生效時間是建立該數據的時間,失效時間是9999-12-31。如今對這條數據進行了修改,那麼咱們只須要將當前時間設置爲上一個版本的失效時間,同時建立一條新數據,生效時間是當前時間,失效時間是9999-12-31便可。3d
ID | 單號 | 金額 | 生效時間 | 失效時間 |
1 | EXP123 | 100 | 2013/9/1 15:30:00 | 9999/12/31 23:59:59 |
好比上面一條單據,是2013-9-1建立的,後來在2013-9-9 15:00:00對該單據進行修改,將金額從100修改成120,保存時建立的新數據以下:版本控制
ID | 單號 | 金額 | 生效時間 | 失效時間 |
1 | EXP123 | 100 | 2013/9/1 15:30:00 | 2013/9/9 15:00:00 |
2 | EXP123 | 120 | 2013/9/9 15:00:00 | 9999/12/31 23:59:59 |
使用了生效、失效時間後,咱們能夠查詢任意時刻數據庫中數據的值,只須要把要查詢的時刻傳入,而後between 生效時間 and 失效時間便可。對象
使用前兩種方案都須要一個業務主鍵來標識具體的一個業務數據。若是咱們要記錄的實體沒有明確的「單號」、「訂單號」這類的業務主鍵該怎麼辦?咱們可使用建立數據時的數據庫主鍵做爲業務主鍵。blog
員工ID | 姓名 | 生日 | 業務ID | 版本號 |
1 | 張三 | 1984/12/29 | 1 | 1 |
好比咱們有個員工表,記錄員工基本信息,在建立張三這個員工的數據時,其在數據庫的ID爲1,那麼能夠將其業務ID也設置爲1。接下來對張三的屬性進行更改,記錄了版本,那麼就會建立新的版本,其主鍵「員工ID」會變化,可是其業務主鍵「業務ID」始終是1,不會變化的。ci
員工ID | 姓名 | 生日 | 業務ID | 版本號 |
1 | 張三 | 1984/12/29 | 1 | 1 |
2 | 張三 | 1985/1/9 | 1 | 2 |
使用前面兩個方案雖然可以很好的記錄歷史數據,可是每次修改數據都會致使新版本生成保存,因此每一個版本的ID都是新的,因此必須有一個業務主鍵來標識一個實體,這裏的兩個例子「單號」就是其業務主鍵。主鍵的變更使得全部關聯的對象都得變更,從而造成連鎖效應,使得各個關聯的對象也生成新的版本。好比咱們有個訂單系統,裏面有訂單表和訂單明細表。如今咱們要對訂單的修改記錄歷史版本,因此增長了生效時間和實效時間,並使用訂單號做爲業務主鍵。如今有一個訂單A,下面有100條明細,若是要對訂單進行修改,將某一條明細的屬性進行修改,從而致使整個訂單的變化,那麼咱們就須要建立新的訂單數據行,因爲主鍵變更,因此訂單明細都須要變更,因此100條明細都須要建立新的版本,新版本的訂單明細中,「訂單ID」指向了新的版本的訂單數據的ID。
這樣的設計形成的問題就是訂單明細表會極速膨脹,若是一個訂單有1000條明細,咱們只是修改了訂單自己的屬性,並不修改訂單明細,也會形成對這1000條明細作Copy,而後保存。那怎麼辦呢?咱們可使用如下辦法:
1.對訂單明細創建版本字段,將版本的粒度細化到訂單明細,而不是訂單。訂單與訂單明細不存在數據庫級的外鍵關係,只存在業務級的外鍵關係。也就是說訂單明細表中增長生效時間、失效時間以外,還須要增長「訂單號」這個字段,用於表名該明細是屬於哪一個訂單的。
咱們這麼修改後,若是訂單對象進行了修改,訂單明細沒有修改(好比改了一下收件人信息),那麼只須要在訂單表中生成新的一行數據,訂單明細不會Copy生成新的數據。若是咱們對某一條訂單明細進行了更改(比調整了單價、數量)那麼只須要對具體修改的那條訂單明細進行更改,而不須要對整個訂單的全部明細進行更改。
使用這種設計後,查詢訂單及其明細,須要對兩個表執行生效失效時間的過濾,並且明細的獲取是經過訂單號去取,而不是經過訂單ID去取。
將版本控制的粒度細化到訂單明細時,後臺程序的邏輯也會更加複雜。用戶在界面上操做的是訂單對象,系統會將整個修改後的訂單對象傳到後臺,後臺程序須要對每一個訂單項進行對比,若是發現訂單項進行了修改,那麼就會調用生成新版本訂單明細的方法。
2.使用單獨的歷史表
這是另一種實現歷史版本記錄的方法:
使用歷史表其實就是創建徹底相同Schema的表(固然,也能夠添加更多的字段用於記錄額外的歷史版本信息),該表只保留歷史版本的數據。這有點像一個歸檔邏輯,全部歷史版本咱們認爲都應該是不常常訪問的,全部能夠扔到單獨的表,對於現有生效的版本,仍然保留在原表中,若是須要查詢歷史版本,那麼就從歷史表中查詢。
使用單獨的歷史表有如下好處:
使用歷史表記錄歷史版本主要是要對數據操做方法(增長、刪除、修改)進行修改,使得每次數據操做時,先在歷史表中留痕,而後再進行數據操做。另外就是對查詢歷史版本功能進行修改,由於歷史數據在另一個表中,因此對於的SQL是不同的。固然,咱們也能夠建立歷史版本數據庫,裏面保存了全部的歷史表。