PostgreSQL 和 MySQL 中的软删除
软删除是一种数据库技术,用于标记记录为已删除,而不会实际从数据库中删除它们。与其永久删除数据,不如使用一个标志或一个单独的列来指示记录是“已删除”还是不再活动。这种方法允许在需要时恢复或还原已删除的数据。
介绍
软删除允许将行标记为已删除,而不会实际从数据库中删除它们。您可以通过使用辅助标志列并修改查询以检查标志值来实现这一点。
例如,要使用 deleted_at timestamptz
列作为标志来软删除一行
UPDATE users SET deleted_at = now() WHERE id = 1
要选择未删除(活动)的行
SELECT * FROM users WHERE deleted_at IS NULL
通过实现软删除,您可以在数据库中保留已删除的数据,从而允许将来进行潜在的检索或分析。它还通过保留与已删除记录的关系和引用来维护数据完整性。但是,需要注意的是,软删除会占用存储空间,因此请考虑定期清除或归档不再需要的数据。
使用表视图
您还可以使用表视图来实现软删除。假设以下表模式
CREATE TABLE all_users (
id int8 PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
name varchar(500),
created_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz
);
您可以创建一个视图,该视图省略已删除的用户
CREATE VIEW users AS
SELECT * FROM all_users
WHERE deleted_at IS NULL
PostgreSQL 视图支持插入和删除,没有任何问题,因此您可以在模型中使用它们
type User struct {
bun.BaseModel `bun:"users"`
ID uint64
Name string
}
要查询已删除的行,请使用 ModelTableExpr
来更改表
var deletedUsers []User
err := db.NewSelect().
Model(&deletedUsers).
ModelTableExpr("all_users").
Where("deleted_at IS NOT NULL").
Scan(ctx)
使用 Bun 模型
Bun 支持使用 time.Time
列作为标志来实现软删除,该标志报告行是已删除还是未删除。Bun 会自动调整查询以检查标志。
要在模型上启用软删除,请添加带有 soft_delete
标签的 DeletedAt
字段
type User struct {
ID int64
CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
DeletedAt time.Time `bun:",soft_delete,nullzero"`
}
对于此类模型,Bun 会更新行而不是删除它们
_, err := db.NewDelete().Model(user).Where("id = ?", 123).Exec(ctx)
UPDATE users SET deleted_at = current_timestamp WHERE id = 123
Bun 还自动从 SELECT
查询结果中排除软删除的行
err := db.NewSelect().Model(&users).Scan(ctx)
SELECT * FROM users WHERE deleted_at IS NULL
要选择软删除的行
err := db.NewSelect().Model(&users).WhereDeleted().Scan(ctx)
SELECT * FROM users WHERE deleted_at IS NOT NULL
要选择所有行,包括软删除的行
err := db.NewSelect().Model(&users).WhereAllWithDeleted().Scan(ctx)
SELECT * FROM users
最后,要实际从数据库中删除行,无论之前是否已软删除
db.NewDelete().Model(user).Where("id = ?", 123).ForceDelete().Exec(ctx)
DELETE FROM users WHERE id = 123
唯一索引
将软删除与唯一索引一起使用可能会导致插入查询冲突,因为软删除的行与普通行一样包含在唯一索引中。
对于某些 DBMS,您可以将软删除的行从索引中排除
CREATE UNIQUE INDEX index_name ON table (column1) WHERE deleted_at IS NULL;
或者,您可以使用 coalesce
函数将 NULL
时间转换为 1970-01-01 00:00:00+00:00
,将 deleted_at
列包含到索引列中,因为 NULL
不等于任何其他值,包括它本身
CREATE UNIQUE INDEX index_name ON table (column1, coalesce(deleted_at, '1970-01-01 00:00:00'))
如果您的 DBMS 不允许在索引列中使用表达式,您可以配置 Bun 通过删除 nullzero 选项将零时间追加为 1970-01-01 00:00:00+00:00
type User struct {
ID int64
CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
- DeletedAt time.Time `bun:",soft_delete,nullzero"`
+ DeletedAt time.Time `bun:",soft_delete"`
}