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

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