当软删除遇到唯一索引

点击↑上方↑蓝色“编了个程”关注我~

ba8efb2f5848dbbe5c1ac5dd7ff43ac8.png

这是Yasin的第 53 篇原创文章

1f0e4f8a8ed3ac9e72388830430b8d88.png

Y说

最近几周工作上都有点忙,有点精力不够消耗的感觉。

周末倒是没有太多事情,但只想睡觉。

杭州这两天天气都挺好的,阳光不错,每天都会在饭后去隔壁小区的花园里走一走,晒晒太阳。

感觉日子很平静,又过得很快,尤其是周末,感觉什么都没做,但很快就又到周一了。

于是在周日晚上抓住这个周末的尾巴,写一篇文章出来。这个问题是我前段时间在工作中用到的,相信很多开发者也会遇到这个问题,所以分享出来。

什么是软删除

计算机的世界要比真实世界严谨得多。

我们一般不会把数据真正删除,哪怕是在用户来看,这条数据已经是删除了的。比如你发了一条动态,觉得不合适,在自己的APP上把它删除掉,自己和别人都看不到了。

但在数据库中,这条数据一旦创建后就会一直存在,在用户点“删除”按钮的时候,只是给它设置了一个标识,代表它“已删除”,后续通过应用的正常查询,是会过滤掉这条数据的。

但在我们开发人员排查问题或者做数据分析的时候,这条数据是可见的。

要实现软删除成本不大,也不会浪费很多磁盘空间,但带来的好处是可见的,一方面是方便排查问题,可以知道这条数据情况;另一方面是程序出现了问题,导致数据出现错误时,还有补救的机会。

如何实现软删除

实现软删除有几种方案。

最常见的就是设置一个字段,用来标识这条数据是否被删除。常见的数据类型有布尔类型(在MySQL中一般用tinyint来实现);字符串类型(一般是y/n或者yes/no)。我之前在阿里所在的团队就是使用的字符串y/n的设计。

gorm是一个golang的orm框架,它的设计是一个类型为时间的字段,叫deleted_at。如果为空,代表未删除,如果不为空,代表已删除,其值就是删除的时间。

一般场景下,这两种设计都没有任何问题。但如果遇到了唯一索引,那这两种设计都会出现问题。

软删除遇到唯一索引

唯一索引指的是在数据库层面来约束数据的唯一性。比如:每个人的身份证号是独一无二的,不可重复的。那就可以通过给身份证号添加唯一索引来约束。

但如果我们的表用上述两种方式实现了软删除的功能,那使用唯一索引就会出现问题。

首先,「对于使用了软删除的表,唯一索引上必须要加上软删除的字段」,否则已经被删除的数据可能会和新插入的正常数据引起冲突。比如:如果一个博客系统,标题+作者是唯一的。一个用户发了一篇文章,但对内容不满意,删除了。后来又重新发了一篇标题一样的文章,这个时候如果唯一索引没加软删除字段的话,就会报唯一索引冲突。

如果我们使用布尔值或字符串来实现软删除功能,那被删除两次的相同数据会引起唯一键冲突。比如同样是这个博客系统,我删除了一篇文章,又写了一篇标题一样的文章,结果这篇文章也想删除的时候,就会报唯一索引冲突,删除失败。

如果使用gorm的时间戳设计,由于它的deleted_at字段允许为空,唯一索引加上这个字段后,唯一索引会失效。这个时候同一个用户是可以创建两篇标题一模一样的文章的,违背了产品的设计。

其它设计

那如何解决这个问题呢?

其实顺着这个思路去想,是很好解决这个问题的。

首先,软删除字段不能为空,否则唯一索引就失效了。

其次,已删除的数据要想办法不被唯一索引检测到冲突,但未删除的数据又需要被唯一索引检测到冲突。那代表这个唯一索引不能是简单的布尔值,且需要一个“默认值”。

那就可以想到两种设计了。

第一种是像gorm那样,使用时间戳来标识已删除。不同的是,不使用IS NULL来判断未删除的数据,而使用=0来判断未删除的数据。也就是说,deleted_at是非空的,默认值是0。事实上我今天在翻gorm官方文档的时候,发现它其实也是支持这种设计的:

import "gorm.io/plugin/soft_delete"

type User struct {
  ID        uint
  Name      string
  DeletedAt soft_delete.DeletedAt
}

// Query
SELECT * FROM users WHERE deleted_at = 0;

// Delete
UPDATE users SET deleted_at = /* current unix second */ WHERE ID = 1;

另一种就是使用deleted_id来作为软删除字段。默认值是0(如果主键是字符串类型,默认为空字符串),如果是已删除的数据,那更新deleted_id=id

总结

目前项目上都是深度使用的gorm。之前觉得它的软删除设计有些奇怪,但没有细想到底奇怪在哪里,就正常使用了一段时间,直到前段时间发现唯一索引是不生效的,才觉察到有问题。

之前在阿里那种y/n设计是明确有问题的,但当时也没深究怎么去解决。而且软删除这种功能很多时候都是写死在了orm框架里,有时候是没法改变的。

后续翻资料和文档,与同事讨论,发现也有其它不错的解决方案,看来以后在工作中还是要多问自己几个为什么,多思考和查阅资料,才能把软件设计得更好一点。

342c11e56d518a995db8d93387dd1a20.png

关于作者

我是Yasin,一个爱写博客的技术人

微信公众号:编了个程(blgcheng)

个人网站:https://yasinshaw.com

欢迎关注这个公众号1028c23d82dd2bc687a9ff4bac94cc18.png

b54315202d2c9a31d567ba2965feacf4.png

6aefcc6e96af85c955f374f7f15f538b.png

数据源切换的一般步骤


962fe07edec7358bacce86bd6d23f40e.png

MySQL到底在RR层面解决幻读了吗?


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值