實現數據邏輯刪除的一種方案


什么是邏輯刪除

所謂邏輯刪除是指數據已經“不需要”了,但是並沒有使用delete語句將這些數據真實的從數據庫中刪除,而只是用一個標志位將其設置為已經刪除。

為什么需要邏輯刪除

對數據進行邏輯刪除,一般存在以下原因:

  • 防止數據誤刪除,不能找回數據;
  • 這些數據還具有一定的商業價值,比如用戶的注冊信息;
  • 雖然這些數據可以刪除,但是這些數據還有關聯數據,這些關聯數據不能刪除。

對數據進行邏輯刪除,可以保證數據的安全性和完整性。但是,邏輯刪除也會帶來的一些問題:

  • 數據庫表的數據冗余,導致查詢緩慢;
  • 寫sql進行數據處理時需要排除那些已經邏輯刪除的數據,這就會導致sql復雜,容易出錯,特別是涉及多表查詢時;
  • 進行邏輯刪除時,還需要考慮與之相關的數據怎么處理;
  • 還有,如果數據表的某個字段要求唯一,並強制約束,比如用戶表中的登錄用戶名字段,設計為邏輯刪除的話,一旦有新的同用戶名記錄就無法插入。但如果不將該字段設置為唯一性約束的,那么在每次插入數據的時候,都需先進行一次查詢,看看有無未(邏輯)刪除的同名記錄存在,低效率是一回事,而且在高並發的系統中,很難保證其正確性

所以是否需要對數據進行邏輯刪除,需要根據具體的業務場景,以及邏輯刪除的優缺點進行綜合考慮。

網友的一些建議

綜合考慮,對於中小型的項目,邏輯刪除所帶來的好處有限,但帶來的問題卻很多。如果平時做好數據備份工作,還是可以預防物理刪除隱患的。但心里應該清除,當項目大到一定程度,對數據安全性的要求高到一定程度,使用邏輯刪除代替物理刪除是必然的,在后面的數據庫設計中,可以先小范圍的嘗試使用邏輯刪除,一旦開發模式成熟,就全面使用邏輯刪除代替物理刪除。

邏輯刪除怎么設計

設計方案一:在表中加一個字段deleted字段

deleted字段的值為0表示數據未刪除,值為1表示數據已經刪除。

插入數據數據時,這個值默認為0。刪除數據時將這個值設置為1。查詢和更新數據時都將‘deleted=0’這個條件帶上,只查詢和更新沒有刪除的數據。

這個方案比較簡單,但是會有些問題。比如說你表中的一個字段user_name設置了唯一性約束,但是如果你只是進行了邏輯刪除的話,相同的user_name就不能進行數據插入了。

但如果不將該字段設置為唯一性約束的,那么在每次插入數據的時候,都需先進行一次查詢,看看有無未(邏輯)刪除的同名記錄存在,低效率是一回事,而且在高並發的系統中,很難保證其正確性

然而你的服務運行了一段時間后你還是發現了數據庫中存在 name = a 且 is_delete = 0 的多條字段,大部分是由於以下原因(並發問題):

這個問題有下面兩個解決方案:

解決方案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的原生功能,而且幾乎可以省略單表操作的所有增刪改查方法,大大提升了開發效率。詳細的使用方式可以參考官網

下面就來介紹下,MP的邏輯刪除功能。

step1:進行配置

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: 添加注解

@TableLogic()
@TableField(select = false)
private Integer deleted;

step3: 使用

@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條件失效

SELECT * FROM test.user WHERE deleted=0 AND (user_id = ? AND deleted = ? AND user_name = ?)
  • 追加where條件防止更新到已刪除數據,且使用 wrapper.entity 生成的where條件會忽略該字段,原因和上面的原因是一樣的。

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM