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

什麼是邏輯刪除

所謂邏輯刪除是指數據已經「不須要」了,可是並無使用delete語句將這些數據真實的從數據庫中刪除,而只是用一個標誌位將其設置爲已經刪除。java

爲何須要邏輯刪除

對數據進行邏輯刪除,通常存在如下緣由:sql

  • 防止數據誤刪除,不能找回數據;
  • 這些數據還具備必定的商業價值,好比用戶的註冊信息;
  • 雖然這些數據能夠刪除,可是這些數據還有關聯數據,這些關聯數據不能刪除。

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

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

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

網友的一些建議安全

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

邏輯刪除怎麼設計

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

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

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

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

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

然而你的服務運行了一段時間後你仍是發現了數據庫中存在 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條件會忽略該字段,緣由和上面的緣由是同樣的。

參考

相關文章
相關標籤/搜索