软删除设计:为什么使用 deleted_at = ‘1970-01-01 00:00:00‘ 表示未删除?
文章目录
- 软删除设计: 为什么使用 deleted_at = '1970-01-01 00:00:00' 表示未删除?
- 什么是软删除
- 为什么使用 deleted_at = '1970-01-01 00:00:00' 表示未删除?
- 实际演示:在 SQL 中实现软删除
软删除设计: 为什么使用 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';