软删除设计:为什么使用 deleted_at = ‘1970-01-01 00:00:00‘ 表示未删除?

软删除设计: 为什么使用 deleted_at = ‘1970-01-01 00:00:00’ 表示未删除?

在现代数据库设计中,尤其是在处理用户数据、审计追踪和合规要求的应用中,软删除 已成为硬删除的标准替代方案。软删除是通过标记记录为已删除而不实际从数据库中移除它们。这种方法保留了数据以供潜在恢复、历史分析或监管用途。

什么是软删除

**软删除(Soft Delete)**是一种数据删除策略,不是真正从数据库中删除记录,而是通过标记字段来表示记录已被"删除"。

为什么使用 deleted_at = ‘1970-01-01 00:00:00’ 表示未删除?

GORM 官方文档中关于软删除(Soft Delete)的说明位于「Delete」章节。
GORM 文档明确建议:

  • 如果你的模型包含一个gorm.DeletedAt字段(该字段包含在gorm.Model中),它将自动获得软删除能力!
  • 调用 Delete 时,记录不会从数据库中删除,但 GORM 会将 DeletedAt 的值设置为当前时间,并且使用常规查询方法将无法再找到该数据。

MySQL 官方文档里 唯一提到的“零值”是 0000-00-00 00:00:00。
在这里插入图片描述

MySQL permits you to store a “zero” value of ‘0000-00-00’ as a “dummy date.” In some cases, this is more convenient than using NULL values, and uses less data and index space. To disallow ‘0000-00-00’, enable the NO_ZERO_DATE mode.
MySQL 允许您将 ‘0000-00-00’ 的“零”值存储为“虚拟日期”。在某些情况下,这比使用 NULL 值更方便,并且使用的数据和索引空间更少。要禁止 ‘0000-00-00’,请启用 NO_ZERO_DATE 模式。

用 1970-01-01 00:00:00 代替 0000-00-00 00:00:00”是 社区/业务代码里的一种常见做法。完全取决于你的业务约定。

一般开发者为了避免 0000-00-00 00:00:00 带来的数据库严格模式报错、又希望保留“非 NULL”语义时,自发采用的一个“社区惯例”。

但是把“空/未知/缺失”时间写成 1970-01-01 00:00:00 既不会触发数据库 “zero date” 报错,又很容易被开发者一眼识别为“空值”。

这个做法最早在大量开源项目、博客、Stack Overflow 回答里被反复引用,久而久之就成了“约定俗成”。

设计思路:

deleted_at DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00'

1970-01-01 00:00:00 是Unix时间戳的起点,明显不是真实的删除时间,具有特殊意义,不会与实际删除时间冲突。

好处:
✅ 支持唯一约束:同一企业内可以有多个同名的已删除记录,但只能有一个未删除的
支持唯一约束:在多租户应用(如企业系统)中,您可能需要在租户内对名称等字段施加唯一约束。通过软删除,已删除记录保留其数据但被标记为非活跃。该方案允许同一名称的多个“已删除”记录(例如,用于历史版本),同时确保只有一个活跃(未删除)记录。这可以通过在复合唯一索引中包含 deleted_at 来实现。

✅ 索引效率高:deleted_at字段有固定值,索引查询更高效
固定、非 NULL 值如 Unix 纪元能够更好地利用索引。筛选活跃记录的查询(例如 WHERE deleted_at = ‘1970-01-01 00:00:00’)可以更有效地利用索引,而不是使用 IS NULL 或布尔标志的查询,后者在某些情况下可能需要全表扫描。这在高流量系统中会导致更快的读取。

✅ 历史兼容:'1970-01-01’是Unix时间戳起点,语义清晰
Unix 纪元是一个永恒的标准,确保长期兼容性。它不太可能与未来的时间戳冲突,并且与 Python 或 Java 等语言的 Unix 时间戳原生处理很好整合。此外,它避免了时区或夏令时的问题,因为它是一个固定点
所有主流语言和数据库都能无歧义地解析这个常量。

与布尔标志(如 is_deleted)相比,这种基于时间戳的方法提供了更丰富的信息元数据(例如,何时 被删除),而无需额外字段。

实际演示:在 SQL 中实现软删除

步骤 1:创建表

CREATE TABLE enterprises (
    id INT AUTO_INCREMENT PRIMARY KEY,
    tenant_id INT NOT NULL,
    name VARCHAR(255) NOT NULL,
    deleted_at DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00',
    -- 包含 deleted_at 的复合唯一索引,用于软唯一性
    UNIQUE KEY unique_name_per_tenant (tenant_id, name, deleted_at)
);

步骤 2:插入记录

-- 插入一个活跃的企业
INSERT INTO enterprises (tenant_id, name) VALUES (1, 'Acme Corp');

-- 通过更新 deleted_at “删除”它
UPDATE enterprises SET deleted_at = NOW() WHERE id = 1;

-- 插入同一名称的新活跃记录(允许,因为前一个已“删除”)
INSERT INTO enterprises (tenant_id, name) VALUES (1, 'Acme Corp');

步骤 3:查询活跃记录

-- 只获取活跃(未删除)的企业
SELECT * FROM enterprises WHERE deleted_at = '1970-01-01 00:00:00';

-- 获取已删除记录用于审计
SELECT * FROM enterprises WHERE deleted_at > '1970-01-01 00:00:00';
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西京刀客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值