數據庫設計中的Soft Delete模式

  最近幾天有點忙,因此咱們今天來一篇短的,簡單地介紹一下數據庫設計中的一種模式——Soft Delete。數據庫

  能夠說,該模式譭譽參半,甚至有很是多的人認爲該模式是一個Anti-Pattern。所以在本篇文章中,咱們不只僅會對該模式進行介紹,同時也會列出該模式可能致使的一系列問題,以幫助你們正確地決定是否使用該模式。編程

 

Soft Delete簡介數據庫設計

  首先先來想一個需求,那就是對用戶操做的回滾支持。例如我如今正在用Word編寫這篇文章。當我執行了一個錯誤操做的時候,我僅僅須要鍵入Ctrl + Z就能夠進行回滾。而在有些Web應用中,咱們一樣須要這種功能。函數

  例如Rally是一個Web應用,以用來在軟件開發過程當中對進度和任務進行管理。在任務管理功能中,每次對任務的建立,修改以及刪除都會被記錄在系統中。性能

  如今問題來了,若是須要支持回滾,那麼系統該如何記錄一條已經被用戶刪除了的任務呢?最直觀的想法就是在數據庫中添加一列deleted來記錄該任務是否已經被刪除:this

 1 @Entity
 2 class Task extends … {
 3     private boolean deleted;
 4     ……
 5     private boolean isDeleted() {
 6         return deleted;
 7     }
 8 
 9     private void setDeleted(boolean deleted) {
10         this.deleted = deleted;
11     }
12 }

  若是一個任務被刪除了,那麼它的deleted將爲true。也就是說,在用戶刪除一個任務的時候,系統實際上並無將該任務完全地從數據庫中刪除,而僅僅是經過deleted來標示其已經被刪除了。而在恢復該任務的時候,只須要將deleted設置爲false便可。編碼

  OK,這就是有關Soft Delete模式的介紹。是否是很簡單?但也正是由於它很是簡單,進而致使了對它的濫用,從而使它成爲了一個不少人眼中的Anti-Pattern。這種事情在IT技術中發生的還真是很多。最簡單的就是Java的Checked Exception。的確它是一個好的功能,讓使用Java編程變得更加嚴謹。可是過度的濫用致使不少類庫都將用戶徹底沒法處理的異常暴露在了類庫接口中,反而使不少軟件開發人員養成了直接忽略全部異常的壞習慣:spa

1 try {
2     obj.someFunction();
3 } catch (Throwable e) {
4 }

  相信讀者已經看出了這麼作的危害:catch甚至將表示系統錯誤的Error類型實例都抓住了。但這裏不能忽略的一個事實是,當軟件開發人員對某些行爲無能爲力,那麼他極有可能忽略某些編碼準則,而首先選擇使用一種能讓系統在正確運行的狀況下工做起來的方法。例如對於上面的函數調用obj.someFunction(),若是其拋出的異常和類庫內部運行邏輯相關,並且每次均可能致使這種問題,那麼軟件開發人員就極有可能使用上面的代碼忽略掉該異常。這種問題甚至在一些廣爲使用的類庫中存在着。例如OData4j曾經把取得OData元數據時產生的全部異常都看成是目標服務沒有暴露元數據的狀況來處理。設計

  OK,說得有點遠了。總結起來就是,一旦一個技術過於簡單並且可以處理某個狀況,那麼軟件開發人員將不會仔細研究使用該技術所須要的語境,從而致使濫用。和Checked Exception同樣,Soft Delete也是這樣的一個例子。調試

 

Soft Delete的問題

  那麼該數據庫模式有什麼問題呢?簡單地說,那就是太容易出錯,並且是隱蔽的錯誤。試想一下,若是用戶須要列出全部的任務,那麼在SQL語句中就須要使用WHERE deleted = ‘N’這樣的條件。並且該條件幾乎在全部處理任務的SQL語句中都要出現。一旦在一個SQL語句中忘記了該條件,那麼這極有多是一個Bug,並且這種Bug有時候還很是隱蔽。例如若是在一個COUNT語句中忘記標示了該條件,並且系統中任務不少,可是被刪除的任務不多,那麼該Bug可能存在幾年都不會被發現。

  同時若是一旦決定須要在系統中大量地使用Soft Delete,那麼SQL將變得很是混雜。在統計功能中,咱們可能須要篩選出全部包含任務的用例的個數,甚至是包含這些用例的項目的個數,那麼咱們就須要在SQL中同時標明多個WHERE deleted = ‘N’的條件:

1 SELECT COUNT(*)
2 FROM project, story, task
3 WHERE … project.deleted = ‘N’ AND story.deleted = ‘N’ AND task.deleted = ‘N’

  那麼在調試這些語句的時候,或者查看這些語句的執行計劃以進行性能調優的時候,軟件開發人員都會發現因爲這些條件的引入致使SQL的執行變得很是複雜。

  還有一個問題就是,若是一個系統經常執行對記錄的軟刪除,那麼數據庫中所記錄的數據將比實際所須要記錄的數據多得多。這種垃圾數據可能會致使數據庫的索引變得很大,甚至可能會由此而嚴重影響數據庫的性能。

  另外一個問題則和級聯有關。數據庫提供了級聯操做,在刪除一個數據記錄的時候,數據庫會根據該數據與其它記錄之間的關聯關係來自動完成對其它關聯記錄的操做。這也是數據庫保持其數據完整性的一種方法。可是一旦用戶使用了Soft Delete,那麼在對其進行軟刪除的時候就不會將其從數據庫中移除,那麼與其關聯的那些記錄也便不會被數據庫移除。也就是說,軟件開發人員須要自行完成數據完整性的管理。除此以外,軟件開發人員還須要在數據訪問層(DAL,Data Access Layer)中完成事務的組織,而且一旦數據庫表的定義發生了變化,這些事務組織的邏輯也可能須要進行更改。

 

Soft Delete的實現

  也正是因爲Soft Delete擁有這麼多的問題,所以軟件開發人員們提出了不少Soft Delete的變通實現方法,大大地減小了開發和維護Soft Delete模式數據的成本。

  一種方法就是利用數據庫所提供的View功能。在該方法中,咱們須要在數據庫中建立一個View,以顯示錶中deleted值爲’N’的各行數據。而在對數據進行操做的各SQL語句中,咱們只須要直接操做該View,從而避免了每次都須要在SQL語句中標明WHERE deleted = ‘N’這種條件。

  而另外一種方法則是將這些數據分散到兩個不一樣的表中。這兩個表中的一個表記錄deleted值爲’N’的各行數據,而另外一個表則記錄已經被軟刪除的deleted值爲’Y’的各行數據。並且在具備兩個表的狀況下,系統甚至能夠很較爲容易地實現垃圾箱的功能。

  而在某些狀況下,咱們也能夠在數據庫設計中借鑑Soft Delete的思路。Soft Delete須要用戶自行管理數據庫中數據的關聯關係。這是一份額外的工做,但也帶來了更多的靈活性。

  Rally中的任務刪除操做的回滾天然沒必要多說,垃圾箱功能的添加也將變爲很是容易的事。而對於某些自定義的刪除邏輯,Soft Delete所帶來的靈活性將更爲突出。例如在Rally中,若是咱們須要實現「刪除用戶用例時若是用戶用例中包含任務,那麼這些任務將挪至父用例中」這樣一個需求,那麼咱們就能夠在Soft Delete的自定義刪除邏輯中完成該功能。

  好了,今天就到這裏。

相關文章
相關標籤/搜索