什么是邏輯刪除
所謂邏輯刪除是指數據已經“不需要”了,但是並沒有使用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
deleted=0條件會讓后面我們自己加的deleted條件失效
SELECT * FROM test.user WHERE deleted=0 AND (user_id = ? AND deleted = ? AND user_name = ?)
- 追加where條件防止更新到已刪除數據,且使用 wrapper.entity 生成的where條件會忽略該字段,原因和上面的原因是一樣的。
