点击↑上方↑蓝色“编了个程”关注我~
这是Yasin的第 53 篇原创文章
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框架里,有时候是没法改变的。
后续翻资料和文档,与同事讨论,发现也有其它不错的解决方案,看来以后在工作中还是要多问自己几个为什么,多思考和查阅资料,才能把软件设计得更好一点。
关于作者
我是Yasin,一个爱写博客的技术人
微信公众号:编了个程(blgcheng)
个人网站:https://yasinshaw.com
欢迎关注这个公众号