天天看點

資料庫模型設計——曆史與版本設計

在企業資料庫設計中,經常會遇到一個需求,就是希望把操作之前的資料保留下來,能夠看到操作之前是什麼資料,操作之後是什麼資料。對于這種需求,我們可以使用保留曆史資料或者使用版本來實作。

為了能夠保留曆史資料,在版本設計時有以下方案:

一、使用版本号

版本号是一種常見的版本設計方案,就是在要進行曆史資料保留的表上面增加一個版本号字段,該字段可以是DateTime類型,也可以是int類型,每進行資料操作時,都是建立一個新的版本,版本是隻增不減的,是以隻需要拿到最大一個版本号,就能得到最新的業務資料。

版本号除了能夠用于留存曆史資料外,還有一個功能就是避免并發編輯操作。比如我們有一個對象A,目前的版本是1,兩個使用者同時打開了該對象的編輯頁面,進行資料更改。先是甲使用者送出更改,這個時候系統把對象的ID和版本進行查詢,發現要修改的資料最新版本是1,是以成功修改,儲存了對象A的新版本2。這個時候使用者乙也送出了修改。系統把對象的ID和版本1進行查詢,發現要修改的資料最新版本是2,不符合要求,是以拒絕使用者乙的修改。使用者乙隻有重新整理界面,拿到最新的版本2,再進行修改。

ID 單号 金額 版本号
1 EXP123 100

在使用版本号的情況下,對單據的金額進行修改,修改後建立新的版本号2:

2 120

二、使用生效、失效時間

儲存曆史資料的第二辦法是使用生效失效時間來表示一個版本。要進行曆史資料記錄的表增加“生效時間”“失效時間”兩個字段,兩個字段不允許為空。對于剛建立的資料,生效時間是建立該資料的時間,失效時間是9999-12-31。現在對這條資料進行了修改,那麼我們隻需要将目前時間設定為上一個版本的失效時間,同時建立一條新資料,生效時間是目前時間,失效時間是9999-12-31即可。

生效時間 失效時間
2013/9/1 15:30:00 9999/12/31 23:59:59

比如上面一條單據,是2013-9-1建立的,後來在2013-9-9 15:00:00對該單據進行修改,将金額從100修改為120,儲存時建立的新資料如下:

2013/9/9 15:00:00

使用了生效、失效時間後,我們可以查詢任意時刻資料庫中資料的值,隻需要把要查詢的時刻傳入,然後between 生效時間 and 失效時間即可。

使用前兩種方案都需要一個業務主鍵來辨別具體的一個業務資料。如果我們要記錄的實體沒有明确的“單号”、“訂單号”這類的業務主鍵該怎麼辦?我們可以使用建立資料時的資料庫主鍵作為業務主鍵。

員工ID 姓名 生日 業務ID
張三 1984/12/29

比如我們有個員工表,記錄員工基本資訊,在建立張三這個員工的資料時,其在資料庫的ID為1,那麼可以将其業務ID也設定為1。接下來對張三的屬性進行更改,記錄了版本,那麼就會建立新的版本,其主鍵“員工ID”會變化,但是其業務主鍵“業務ID”始終是1,不會變化的。

1985/1/9

使用前面兩個方案雖然能夠很好的記錄曆史資料,但是每次修改資料都會導緻新版本生成儲存,是以每個版本的ID都是新的,是以必須有一個業務主鍵來辨別一個實體,這裡的兩個例子“單号”就是其業務主鍵。主鍵的變動使得所有關聯的對象都得變動,進而形成連鎖效應,使得各個關聯的對象也生成新的版本。比如我們有個訂單系統,裡面有訂單表和訂單明細表。現在我們要對訂單的修改記錄曆史版本,是以增加了生效時間和實效時間,并使用訂單号作為業務主鍵。現在有一個訂單A,下面有100條明細,如果要對訂單進行修改,将某一條明細的屬性進行修改,進而導緻整個訂單的變化,那麼我們就需要建立新的訂單資料行,由于主鍵變動,是以訂單明細都需要變動,是以100條明細都需要建立新的版本,新版本的訂單明細中,“訂單ID”指向了新的版本的訂單資料的ID。

資料庫模型設計——曆史與版本設計

這樣的設計造成的問題就是訂單明細表會極速膨脹,如果一個訂單有1000條明細,我們隻是修改了訂單本身的屬性,并不修改訂單明細,也會造成對這1000條明細做Copy,然後儲存。那怎麼辦呢?我們可以使用以下辦法:

1.對訂單明細建立版本字段,将版本的粒度細化到訂單明細,而不是訂單。訂單與訂單明細不存在資料庫級的外鍵關系,隻存在業務級的外鍵關系。也就是說訂單明細表中增加生效時間、失效時間之外,還需要增加“訂單号”這個字段,用于表名該明細是屬于哪個訂單的。

資料庫模型設計——曆史與版本設計

我們這麼修改後,如果訂單對象進行了修改,訂單明細沒有修改(比如改了一下收件人資訊),那麼隻需要在訂單表中生成新的一行資料,訂單明細不會Copy生成新的資料。如果我們對某一條訂單明細進行了更改(比調整了單價、數量)那麼隻需要對具體修改的那條訂單明細進行更改,而不需要對整個訂單的所有明細進行更改。

使用這種設計後,查詢訂單及其明細,需要對兩個表執行生效失效時間的過濾,而且明細的擷取是通過訂單号去取,而不是通過訂單ID去取。

将版本控制的粒度細化到訂單明細時,背景程式的邏輯也會更加複雜。使用者在界面上操作的是訂單對象,系統會将整個修改後的訂單對象傳到背景,背景程式需要對每個訂單項進行對比,如果發現訂單項進行了修改,那麼就會調用生成新版本訂單明細的方法。

2.使用單獨的曆史表

這是另外一種實作曆史版本記錄的方法:

三、使用單獨的曆史表

使用曆史表其實就是建立完全相同Schema的表(當然,也可以添加更多的字段用于記錄額外的曆史版本資訊),該表隻保留曆史版本的資料。這有點像一個歸檔邏輯,所有曆史版本我們認為都應該是不經常通路的,所有可以扔到單獨的表,對于現有生效的版本,仍然保留在原表中,如果需要查詢曆史版本,那麼就從曆史表中查詢。

使用單獨的曆史表有以下好處:

  • 業務資料表的資料量不會因為曆史版本記錄而膨脹。因為曆史資料都記錄到了另外一個表中,是以業務資料表隻記錄了一份資料。
  • 業務資料表的Schema不需要調整,增加額外的版本字段。由于對原有資料表不做Schema變更,是以原有查詢邏輯也不用更改。對于一個現有的資料庫設計,在增加曆史資料記錄功能時更簡單。
  • 業務資料表可以直接進行update操作,不會生成新的ID。由于ID不會變,是以我們并需要業務主鍵應用到程式邏輯中。

使用曆史表記錄曆史版本主要是要對資料操作方法(增加、删除、修改)進行修改,使得每次資料操作時,先在曆史表中留痕,然後再進行資料操作。另外就是對查詢曆史版本功能進行修改,因為曆史資料在另外一個表中,是以對于的SQL是不一樣的。當然,我們也可以建立曆史版本資料庫,裡面儲存了所有的曆史表。

原文位址:https://www.cnblogs.com/studyzy/p/3310266.html