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

米洛丶發表於2021-09-18

大家好~我是米洛


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



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

回顧

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

什麼是軟刪除

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

刪除資料,一般來說我們是從資料庫或其他儲存介質裡面刪除資料,舉個?栗子:

我們需要把資料表裡面的使用者A刪除掉,那我們最常見的方式就是:

delete from user where name='A';

這個我理解的話,就是物理刪除,刪除了以後,我們的資料表裡面不再有這條資料的資訊,如果還需要這個使用者,則需要重新insert一遍。

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

軟刪除的優點

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

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

軟刪除的缺點

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

  • 查詢資料更加複雜

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

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

索引之殤

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

  • 使用者表

    我們的使用者表裡面有email欄位,但email大家都懂的,是不可以重複的,所以我們需要給它加上唯一索引

場景一

來看第一種場景,我們為使用者表建立了一條資料:

email  -> 123456@qq.com
deleted_at -> null

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

update user set deleted_at = now() where email='12345@qq.com';

你以為這就沒事兒了嗎?

接下來的事情就讓人腦崩了,由於唯一索引email_uidx的存在,我沒法再新插入一條email=123456@qq.com的資料了。

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

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

試著解決一下

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

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

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

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

  • 建立使用者
email=12345@qq.com
deleted_at=null

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

email=12345@qq.com
deleted_at=2021-09-12 20:13:00

然後我們繼續新增12345@qq.com的使用者,發現可以新增了

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

場景二

這個比較簡單,我們insert2條相同的資料:

insert into user (email, deleted_at) values ('12345@qq.com', null);

insert into user (email, deleted_at) values ('12345@qq.com', 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()

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

相關文章