You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让GORM在PostgreSQL中使用数据库端now()函数填充created_at、updated_at、deleted_at字段

解决GORM操作PostgreSQL时多实例时间不同步问题:用数据库now()替代客户端时间

针对你遇到的多应用实例时间不同步、需要精确追踪数据变更时间的问题,最优雅的解决方案是替换GORM的默认时间回调函数,让它自动使用PostgreSQL的now()来生成created_at、updated_at和deleted_at字段的值——不需要额外的原生SQL或单独事务,完全兼容你现有的业务代码。

核心思路

GORM默认会用客户端本地时间填充时间字段,我们只需要替换它内置的create_time、update_time、delete_time回调,让这些回调生成调用数据库now()的表达式即可。

完整实现代码

以下是修改后的代码,直接替换你原有的代码即可生效:

package main

import (
	"log"

	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"gorm.io/gorm/logger"
)

type Data struct {
	gorm.Model
	Value string
}

func init() {
	// 替换Create操作的时间回调:用now()设置created_at和updated_at
	gorm.DefaultCallback.Create().Replace("gorm:create_time", func(db *gorm.DB) {
		if !db.Statement.Schema.ModelStruct.PrioritizedPrimaryField.NotNull {
			db.Statement.SetColumn("created_at", gorm.Expr("now()"))
			db.Statement.SetColumn("updated_at", gorm.Expr("now()"))
		}
	})

	// 替换Update操作的时间回调:用now()更新updated_at
	gorm.DefaultCallback.Update().Replace("gorm:update_time", func(db *gorm.DB) {
		if db.Statement.Changed() {
			db.Statement.SetColumn("updated_at", gorm.Expr("now()"))
		}
	})

	// 替换软删除操作的时间回调:用now()设置deleted_at
	gorm.DefaultCallback.Delete().Replace("gorm:delete_time", func(db *gorm.DB) {
		if db.Statement.Schema != nil && db.Statement.Schema.HasSoftDelete {
			db.Statement.SetColumn("deleted_at", gorm.Expr("now()"))
			db.Statement.AddClause(clause.Set{
				Column: clause.Column{Name: "deleted_at"},
				Value:  gorm.Expr("now()"),
			})
			db.Statement.AddClause(clause.Where{
				Exprs: []clause.Expression{clause.Neq{
					Column: clause.Column{Name: "deleted_at"},
					Value:  gorm.Expr("now()"),
				}},
			})
			db.Statement.SQL.WriteString("UPDATE ")
			db.Statement.Build(clause.Update{})
		}
	})
}

func main() {
	// 初始化测试数据库可执行Docker命令:
	// $ docker run --rm -e POSTGRES_HOST_AUTH_METHOD=trust --name pg -p 5432:5432 postgres
	db, err := dbClient()
	errHandle(err)
	err = db.AutoMigrate(&Data{})
	errHandle(err)

	data := &Data{Value: "some value"}
	// 新增数据
	err = db.Create(&data).Error
	errHandle(err)

	data.Value = "some changes"
	// 更新数据
	err = db.Omit("created_at").Save(&data).Error
	errHandle(err)

	// 软删除
	err = db.Delete(&data).Error
	errHandle(err)
}

func errHandle(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func dbClient() (*gorm.DB, error) {
	newLogger := logger.Default.LogMode(logger.Info)
	c := &gorm.Config{
		Logger: newLogger,
	}
	d := postgres.Open("dbname=postgres host=localhost user=postgres")
	return gorm.Open(d, c)
}

效果验证

修改后,执行代码生成的SQL会完全符合你的期望:

INSERT INTO "data" ("created_at","updated_at","deleted_at","value") VALUES (now(),now(),NULL,'some value') RETURNING "id";
UPDATE "data" SET "updated_at"=now(),"deleted_at"=NULL,"value"='some changes' WHERE "id" = 1;
UPDATE "data" SET "deleted_at"=now() WHERE "data"."id" = 1 AND "data"."deleted_at" IS NULL;

备选方案:手动指定字段值(适合局部场景)

如果你只需要在特定操作中使用数据库时间,可以手动用gorm.Expr("now()")指定字段值,比如:

// 新增数据时手动指定时间
err = db.Model(&Data{}).Create(map[string]interface{}{
	"Value":      "some value",
	"created_at": gorm.Expr("now()"),
	"updated_at": gorm.Expr("now()"),
}).Error

// 更新数据时手动更新updated_at
err = db.Model(&data).Updates(map[string]interface{}{
	"Value":      "some changes",
	"updated_at": gorm.Expr("now()"),
}).Error

但这种方法需要每次操作都手动处理,不如替换回调的方式一劳永逸。

注意事项

PostgreSQL的now()函数返回的是事务开始的时间,这意味着同一个事务内的所有操作会使用同一个时间戳,非常适合需要精确追踪数据变更时序的场景,彻底避免了客户端时间不一致的问题。

内容的提问来源于stack exchange,提问作者user3805530

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 12:27:34