1. 程式人生 > >實現資料邏輯刪除的一種方案

實現資料邏輯刪除的一種方案

## 什麼是邏輯刪除 所謂邏輯刪除是指資料已經“不需要”了,但是並沒有使用delete語句將這些資料真實的從資料庫中刪除,而只是用一個標誌位將其設定為已經刪除。 ![](https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3340871784,1186932952&fm=26&gp=0.jpg) ## 為什麼需要邏輯刪除 對資料進行邏輯刪除,一般存在以下原因: - 防止資料誤刪除,不能找回資料; - 這些資料還具有一定的商業價值,比如使用者的註冊資訊; - 雖然這些資料可以刪除,但是這些資料還有關聯資料,這些關聯資料不能刪除。 對資料進行邏輯刪除,可以保證資料的安全性和完整性。但是,邏輯刪除也會帶來的一些問題: - 資料庫表的資料冗餘,導致查詢緩慢; - 寫sql進行資料處理時需要排除那些已經邏輯刪除的資料,這就會導致sql複雜,容易出錯,特別是涉及多表查詢時; - 進行邏輯刪除時,還需要考慮與之相關的資料怎麼處理; - 還有,如果資料表的某個欄位要求唯一,並強制約束,比如使用者表中的登入使用者名稱欄位,設計為邏輯刪除的話,一旦有新的同用戶名記錄就無法插入。但如果不將該欄位設定為唯一性約束的,那麼在每次插入資料的時候,都需先進行一次查詢,看看有無未(邏輯)刪除的同名記錄存在,低效率是一回事,而且在**高併發的系統中,很難保證其正確性**。 所以是否需要對資料進行邏輯刪除,需要根據具體的業務場景,以及邏輯刪除的優缺點進行綜合考慮。 網友的一些建議 > 綜合考慮,對於中小型的專案,邏輯刪除所帶來的好處有限,但帶來的問題卻很多。如果平時做好資料備份工作,還是可以預防物理刪除隱患的。但心裡應該清除,當專案大到一定程度,對資料安全性的要求高到一定程度,使用邏輯刪除代替物理刪除是必然的,在後面的資料庫設計中,可以先小範圍的嘗試使用邏輯刪除,一旦開發模式成熟,就全面使用邏輯刪除代替物理刪除。 ## 邏輯刪除怎麼設計 **設計方案一:在表中加一個欄位deleted欄位** deleted欄位的值為0表示資料未刪除,值為1表示資料已經刪除。 插入資料資料時,這個值預設為0。刪除資料時將這個值設定為1。查詢和更新資料時都將‘deleted=0’這個條件帶上,只查詢和更新沒有刪除的資料。 這個方案比較簡單,但是會有些問題。比如說你表中的一個欄位`user_name`設定了唯一性約束,但是如果你只是進行了邏輯刪除的話,相同的`user_name`就不能進行資料插入了。 但如果不將該欄位設定為唯一性約束的,那麼在每次插入資料的時候,都需先進行一次查詢,看看有無未(邏輯)刪除的同名記錄存在,低效率是一回事,而且在**高併發的系統中,很難保證其正確性**。 ![](https://upload-images.jianshu.io/upload_images/13147067-870a719f56e68a12.png?imageMogr2/auto-orient/strip|imageView2/2/w/1128) 然而你的服務運行了一段時間後你還是發現了資料庫中存在 name = a 且 is_delete = 0 的多條欄位,大部分是由於以下原因(併發問題): ![](https://upload-images.jianshu.io/upload_images/13147067-e60f8e993c1b3a4d.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200) 這個問題有下面兩個解決方案: 解決方案1:為資料庫新增新的一列delete_token,當某一條記錄需要刪除時,將該欄位設定為一個UUID,將name、delete_token設定為唯一鍵,這樣當is_delete=0時,delete_token保持一個預設值,能夠有效地限制name唯一,當記錄被刪除時,由於delete_token是一個唯一的UUID,便能保證刪除的記錄不會被唯一約束束縛。但正如該文章的博主所說,UUID會佔用很大的空間,所以不推薦使用。評論網友針對該問題提出優化對策:將刪除記錄的delete_token設定為該記錄的id。 > 個人認為,索引太大隻是其中一個弊端,該方法還會面臨一個很棘手的問題:當需要批量刪除時,需要對每一條記錄進行逐行刪除。例如該表還有一個欄位叫age,現在需要刪除age > 18的記錄,共有50條,在業務中,由於需要為每條的delete_token欄位插入一個UUID所以需要將其拆分為50條更新操作來進行。這樣的代價顯然很難接受。 解決方案2:將刪除標記設定預設值(例如0),將唯一欄位與刪除標記新增唯一鍵約束。當某一記錄需要刪除時,將刪除標記置為NULL。 由於NULL不會和其他欄位有組合唯一鍵的效果,所以當記錄被刪除時(刪除標記被置為NULL時),解除了唯一鍵的約束。此外該方法能很好地解決批量刪除的問題(只要置為NULL就完事了),消耗的空間也並不多(1位 + 聯合索引)。 **設計方案一:表備份** 將刪除的資料備份到其他備份表再進行刪除。如果有級聯資料,也需要進行刪除備份。不然資料的完整性就不存在了。 ## 使用MyBatis-Plus實現邏輯刪除 這邊,我們使用MyBatis-Plus的邏輯刪除功能來實現下上面介紹的方案一。 MyBatis-Plus(簡稱MP)是對MyBatis的增強,可以完全相容MyBatis的原生功能,而且幾乎可以省略單表操作的所有增刪改查方法,大大提升了開發效率。詳細的使用方式可以參考[官網](https://baomidou.com/guide/#%E7%89%B9%E6%80%A7) 下面就來介紹下,MP的邏輯刪除功能。 step1:進行配置 ```yaml mybatis-plus: global-config: db-config: # 全域性邏輯刪除的實體欄位名(since 3.3.0,配置後可以忽略不配置步驟2) # logic-delete-field: flag # 邏輯已刪除值(預設為 1) logic-delete-value: 1 # 邏輯未刪除值(預設為 0) logic-not-delete-value: 0 ``` step2: 添加註解 ```java @TableLogic() @TableField(select = false) private Integer deleted; ``` step3: 使用 ```java @Test public void apiTest(){ // UPDATE test.user SET deleted=1 WHERE user_id=? AND deleted=0 logger.info("開始邏輯刪除"); int count = userDAO.deleteById(356); // SELECT * FROM test.user WHERE user_id=? AND deleted=0 logger.info("開始查詢"); User user = userDAO.selectById(357); // UPDATE test.user SET user_name=?, telephone_no=?, id_card_no=?, identity_type=?, sex=?, birth_date=?, marital_status=?, asset_code=?, asset_branch_code=?, issuing_authority=?, job_type=?, address=?, work_unit=?, create_time=? WHERE user_id=? AND deleted=0 logger.info("開始更新"); userDAO.updateById(user); } ``` MP的邏輯刪除功能使用起來非常簡單。但是需要我們注意以下幾點: - 開啟邏輯刪除功能後,MP在刪除、查詢和更新時會自動加上條件`deleted=0`,也就是隻對沒有刪除的資料進行操作; - 雖然MP對開啟邏輯刪除的表的插入操作沒什麼限制,但是還是建議在建表時,對`deleted`欄位做預設限制,預設為0(未刪除),插入資料時這個值可以不用設定; - **對於自己在xml檔案中定義的介面方法,MP是不會自動對其開啟邏輯刪除功能的,需要我們自己維護邏輯刪除功能**; - 查詢: 追加where條件過濾掉已刪除資料,且使用 wrapper.entity 生成的where條件會忽略該欄位; 下面是使用 QueryWrapper 進行查詢時的sql,我們發現前面的`deleted=0`條件會讓後面我們自己加的deleted條件失效 ```sql SELECT * FROM test.user WHERE deleted=0 AND (user_id = ? AND deleted = ? AND user_name = ?) ``` - 追加where條件防止更新到已刪除資料,且使用 wrapper.entity 生成的where條件會忽略該欄位,原因和上面的原因是一樣的。 ## 參考 - https://www.jianshu.com/p/0d3ce8aed8b5 - https://zhuanlan.zhihu.com/p/303458410