實現資料邏輯刪除的一種方案
阿新 • • 發佈:2021-01-08
## 什麼是邏輯刪除
所謂邏輯刪除是指資料已經“不需要”了,但是並沒有使用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