核心诉求是「可追溯」和「可恢复」,布尔字段仅表状态而无法记录删除时间、次数,时间戳\_Deleted\_At支持按时间筛选、审计、自动清理;GORM需显式配置字段映射与全局设置,且必须加索引并确保SQL显式过滤\_Deleted\_At IS NULL。
为什么不用 _Deleted_At 布尔字段而要用时间戳
软删除的核心诉求是「可追溯」和「可恢复」,布尔字段(比如 is_deleted)只能表达“删了”或“没删”,但无法回答“什么时候删的”“删之前最后更新过几次”。时间戳字段 _deleted_at 天然支持按删除时间筛选、分页、审计,也方便后续加逻辑(如 30 天后自动清理)。用布尔字段看似简单,后期补日志、加策略、查问题时反而要改表、补数据、修代码,得不偿失。
常见错误现象:is_deleted TINYINT(1) 字段上线后,运营突然要查某条记录“哪天被误删”,结果发现全表都是 1,没有时间信息,只能翻日志或求 DBA 恢复 binlog。
-
_Deleted_At类型必须为DATETIME或TIMESTAMP,NOT NULL 默认为NULL(未删除状态) - 不要用
BOOLEAN或TINYINT模拟,MySQL 的BOOLEAN实际是TINYINT(1)别名,语义模糊且不可扩展 - 如果业务要求极低延迟写入,
TIMESTAMP比DATETIME省 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