MySQL邏輯刪除與唯一索引沖突解決

問題背景

在MySQL數據庫設計中,邏輯刪除(軟刪除)是一種常見的實踐,它通過設置標志位(如is_delete)來標記記錄被"刪除",而不是實際刪除數據。然而,當表中存在唯一約束時,如在用戶表中我們要求用戶名必須唯一,并且用戶數據不要物理刪除,那這個時候可能會產生一個問題:

  • 用戶A(username=“Tom”)被邏輯刪除(is_delete=1)
  • 新用戶嘗試使用username="Tom"注冊時
  • 唯一約束阻止創建新記錄,即使原始用戶已被"刪除"

本文將介紹解決此問題的方案。

問題復現

1.創建用戶表

CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY ,username VARCHAR(50) NOT NULL COMMENT '用戶名',email VARCHAR(100) NOT NULL COMMENT '用戶郵箱',is_delete TINYINT(1) NOT NULL DEFAULT 0 COMMENT '邏輯刪除標記,用于標記當前記錄是否已刪除,0:否,1:是'
);

2.執行腳本:

-- 插入一條測試數據,用戶名為:tom
INSERT INTO users (username, email) VALUES ('tom', 'tom@example.com');-- 邏輯刪除tom用戶(is_delet設置為1)
UPDATE users SET is_delete = 1 WHERE username = 'tom';-- 創建同名用戶(is_delete不同)
INSERT INTO users (username, email) VALUES ('tom', 'new_tom@example.com');

在執行第三步時,會報錯如下:

[23000][1062] Duplicate entry 'tom' for key 'users.idx_uq_username'

原因分析:從結果可以看到,在插入相同名字的記錄時,違反了唯一約束idx_uq_username,但實際上用戶tom已經刪除了,唯一索引阻止了用戶名=tom的記錄插入。

解決方案

解決方案1.復合唯一索引 + 時間戳刪除字段

改動點:
1)添加一個字段delete_time,用于記錄被刪除的時間,默認值為NULL,當刪除該記錄時將該字段設置為當前時間
2)新建復合唯一索引,將用戶名username和刪除時間delete_time字段包含在復合唯一索引中

-- 方案1
CREATE TABLE users_test1 (id INT AUTO_INCREMENT PRIMARY KEY ,username VARCHAR(50) NOT NULL COMMENT '用戶名',email VARCHAR(100) NOT NULL COMMENT '用戶郵箱',is_delete TINYINT(1) NOT NULL DEFAULT 0 COMMENT '邏輯刪除標記,用于標記當前記錄是否已刪除,0:否,1:是',delete_time DATETIME NULL DEFAULT NULL COMMENT '邏輯刪除時間,默認為NULL'
);-- 添加復合唯一索引
ALTER TABLE users_test1
ADD UNIQUE INDEX idx_unique_username_dt (username, delete_time);-- 插入初始用戶
INSERT INTO users_test1 (username, email) VALUES ('tom', 'tom@example.com');-- 邏輯刪除用戶(設置刪除時間)
UPDATE users_test1 SET is_delete = 1,delete_time = NOW() WHERE username = 'tom';-- 創建同名新用戶(delete_time為NULL)
INSERT INTO users_test1 (username, email) VALUES ('tom', 'new_tom@example.com');

執行完上面腳本發現并沒有報錯,執行查詢sql

select * from users_test1;

結果如下:


+--+--------+-------------------+---------+-------------------+
|id|username|email              |is_delete|delete_time        |
+--+--------+-------------------+---------+-------------------+
|1 |tom     |tom@example.com    |1        |2025-07-13 14:55:59|
|2 |tom     |new_tom@example.com|0        |null               |
+--+--------+-------------------+---------+-------------------+

解決方案2:刪除后修改唯一字段值

改動點:
在邏輯刪除時,為唯一字段添加特定前綴/后綴,使其不再與原有值沖突

-- 方案2 刪除后修改唯一字段值
CREATE TABLE users_test2 (id INT AUTO_INCREMENT PRIMARY KEY ,username VARCHAR(50) NOT NULL COMMENT '用戶名',email VARCHAR(100) NOT NULL COMMENT '用戶郵箱',is_delete INT(1) NULL DEFAULT 0 COMMENT '邏輯刪除標記,用于標記當前記錄是否已刪除,0:否,1:是'
);-- 添加復合唯一索引
ALTER TABLE users_test2
ADD UNIQUE INDEX idx_unique_username (username);-- 插入初始用戶
INSERT INTO users_test2 (username, email) VALUES ('tom', 'tom@example.com');-- 邏輯刪除用戶,修改用戶名
UPDATE users_test2 SET is_delete = 1,username = CONCAT(username, '_deleted_', UUID_SHORT()) WHERE username = 'tom';-- 創建同名新用戶
INSERT INTO users_test2 (username, email) VALUES ('tom', 'new_tom@example.com');select * from users_test2;

查詢結果示例:

+--+------------------------------+-------------------+---------+
|id|username                      |email              |is_delete|
+--+------------------------------+-------------------+---------+
|1 |tom_deleted_100950808475992064|tom@example.com    |1        |
|2 |tom                           |new_tom@example.com|0        |
+--+------------------------------+-------------------+---------+

解決方案3. 使用歷史表

修改點:將刪除的記錄移動到專門的歷史表,主表只保留有效記錄

-- 方案3
-- 主表(活躍用戶)
CREATE TABLE users_test3 (id INT AUTO_INCREMENT PRIMARY KEY ,username VARCHAR(50) NOT NULL COMMENT '用戶名',email VARCHAR(100) NOT NULL COMMENT '用戶郵箱',is_delete INT(1) NULL DEFAULT 0 COMMENT '邏輯刪除標記,用于標記當前記錄是否已刪除,0:否,1:是'
);-- 添加復合唯一索引
ALTER TABLE users_test3
ADD UNIQUE INDEX idx_unique_username (username);-- 歷史表(已刪除用戶)
CREATE TABLE users_test3_deleted (id INT AUTO_INCREMENT PRIMARY KEY ,username VARCHAR(50) NOT NULL COMMENT '用戶名',email VARCHAR(100) NOT NULL COMMENT '用戶郵箱',is_delete INT(1) NULL DEFAULT 0 COMMENT '邏輯刪除標記,用于標記當前記錄是否已刪除,0:否,1:是'
);-- 添加歷史表復合唯一索引
ALTER TABLE users_test3_deleted 
ADD UNIQUE INDEX idx_unique_username (username);-- 插入測試數據
INSERT INTO users_test3 (username, email) VALUES ('tom', 'tom@example.com');-- 邏輯刪除:移動到歷史表
INSERT INTO users_test3_deleted (id, username, email,is_delete)
SELECT id, username, email,1 FROM users_test3 WHERE username = 'tom';
-- 刪除原紀錄
DELETE FROM users_test3 WHERE username = 'tom';-- 可以重新創建原用戶名
INSERT INTO users_test3 (username, email) VALUES ('tom', 'new_tom@example.com');select * from users_test3;
select * from users_test3_deleted;

解決方案4. 業務層校驗+更新記錄

修改點:
保持唯一索引不變,在業務層處理沖突

-- 創建表(普通唯一索引)
CREATE TABLE users_test4 (id INT AUTO_INCREMENT PRIMARY KEY ,username VARCHAR(50) NOT NULL COMMENT '用戶名',email VARCHAR(100) NOT NULL COMMENT '用戶郵箱',is_delete INT(1) NULL DEFAULT 0 COMMENT '邏輯刪除標記,用于標記當前記錄是否已刪除,0:否,1:是'
);-- 業務層邏輯示例偽代碼:
/*
1. 先查詢是否存在已刪除的同名用戶SELECT id FROM users_test4 WHERE username = ? AND is_delete = 1
2. 如果存在,則更新原記錄(恢復)UPDATE users_test4 SET is_delete = 0, email = ? WHERE username = ?
3. 如果不存在,則新建記錄INSERT INTO users_test4 (username, email) VALUES (?, ?)
*/

總結

  • 復合唯一索引 + 時間戳刪除字段:需要需修改表結構,適用于新項目設計,需保留完整數據歷史且查詢頻繁的系統;
  • 刪除后修改唯一字段值:需要修改業務字段(如用戶名),可能影響日志或歷史記錄追溯,適用于臨時解決方案;
  • 使用歷史表:需同步維護兩個表結構,備份恢復方便,查詢主表的數據量比較小,查詢效率高,適用于數據量大、刪除頻繁且需要嚴格區分活躍/歷史數據的系統;
  • 業務層校驗+更新記錄:不用修改字段,需編寫額外校驗和恢復邏輯,可能存在并發問題風險;

綜合以上,建議采用方案1和方案3

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/88850.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/88850.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/88850.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

php命名空間用正斜杠還是反斜杠?

在PHP中,命名空間使用反斜杠(\)作為分隔符,這是PHP語言規范明確規定的。反斜杠在命名空間中扮演路徑分隔的角色,用于區分不同層級的命名空間。 具體說明:語法規則 PHP命名空間使用反斜杠(\&…

《從依賴糾纏到接口協作:ASP.NET Core注入式開發指南》

在C#的ASP.NET Core開發中,依賴注入絕非簡單的技術技巧,而是重構代碼關系的底層邏輯。它像一套隱形的神經網絡,讓程序模塊擺脫硬編碼的束縛,在運行時實現動態連接,從而為系統注入可測試、可進化的核心生命力。理解其深…

星云ERP本地環境搭建筆記

看到星云ERP兩個比較實用的功能,編號規則和打印模板,如下圖所示,于是本地跑起來學習學習。開發環境必備:1. JDK 1.82. MySQL 5.73. Redis 44. RabbitMQ 3.12.45. nodejs 206. pnpm 9.7.1 (npm install -g pnpm9.7.1)其他開發工具&…

RedisJSON 的 `JSON.ARRAPPEND`一行命令讓數組動態生長

1 、 為什么選擇 JSON.ARRAPPEND 在傳統的鍵值模型里,若要往數組尾部追加元素,通常需要 取→改→寫 三步: GET 整個 JSON;在應用層把元素 push 進數組;SET 回 Redis。 一條 JSON.ARRAPPEND 則可一次完成,具…

14:00開始面試,14:08就出來了,問的問題有點變態。。。

從小廠出來,沒想到在另一家公司又寄了。 到這家公司開始上班,加班是每天必不可少的,看在錢給的比較多的份上,就不太計較了。沒想到4月一紙通知,所有人不準加班,加班費不僅沒有了,薪資還要降40%…

Unity物理系統由淺入深第四節:物理約束求解與穩定性

Unity物理系統由淺入深第一節:Unity 物理系統基礎與應用 Unity物理系統由淺入深第二節:物理系統高級特性與優化 Unity物理系統由淺入深第三節:物理引擎底層原理剖析 Unity物理系統由淺入深第四節:物理約束求解與穩定性 物理引擎的…

深入淺出Kafka Consumer源碼解析:設計哲學與實現藝術

一、Kafka Consumer全景架構 1.1 核心組件交互圖 #mermaid-svg-JDEEOd2M5PzLkYa6 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-JDEEOd2M5PzLkYa6 .error-icon{fill:#552222;}#mermaid-svg-JDEEOd2M5PzLkYa6 .erro…

Matplotlib(一)- 數據可視化與Matplotlib

文章目錄一、數據可視化1. 數據可視化的概念2. 數據可視化流程3. 數據可視化目的4. 常見的可視化圖表4.1 折線圖4.2 柱形圖4.3 條形圖4.4 堆積圖4.4.1 堆積面積圖4.4.2 堆積柱形圖和堆積條形圖4.5 直方圖4.6 箱形圖4.7 餅圖4.8 散點圖4.9 氣泡圖4.10 誤差棒圖4.11 雷達圖二、Py…

傳輸層協議UDP原理

端口號回顧端口號的作用類似pid,用來標識進程的唯一性。只是為了與系統解耦,所以有了端口號。通過ip來確定唯一主機,再通過端口號找到指定的進程。就可以讓全網內唯一的兩個進程通信了。所以一個完整的報文至少要攜帶ip和端口號,i…

【牛客刷題】小紅的數字刪除

文章目錄 一、題目介紹1.1 題目描述1.2 輸入描述:1.3 輸出描述:1.4 示例11.5 示例2二、解題思路2.1 核心觀察2.2 關鍵問題處理三、算法實現四、算法分析4.1 算法流程圖4.2 為什么這么設計算法?4.3 算法復雜度五、模擬演練數據示例1: "103252"示例2: "333&quo…

《大數據技術原理與應用》實驗報告三 熟悉HBase常用操作

目 錄 一、實驗目的 二、實驗環境 三、實驗內容與完成情況 3.1 用Hadoop提供的HBase Shell命令完成以下任務 3.2 現有以下關系型數據庫中的表和數據,要求將其轉換為適合于HBase存儲的表并插入數據: 四、問題和解決方法 五、心得體會 一、實驗目的…

微服務初步入門

服務拆分原則 單一職責原則 單一職責原則原本是面向對象設計的一個基本原則,是指一個類應該專注于單一的功能,不要存在多于一個導致類變更的原因 在微服務架構中,是指一個微服務只負責一個功能或者業務領域,每個服務應該由清晰的定…

Liunx操作系統筆記5

用戶管理命令: useradd命令: useradd命令的功能是創建并設置用戶信息。使用useradd命令可以自動完成用戶信息、基本組、家目錄等的創建工作,并在創建過程中對用戶初始信息進行定制。語法格式:useradd 參數 用戶名常用參數: -M 不建立用…

spring-ai-alibaba 接入Tushare查詢股票行情

最近spring-ai-alibaba主干分支新增了對Tushare的支持&#xff0c;一起來看看如何使用簡單樣例老樣子&#xff0c;分三步進行&#xff1a;第一步&#xff1a;添加依賴<dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-aliba…

Java使用Langchai4j接入AI大模型的簡單使用(一)

一、LangChain4j 簡介 LangChain4j 是 Java 生態中的 LangChain 實現&#xff0c;是一個用于構建大語言模型(LLM)應用程序的框架。它提供了與各種LLM服務集成的能力&#xff0c;并簡化了構建復雜AI應用的過程。 LangChain4j官方文檔&#xff1a;Integrations | LangChain4j …

Linux —— A / 基礎指令

建議學習路徑&#xff1a;Linux系統與系統編程 ? Linux網絡和網絡編程 ? MySQL一、初識shell命令 1.1、關于 Linux 桌面很多同學的 Linux 啟動進?圖形化的桌?. 這個東西?家以后就可以忘記了。以后的工作中沒有機會使用圖形界面。思考: 為什么不使用圖形界面? 1.2、下…

[論文閱讀] 人工智能 + 軟件工程 | 用大語言模型+排名機制,讓代碼評論自動更新更靠譜

LLMCup&#xff1a;用大語言模型排名機制&#xff0c;讓代碼評論自動更新更靠譜 LLMCup: Ranking-Enhanced Comment Updating with LLMsarXiv:2507.08671 LLMCup: Ranking-Enhanced Comment Updating with LLMs Hua Ge, Juan Zhai, Minxue Pan, Fusen He, Ziyue Tan Comments: …

悲觀鎖 樂觀鎖

悲觀鎖 樂觀鎖 在沒有加鎖的秒殺場景下 每秒打進來的請求是巨大的 高并發場景下 我們發現不僅異常率高的可怕 庫存竟然還變成了負數 這產生的結果肯定是很大損失的 那為什么會出現超賣問題呢 我們假設有下面兩個線程線程1查詢庫存&#xff0c;發現庫存充足&#xff0c;創建訂單…

如何使用Cisco DevNet提供的免費ACI學習實驗室(Learning Labs)?(Grok3 回答)

Cisco DevNet 提供的免費 ACI&#xff08;Application Centric Infrastructure&#xff09;學習實驗室&#xff08;Learning Labs&#xff09;是幫助用戶學習和實踐 Cisco ACI 技術&#xff08;包括 APIC 控制器&#xff09;的優秀資源&#xff0c;適合網絡工程師、開發者和準備…

Combine的介紹與使用

目錄一、Combine 框架介紹二、核心概念三、基礎使用示例3.1、創建 Publisher & 訂閱3.2、操作符鏈式調用3.3、Subject 使用&#xff08;手動發送值&#xff09;3.4、網絡請求處理3.5、組合多個 Publisher3.6、錯誤處理四、核心操作符速查表 Operator五、UIKit 綁定示例六、…