还在使用 Jaeger/Sentry?Uptrace 是一个用于 OpenTelemetry 的 开源 APM,您可以使用它来监控应用程序并设置警报以通过电子邮件、Slack、Telegram 等方式接收通知。

PostgreSQL 和 MySQL 中的软删除

软删除是一种数据库技术,用于标记记录为已删除,而不会实际从数据库中删除它们。与其永久删除数据,不如使用一个标志或一个单独的列来指示记录是“已删除”还是不再活动。这种方法允许在需要时恢复或还原已删除的数据。

Soft deletes

介绍

软删除允许将行标记为已删除,而不会实际从数据库中删除它们。您可以通过使用辅助标志列并修改查询以检查标志值来实现这一点。

例如,要使用 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"`
}