解决MySQL虚拟删除影响唯一索引的问题
场景
现在的项目一般使用逻辑删除来处理业务上的删除逻辑,这样方便数据的恢复和追溯,一般加一个 is_deleted
列,值为 0
说明没有没删除,值为 1
说明被删除了。
但是如果表中存在一个唯一索引,例如 name
,第一次 insert
一条 {name: '张三', is_deleted: 0}
的数据,之后将这条数据删除,数据变成 {name: '张三', is_deleted: 1}
,
随后再次 insert
一条 {name: '张三', is_deleted: 0}
就会出现唯一索引冲突的问题,虽然逻辑上来说之前那条数据已经被删除,但是因为我们唯一索引是建立在 name
上,其并不识别虚拟删除列,
这时候是不是可以考虑将 name
和 is_deleted
做一个联合唯一索引?但是依然会有问题,如下:
-
+
{id: 1, name: '张三', is_deleted: 0}
联合唯一索引:张三 0
-
-
{id: 1, name: '张三', is_deleted: 0}
联合唯一索引:张三 1
-
+
{id: 2, name: '张三', is_deleted: 0}
联合唯一索引:张三 0
-
-
{id: 2, name: '张三', is_deleted: 0}
联合唯一索引:张三 1
执行这一步删除时报错,因为上面第二步 id为1的已经产生了一个
张三 1
的唯一索引。
解决方式
思路很简单,就是将虚拟删除后的数据不纳入唯一索引管理范围内,因为删除后的数据也没用了,所以不纳入索引也无关紧要,本身就用不着,因此主要看数据库引擎是否支持这么做,mysql5.7和8分别有不同的支持方式。
mysql5.7
mysql5.7 支持使用一个虚拟列来做唯一索引,这个虚拟列可以加上条件判断,如果非虚拟删除时,列的值是name,否则就是null,这样可以达成我们上述的结果,保证没有被逻辑删除的数据被唯一索引约束着。
CREATE TABLE your_table (id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(50),is_deleted TINYINT DEFAULT 0 COMMENT '0-未删除,1-已删除',name_for_unique VARCHAR(50) GENERATED ALWAYS AS (IF(is_deleted = 0, name, NULL)) VIRTUAL,UNIQUE KEY uk_name_active (name_for_unique)
);
mysql8
mysql8更直接一些,直接将条件判断带入索引中,无需新建虚拟列。
CREATE TABLE your_table (id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(50),is_deleted TINYINT DEFAULT 0 COMMENT '0-未删除,1-已删除',UNIQUE KEY uk_name_active (name, (IF(is_deleted = 0, 0, NULL)))
);