如何在表结构中加入软删除字段?Deleted?At布尔与时间戳

核心诉求是「可追溯」和「可恢复」,布尔字段仅表状态而无法记录删除时间、次数,时间戳\_Deleted\_At支持按时间筛选、审计、自动清理;GORM需显式配置字段映射与全局设置,且必须加索引并确保SQL显式过滤\_Deleted\_At IS NULL。

为什么不用 _Deleted_At 布尔字段而要用时间戳

软删除的核心诉求是「可追溯」和「可恢复」,布尔字段(比如 is_deleted)只能表达“删了”或“没删”,但无法回答“什么时候删的”“删之前最后更新过几次”。时间戳字段 _deleted_at 天然支持按删除时间筛选、分页、审计,也方便后续加逻辑(如 30 天后自动清理)。用布尔字段看似简单,后期补日志、加策略、查问题时反而要改表、补数据、修代码,得不偿失。

常见错误现象:is_deleted TINYINT(1) 字段上线后,运营突然要查某条记录“哪天被误删”,结果发现全表都是 1,没有时间信息,只能翻日志或求 DBA 恢复 binlog。

  • _Deleted_At 类型必须为 DATETIMETIMESTAMP,NOT NULL 默认为 NULL(未删除状态)
  • 不要用 BOOLEANTINYINT 模拟,MySQL 的 BOOLEAN 实际是 TINYINT(1) 别名,语义模糊且不可扩展
  • 如果业务要求极低延迟写入,TIMESTAMPDATETIME 省 1 字节,但注意时区行为差异:前者存 UTC、读取转本地;后者原样存取

Go + GORM 中正确使用 _Deleted_At 软删除

GORM v2 默认开启软删除,只要字段名是 DeletedAt 就自动识别。但你命名成 _Deleted_At?它不会认——GORM 不支持下划线+驼峰混合的软删除字段名,除非显式配置。

使用场景:你不想改已有字段命名规范,又想用 GORM 自带的 Unscoped()Where("deleted_at IS NULL") 等能力。

  • 在模型 struct 中用 gorm:"column:_Deleted_At" 映射,并加 gorm.DeletedAt 标签:
    type User struct {
        ID        uint      `gorm:"primaryKey"`
        Name      string    `gorm:"not null"`
        _Deleted_At *time.Time `gorm:"column:_Deleted_At;index"`
    }
  • 必须手动注册软删除字段:db.SetupJoinTable(&User{}, "_Deleted_At", ...) 不行,正确做法是全局设置:gorm.Config{NowFunc: func() time.Time { return time.Now().UTC() }} 并确保迁移时字段允许 NULL
  • 调用 Delete() 时,GORM 只会 SET _Deleted_At = NOW(),不会删行;但如果你写了 db.Unscoped().Delete(&u),它仍会物理删除——这点容易踩坑,尤其在定时任务里混用

SQL 查询时绕不开的 _Deleted_At IS NULL 条件

所有「查有效数据」的 SQL 都得显式过滤,否则 SELECT * 会把已删除的也捞出来。ORM 可能帮你加,但原生 SQL、视图、报表脚本、DBA 直连查询都不会自动加——这是最常被忽略的一环。

性能影响:给 _Deleted_At 加索引几乎必做,否则 WHERE _Deleted_At IS NULL 在大表上是全表扫描。

  • 建索引命令:CREATE INDEX idx_users_deleted_at ON users (_Deleted_At);
  • 复合查询慎用 OR _Deleted_At IS NOT NULL,MySQL 5.7+ 对 IS NULL 索引优化较好,但 OR 容易让索引失效
  • 视图里如果漏写 WHERE _Deleted_At IS NULL,下游所有依赖该视图的服务都会看到脏数据,且难以定位源头

迁移旧表加 _Deleted_At 的三个关键动作

不是 ALTER TABLE ADD COLUMN 就完事。老数据默认应视为「未删除」,但有些表可能已有逻辑删除标记(如 status = 'deleted'),这时要对齐语义。

  • 先加字段:ALTER TABLE orders ADD COLUMN _Deleted_At DATETIME NULL DEFAULT NULL;
  • 再批量回填:若原有 status 字段含删除态,用 UPDATE orders SET _Deleted_At = NOW() WHERE status = 'deleted';;否则跳过 UPDATE,保持全 NULL
  • 最后加约束(可选但推荐):ALTER TABLE orders ADD CHECK (_Deleted_At IS NULL OR _Deleted_At 防止未来写入未来时间

容易被忽略的是应用层缓存。加完字段后,如果 Redis 里存着旧结构的 JSON(不含 _Deleted_At),反序列化时可能 panic 或静默丢字段——得同步清理或升级缓存 schema。

文章来自机圈观察员网,发布者:,转载请注明出处:https://www.jqgcy.com/xitongjiaocheng/127198.html

上一篇 2026-07-20 06:52
为HTML中的按钮添加波浪起伏动效的技巧
下一篇 2026-07-20 07:00

相关推荐