在企業數據庫設計中,經常會遇到一個需求,就是希望把操作之前的數據保留下來,能夠看到操作之前是什么數據,操作之后是什么數據。對于這種需求,我們可以使用保留歷史數據或者使用版本來實現。
為了能夠保留歷史數據,在版本設計時有以下方案:
?
一、使用版本號
版本號是一種常見的版本設計方案,就是在要進行歷史數據保留的表上面增加一個版本號字段,該字段可以是DateTime類型,也可以是int類型,每進行數據操作時,都是創建一個新的版本,版本是只增不減的,所以只需要拿到最大一個版本號,就能得到最新的業務數據。
版 本號除了能夠用于留存歷史數據外,還有一個功能就是避免并發編輯操作。比如我們有一個對象A,當前的版本是1,兩個用戶同時打開了該對象的編輯頁面,進行 數據更改。先是甲用戶提交更改,這個時候系統把對象的ID和版本進行查詢,發現要修改的數據最新版本是1,所以成功修改,保存了對象A的新版本2。這個時 候用戶乙也提交了修改。系統把對象的ID和版本1進行查詢,發現要修改的數據最新版本是2,不符合要求,所以拒絕用戶乙的修改。用戶乙只有刷新界面,拿到 最新的版本2,再進行修改。
ID | 單號 | 金額 | 版本號 |
1 | EXP123 | 100 | 1 |
在使用版本號的情況下,對單據的金額進行修改,修改后創建新的版本號2:
ID | 單號 | 金額 | 版本號 |
1 | EXP123 | 100 | 1 |
2 | EXP123 | 120 | 2 |
二、使用生效、失效時間
保存歷史數據的第二辦法是使用生效失效時間來表示一個版本。要進行歷史數據記錄的表增加“生效時間”“失效時間”兩個字段,兩個字段不允許為空。對于剛創 建的數據,生效時間是創建該數據的時間,失效時間是9999-12-31?,F在對這條數據進行了修改,那么我們只需要將當前時間設置為上一個版本的失效時 間,同時創建一條新數據,生效時間是當前時間,失效時間是9999-12-31即可。
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 失效時間即可。
使用前兩種方案都需要一個業務主鍵來標識具體的一個業務數據。如果我們要記錄的實體沒有明確的“單號”、“訂單號”這類的業務主鍵該怎么辦?我們可以使用創建數據時的數據庫主鍵作為業務主鍵。
員工ID | 姓名 | 生日 | 業務ID | 版本號 |
1 | 張三 | 1984/12/29 | 1 | 1 |
比如我們有個員工表,記錄員工基本信息,在創建張三這個員工的數據時,其在數據庫的ID為1,那么可以將其業務ID也設置為1。接下來對張三的屬性進行更改,記錄了版本,那么就會創建新的版本,其主鍵“員工ID”會變化,但是其業務主鍵“業務ID”始終是1,不會變化的。
員工ID | 姓名 | 生日 | 業務ID | 版本號 |
1 | 張三 | 1984/12/29 | 1 | 1 |
2 | 張三 | 1985/1/9 | 1 | 2 |
使 用前面兩個方案雖然能夠很好的記錄歷史數據,但是每次修改數據都會導致新版本生成保存,所以每個版本的ID都是新的,所以必須有一個業務主鍵來標識一個實 體,這里的兩個例子“單號”就是其業務主鍵。主鍵的變動使得所有關聯的對象都得變動,從而形成連鎖效應,使得各個關聯的對象也生成新的版本。比如我們有個 訂單系統,里面有訂單表和訂單明細表?,F在我們要對訂單的修改記錄歷史版本,所以增加了生效時間和實效時間,并使用訂單號作為業務主鍵。現在有一個訂單 A,下面有100條明細,如果要對訂單進行修改,將某一條明細的屬性進行修改,從而導致整個訂單的變化,那么我們就需要創建新的訂單數據行,由于主鍵變 動,所以訂單明細都需要變動,所以100條明細都需要創建新的版本,新版本的訂單明細中,“訂單ID”指向了新的版本的訂單數據的ID。
這樣的設計造成的問題就是訂單明細表會極速膨脹,如果一個訂單有1000條明細,我們只是修改了訂單本身的屬性,并不修改訂單明細,也會造成對這1000條明細做Copy,然后保存。那怎么辦呢?我們可以使用以下辦法:
1.對訂單明細建立版本字段,將版本的粒度細化到訂單明細,而不是訂單。訂單與訂單明細不存在數據庫級的外鍵關系,只存在業務級的外鍵關系。也就是說訂單明細表中增加生效時間、失效時間之外,還需要增加“訂單號”這個字段,用于表名該明細是屬于哪個訂單的。
我 們這么修改后,如果訂單對象進行了修改,訂單明細沒有修改(比如改了一下收件人信息),那么只需要在訂單表中生成新的一行數據,訂單明細不會Copy生成 新的數據。如果我們對某一條訂單明細進行了更改(比調整了單價、數量)那么只需要對具體修改的那條訂單明細進行更改,而不需要對整個訂單的所有明細進行更 改。
使用這種設計后,查詢訂單及其明細,需要對兩個表執行生效失效時間的過濾,而且明細的獲取是通過訂單號去取,而不是通過訂單ID去取。
將版本控制的粒度細化到訂單明細時,后臺程序的邏輯也會更加復雜。用戶在界面上操作的是訂單對象,系統會將整個修改后的訂單對象傳到后臺,后臺程序需要對每個訂單項進行對比,如果發現訂單項進行了修改,那么就會調用生成新版本訂單明細的方法。
2.使用單獨的歷史表
這是另外一種實現歷史版本記錄的方法:
三、使用單獨的歷史表
使 用歷史表其實就是建立完全相同Schema的表(當然,也可以添加更多的字段用于記錄額外的歷史版本信息),該表只保留歷史版本的數據。這有點像一個歸檔 邏輯,所有歷史版本我們認為都應該是不經常訪問的,所有可以扔到單獨的表,對于現有生效的版本,仍然保留在原表中,如果需要查詢歷史版本,那么就從歷史表 中查詢。
使用單獨的歷史表有以下好處:
- 業務數據表的數據量不會因為歷史版本記錄而膨脹。因為歷史數據都記錄到了另外一個表中,所以業務數據表只記錄了一份數據。
- 業務數據表的Schema不需要調整,增加額外的版本字段。由于對原有數據表不做Schema變更,所以原有查詢邏輯也不用更改。對于一個現有的數據庫設計,在增加歷史數據記錄功能時更簡單。
- 業務數據表可以直接進行update操作,不會生成新的ID。由于ID不會變,所以我們并需要業務主鍵應用到程序邏輯中。
使 用歷史表記錄歷史版本主要是要對數據操作方法(增加、刪除、修改)進行修改,使得每次數據操作時,先在歷史表中留痕,然后再進行數據操作。另外就是對查詢 歷史版本功能進行修改,因為歷史數據在另外一個表中,所以對于的SQL是不一樣的。當然,我們也可以創建歷史版本數據庫,里面保存了所有的歷史表。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
