Design7:數據刪除設計
在設計一個新系統的Table Schema的時候,不僅需要滿足業務邏輯的復雜需求,而且需要考慮如何設計schema才能更快的更新和查詢數據,減少維護成本。
模擬一個場景,有如下Table Schema:
Product(ID,Name,Description)
在設計思路上,ID是自增的Identity字段,用以唯一標識一個Product;在業務邏輯上要求Name字段是唯一的,通過Name能夠確定一個Product。業務上和設計上有所沖突在所難免,解決沖突的方法其實很簡單:將ID字段做主鍵,並創建clustered index;在Name字段上創建唯一約束,保證Product Name是唯一的。
這樣的Table Schema 設計看似完美:ID字段具有做clustered index的天賦:窄類型,自增,不會改變;Name上的唯一約束,能夠滿足業務邏輯上的需求。但是,如果業務人員操作失誤,將Product 的 Name 寫錯,需要將其刪除,最簡單的方式是使用delete 命令,直接將數據行刪除,但是這種方式帶來的隱患特別大:如果業務人員一不小心將重要的數據刪除,那麽,恢復數據的成本可能非常高。如果數據庫很大,僅僅為恢復一條數據,可能需要N個小時執行還原操作。如何設計Table Schema,才能避免在維護系統時出現被動的情況?
delete Product where Name=‘xxx‘
設計目的:在短時間內恢復被誤刪除的數據,以使系統盡快恢復
在實際的產品環境中,數據刪除操作有兩種方式:軟刪除和硬刪除,也稱作Logic Delete 和 Physical Delete。硬刪除是指使用delete命令,從table中直接刪除數據行;軟刪除是在Table Schema中增加一個bit類型的column:IsDeleted,默認值是0,設置IsDeleted=1,表示該數據行在邏輯上是已刪除的。
Product(ID,Name,Content,IsDeleted,DeletedBy)
軟刪除實際上是一個Update 操作,將IsDeleted字段更新為1,在邏輯上將數據刪除,並沒有將數據行從物理上刪除。使用軟刪除,能夠保留有限的數據刪除的歷史記錄,以便audit,但是,這可能導致外鍵關系引用被邏輯刪除的數據;如果歷史記錄太多,這又會導致數據表中有效數據行的密度降低,降低查詢速度。
1,能夠快速恢復被誤刪除的數據
用戶的刪除操作是將IsDeleted設置為1,在邏輯上表示刪除數據,如果用戶由於誤操作,將重要數據行刪除,那麽只需要將IsDeleted重置為0,就能恢復數據。
update Product set IsDeleted=1 where Name=‘xxx‘ -- or use ID=yyyy as filter
2,每次引用該表時,必須設置filter
任何引用該表的查詢語句中,必須設置Filter:IsDeleted=0,為來避免遺漏filter,可以創建視圖,不直接引用該表,而是直接引用視圖。
--view definition select ID,Name,Content from Product where IsDeleted=0
3,手動處理外鍵關系
如果在該表上創建外鍵關系,那麽可能存在外鍵關系引用被邏輯刪除的數據,造成數據的不一致性,這可能是很難發現的bug:如果需要保持關鍵關系的一致性,需要做特殊的處理。在將數據行邏輯刪除之時,必須在一個事務中,將外鍵關系全部刪除。
4,不能被用作歷史表
數據表是用來存儲數據的,不是用來用戶操作的歷史記錄。如果需要存儲用戶操作的歷史記錄,必須使用另外一個HistoryOperation來存儲。
上述Product表中Name字段上存在一個唯一約束,如果用戶將相同Name的Product重新插入到table中,Insert 操作因為違反唯一約束而失敗,針對這種情況,軟刪除操作必須額外進行一次判斷:
if exists( select null from Product where name =‘xxx‘ and IsDeleted=1 ) update set IsDeleted=0, ... from Product where name =‘xxx‘ and IsDeleted=1 else insert Product(...) values(....)
如果Product表的數據量十分大,額外的查詢操作,會增加插入操作的延遲,同時,"無效"的歷史數據降充斥在數據表中,也會降低數據查詢的速度。
單純從業務需求上考慮,軟刪是首選的design,定期清理軟刪的冗余數據,也可以提高數據查詢的速度,不過,在清理數據時,可能會產生大量的索引碎片,造成並發性降低等問題。
5,將刪除的數據存儲到History表
使用軟刪除設計,增加IsDelete=1 字段,實際上降低了有效數據的密度,在使用軟刪除時,必須慎重考慮這一點。改進的刪除數據的設計是:在一個事務中,將刪除的數據存儲到另外一個History表中。
delete from Product output deleted.ID, deleted.Name, deleted.Content, ‘Delete‘ as CommandType ‘‘ as UpdatedBy, getdate() as UpdatedTime into History_table where Name =‘xxx‘ -- or use Id=yyy as filter
恢復誤刪的數據,只需要到History表找到相應的數據,將其重新插入到Prodcut 表中,並且,History 表中不僅可以存儲用戶刪除操作的歷史記錄,而且可以存儲用戶更新的歷史記錄,對於系統的維護,解決用戶糾紛和故障排除,十分有幫助。
Product(ID,Name,Content) OperationHistory(ID,ProductID,ProductName,ProductContent,CommandType,UpdatedBy,UpdatedTime)
為設計Product 表的刪除操作,需要兩個Table,對於OperationHistory表,可以做的更通用一些。拋磚引玉,提供一個思路,我就不做擴展了。
Design7:數據刪除設計