測試平台系列(63) 軟刪除之殤

大家好~我是米洛

這是一個完整的接口測試平台系列教程,希望能和大家一起學習,從0到1打造一個開源平台。

歡迎關注我的公眾號測試開發坑貨,獲取最新文章教程!

回顧

上一節我們基本上搞定了數據構造器的增刪改等操作,這一篇我們來講講軟刪除相關的內容。

什麼是軟刪除

先聲明一下,我沒有查閱關於具體軟刪除的資料,大家有興趣的可以自己去搜索下,我說的都是自己的理解

刪除數據,一般來說我們是從數據庫或其他存儲介質裏面刪除數據,舉個🌰栗子:

我們需要把數據表裏面的用戶A刪除掉,那我們最常見的方式就是:

delete from user where name='A';

這個我理解的話,就是物理刪除,刪除了以後,我們的數據表裏面不再有這條數據的信息,如果還需要這個用戶,則需要重新insert一遍。

軟刪除又是什麼呢?軟刪除也可以叫做邏輯刪除,比如我們給User表定義一個字段: deleted_at(代表這條數據刪除的時間),如果這個字段不為空,說明用戶未被刪除, 反之則說明用戶已經被刪除了。因為只是邏輯上的”刪除”,並不是真正刪掉了這條數據,所以又叫做邏輯刪除。

軟刪除的優點

軟刪除對比物理刪除的話,好處在哪呢?我認為有以下幾個方面:

  • 數據未被真實刪除,數據更可靠
  • 可以通過把刪除的時間戳賦予給deleted_at,從而知道刪除的時間,無形之中記錄了用戶的刪除操作
  • 對於用戶而言和物理刪除沒什麼區別
  • 擁有天然的回收站功能

軟刪除的缺點

上面提到了軟刪除的好處,這裡就來說說軟刪除讓人又愛又恨的地方。

  • 查詢數據更加複雜

    查詢條件需要帶入deleted_at is null,否則會查詢出被刪除的數據。

    除了這個以外,還有一個比較棘手的問題,且聽我慢慢道來。

索引之殤

索引這塊是一個比較棘手的問題,特別是當我們有唯一索引的時候,我們先來看一種場景。

  • 用戶表

    我們的用戶表裏面有email字段,但email大家都懂的,是不可以重複的,所以我們需要給它加上唯一索引

場景一

來看第一種場景,我們為用戶表創建了一條數據:

email  -> [email protected]
deleted_at -> null

我們這時候要刪除這條數據,但因為軟刪除的原因我們會寫這樣的sql:

update user set deleted_at = now() where email='[email protected]';

你以為這就沒事兒了嗎?

接下來的事情就讓人腦崩了,由於唯一索引email_uidx的存在,我沒法再新插入一條[email protected]的數據了。

執行插入語句的時候,會報duplicate index的錯誤,相信大家也不少見。

那這個問題怎麼解決呢?我們來看看場景二

試着解決一下

為了解決場景一的唯一索引問題,我們想到了聯合索引,啥子叫聯合唯一索引呢?就是多個字段同時相同的時候,數據才算重複,也就是觸發duplicate index報錯。

於是我們創建一個聯合唯一索引:

ALTER TABLE user ADD UNIQUE INDEX(email, deleted_at);

這時候,我們再來看場景一:

  • 創建用戶
[email protected]
deleted_at=null

接着,我們刪除之,這時候它變成了:

[email protected]
deleted_at=2021-09-12 20:13:00

然後我們繼續添加[email protected]的用戶,發現可以添加了

你以為這就完事了?那我們再看看場景二:

場景二

這個比較簡單,我們insert2條相同的數據:

insert into user (email, deleted_at) values ('[email protected]', null);

insert into user (email, deleted_at) values ('[email protected]', null);

好久沒寫sql,也不知道寫的對不對,湊合看,大概是這麼個意思。

這時候奇蹟出現了,可以發現2條數據都插入成功了,也就是說,之前的email的唯一性得不到保證了。

這是為什麼呢?


原來,當聯合索引裏面有字段為null的時候,聯合索引會自動失效

那這個真的是非常難受,可謂是修復了一個bug又導致了另一個bug。

解決方案一

其實我們可以把deleted_at設置為和主鍵一樣的自增id,每當被刪除的時候就+1,恢復的時候也+1,默認為0,這樣也不會因為默認為NULL而引發唯一索引失效。

解決方案二

我們可以把deleted_at設置為時間戳,默認為0(代表未刪除),一般來說,手動操作刪除操作時間戳肯定有細微變化,這樣索引將不會生效,也就是不會影響到之前的數據。

但也有一個缺點: 如果一個數據被刪除多次,數據庫將存在許多相同的被刪除的數據,當然一般人也不會做辣么無聊的事情。

class Model(Base):
    deleted_at = Column(BIGINT, nullable=False, default=0)

可以看到deleted_at如此定義,當有刪除操作的時候,我們設置deleted_at = time.time()即可。

import time
model.deleted_at = time.time()

大家如果有更合適的方案,也歡迎一起探討感激不盡!