MySQL邏輯刪除與唯一索引沖突解決方案
問(wèn)題背景
在MySQL數(shù)據(jù)庫(kù)設(shè)計(jì)中,邏輯刪除(軟刪除)是一種常見(jiàn)的實(shí)踐,它通過(guò)設(shè)置標(biāo)志位(如is_delete)來(lái)標(biāo)記記錄被"刪除",而不是實(shí)際刪除數(shù)據(jù)。然而,當(dāng)表中存在唯一約束時(shí),如在用戶表中我們要求用戶名必須唯一,并且用戶數(shù)據(jù)不要物理刪除,那這個(gè)時(shí)候可能會(huì)產(chǎn)生一個(gè)問(wèn)題:
- 用戶A(username=“Tom”)被邏輯刪除(is_delete=1)
- 新用戶嘗試使用username="Tom"注冊(cè)時(shí)
- 唯一約束阻止創(chuàng)建新記錄,即使原始用戶已被"刪除"
本文將介紹解決此問(wèn)題的方案。
問(wèn)題復(fù)現(xiàn)
1.創(chuàng)建用戶表
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 '邏輯刪除標(biāo)記,用于標(biāo)記當(dāng)前記錄是否已刪除,0:否,1:是'
);2.執(zhí)行腳本:
-- 插入一條測(cè)試數(shù)據(jù),用戶名為:tom
INSERT INTO users (username, email) VALUES ('tom', 'tom@example.com');
-- 邏輯刪除tom用戶(is_delet設(shè)置為1)
UPDATE users SET is_delete = 1 WHERE username = 'tom';
-- 創(chuàng)建同名用戶(is_delete不同)
INSERT INTO users (username, email) VALUES ('tom', 'new_tom@example.com');在執(zhí)行第三步時(shí),會(huì)報(bào)錯(cuò)如下:
[23000][1062] Duplicate entry 'tom' for key 'users.idx_uq_username'
原因分析:從結(jié)果可以看到,在插入相同名字的記錄時(shí),違反了唯一約束idx_uq_username,但實(shí)際上用戶tom已經(jīng)刪除了,唯一索引阻止了用戶名=tom的記錄插入。
解決方案
解決方案1.復(fù)合唯一索引 + 時(shí)間戳刪除字段
改動(dòng)點(diǎn):
1)添加一個(gè)字段delete_time,用于記錄被刪除的時(shí)間,默認(rèn)值為NULL,當(dāng)刪除該記錄時(shí)將該字段設(shè)置為當(dāng)前時(shí)間
2)新建復(fù)合唯一索引,將用戶名username和刪除時(shí)間delete_time字段包含在復(fù)合唯一索引中
-- 方案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 '邏輯刪除標(biāo)記,用于標(biāo)記當(dāng)前記錄是否已刪除,0:否,1:是',
delete_time DATETIME NULL DEFAULT NULL COMMENT '邏輯刪除時(shí)間,默認(rèn)為NULL'
);
-- 添加復(fù)合唯一索引
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');
-- 邏輯刪除用戶(設(shè)置刪除時(shí)間)
UPDATE users_test1 SET is_delete = 1,delete_time = NOW() WHERE username = 'tom';
-- 創(chuàng)建同名新用戶(delete_time為NULL)
INSERT INTO users_test1 (username, email) VALUES ('tom', 'new_tom@example.com');執(zhí)行完上面腳本發(fā)現(xiàn)并沒(méi)有報(bào)錯(cuò),執(zhí)行查詢sql
select * from users_test1;
結(jié)果如下:
+--+--------+-------------------+---------+-------------------+ |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:刪除后修改唯一字段值
改動(dòng)點(diǎn):
在邏輯刪除時(shí),為唯一字段添加特定前綴/后綴,使其不再與原有值沖突
-- 方案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 '邏輯刪除標(biāo)記,用于標(biāo)記當(dāng)前記錄是否已刪除,0:否,1:是'
);
-- 添加復(fù)合唯一索引
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';
-- 創(chuàng)建同名新用戶
INSERT INTO users_test2 (username, email) VALUES ('tom', 'new_tom@example.com');
select * from users_test2;查詢結(jié)果示例:
+--+------------------------------+-------------------+---------+ |id|username |email |is_delete| +--+------------------------------+-------------------+---------+ |1 |tom_deleted_100950808475992064|tom@example.com |1 | |2 |tom |new_tom@example.com|0 | +--+------------------------------+-------------------+---------+
解決方案3. 使用歷史表
修改點(diǎn):將刪除的記錄移動(dòng)到專門(mén)的歷史表,主表只保留有效記錄
-- 方案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 '邏輯刪除標(biāo)記,用于標(biāo)記當(dāng)前記錄是否已刪除,0:否,1:是'
);
-- 添加復(fù)合唯一索引
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 '邏輯刪除標(biāo)記,用于標(biāo)記當(dāng)前記錄是否已刪除,0:否,1:是'
);
-- 添加歷史表復(fù)合唯一索引
ALTER TABLE users_test3_deleted
ADD UNIQUE INDEX idx_unique_username (username);
-- 插入測(cè)試數(shù)據(jù)
INSERT INTO users_test3 (username, email) VALUES ('tom', 'tom@example.com');
-- 邏輯刪除:移動(dòng)到歷史表
INSERT INTO users_test3_deleted (id, username, email,is_delete)
SELECT id, username, email,1 FROM users_test3 WHERE username = 'tom';
-- 刪除原紀(jì)錄
DELETE FROM users_test3 WHERE username = 'tom';
-- 可以重新創(chuàng)建原用戶名
INSERT INTO users_test3 (username, email) VALUES ('tom', 'new_tom@example.com');
select * from users_test3;
select * from users_test3_deleted;解決方案4. 業(yè)務(wù)層校驗(yàn)+更新記錄
修改點(diǎn):
保持唯一索引不變,在業(yè)務(wù)層處理沖突
-- 創(chuàng)建表(普通唯一索引)
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 '邏輯刪除標(biāo)記,用于標(biāo)記當(dāng)前記錄是否已刪除,0:否,1:是'
);
-- 業(yè)務(wù)層邏輯示例偽代碼:
/*
1. 先查詢是否存在已刪除的同名用戶
SELECT id FROM users_test4 WHERE username = ? AND is_delete = 1
2. 如果存在,則更新原記錄(恢復(fù))
UPDATE users_test4 SET is_delete = 0, email = ? WHERE username = ?
3. 如果不存在,則新建記錄
INSERT INTO users_test4 (username, email) VALUES (?, ?)
*/總結(jié)
- 復(fù)合唯一索引 + 時(shí)間戳刪除字段:需要需修改表結(jié)構(gòu),適用于新項(xiàng)目設(shè)計(jì),需保留完整數(shù)據(jù)歷史且查詢頻繁的系統(tǒng);
- 刪除后修改唯一字段值:需要修改業(yè)務(wù)字段(如用戶名),可能影響日志或歷史記錄追溯,適用于臨時(shí)解決方案;
- 使用歷史表:需同步維護(hù)兩個(gè)表結(jié)構(gòu),備份恢復(fù)方便,查詢主表的數(shù)據(jù)量比較小,查詢效率高,適用于數(shù)據(jù)量大、刪除頻繁且需要嚴(yán)格區(qū)分活躍/歷史數(shù)據(jù)的系統(tǒng);
- 業(yè)務(wù)層校驗(yàn)+更新記錄:不用修改字段,需編寫(xiě)額外校驗(yàn)和恢復(fù)邏輯,可能存在并發(fā)問(wèn)題風(fēng)險(xiǎn);
綜合以上,建議采用方案1和方案3
到此這篇關(guān)于MySQL邏輯刪除與唯一索引沖突解決的文章就介紹到這了,更多相關(guān)mysql邏輯刪除與唯一索引沖突內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL8.0.28安裝教程詳細(xì)圖解(windows?64位)
如果電腦上已經(jīng)有MySQL數(shù)據(jù)庫(kù)再進(jìn)行重做往往會(huì)遇到問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于windows?64位系統(tǒng)下MySQL8.0.28安裝教程的詳細(xì)教程,文章通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-04-04
MySQL報(bào)錯(cuò)Lost connection to MySQL server&n
在確保網(wǎng)絡(luò)沒(méi)有問(wèn)題的情況下,服務(wù)器正常運(yùn)行一段時(shí)間后,數(shù)據(jù)庫(kù)拋出了異常"Lost connection to MySQL server during query",本文將給大家介紹MySQL報(bào)錯(cuò)Lost connection to MySQL server during query的解決方案,需要的朋友可以參考下2024-01-01
Mysql-exporter監(jiān)控指標(biāo)詳解
本文列舉了14項(xiàng)MySQL監(jiān)控指標(biāo)及PromQL告警語(yǔ)句,涵蓋連接數(shù)、文件打開(kāi)數(shù)、主從狀態(tài)、流量、緩沖池利用率等,觸發(fā)閾值如75%、1024KB、100等,用于實(shí)時(shí)監(jiān)控?cái)?shù)據(jù)庫(kù)健康與性能2025-07-07
一次Mysql?update?sql不當(dāng)引起的生產(chǎn)故障記錄
這篇文章主要給大家介紹了關(guān)于一次Mysql?update?sql不當(dāng)引起的生產(chǎn)故障的相關(guān)資料,由于update涉及到數(shù)據(jù)的修改,所以很容易推斷,update語(yǔ)句比select語(yǔ)句會(huì)更復(fù)雜一些,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04
mysql case when group by 實(shí)例詳解
這篇文章主要介紹了mysql 中類似php switch case 的語(yǔ)句,需要的朋友可以參考下2018-01-01
5個(gè)保護(hù)MySQL數(shù)據(jù)倉(cāng)庫(kù)的小技巧
這篇文章主要為大家詳細(xì)介紹了五個(gè)小技巧,告訴你如何保護(hù)MySQL數(shù)據(jù)倉(cāng)庫(kù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08

