从手动 updated_at = NOW () 到 Mysql自动处理 ON UPDATE CURRENT_TIMESTAMP 又交给 GORM 维护
每次 UPDATE 都手动塞一句 updated_at = NOW(),或者在 Go 里写 user.UpdatedAt = time.Now()。
烦!
现在:把 updated_at 彻底扔给 MySQL 自己维护,省代码、少踩坑、还靠谱。
MySQL 原生自带,一行配置搞定 ON UPDATE CURRENT_TIMESTAMP
MySQL 本身就支持字段自动更新时间,不需要触发器、不需要框架钩子,建表时字段上加个属性就行。
标准写法长这样:
`updated_at` DATETIME NOT NULL
DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP
COMMENT '最后更新时间'
就两个规则,跑一遍就记住了:
DEFAULT CURRENT_TIMESTAMP:INSERT 的时候如果你没传这个字段,数据库自动填当前时间ON UPDATE CURRENT_TIMESTAMP:这一行任何字段的值发生变化,数据库自动把这个字段刷成最新时间
时间维护这件事,从业务代码手里接过来,下沉到数据库层去做。
注意:
- 用 DATETIME,别用 TIMESTAMP
TIMESTAMP 有 2038 年上限,时区处理也一堆坑。
DATETIME 完全支持 DEFAULT 和 ON UPDATE 特性,生产环境直接选 DATETIME。
- 字段值没变化时,不会更新
ON UPDATE 触发的前提是「行数据真的发生了改变」。
如果你 UPDATE 的值和原值一模一样,数据库会认为数据没变化,updated_at 不会刷新。
跑一下看实际效果
拿一张用户表演示,先建表:
CREATE TABLE t_user (
user_id INT PRIMARY KEY AUTO_INCREMENT,
user_name VARCHAR(32) NOT NULL,
status VARCHAR(20) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
插一条数据,不指定时间:
INSERT INTO t_user (user_name, status) VALUES ('alice', 'active');
查一下:
+---------+-----------+--------+---------------------+---------------------+
| user_id | user_name | status | created_at | updated_at |
+---------+-----------+--------+---------------------+---------------------+
| 1 | alice | active | 2026-07-05 10:00:00 | 2026-07-05 10:00:00 |
+---------+-----------+--------+---------------------+---------------------+
两个时间都自动填上了,不用你写任何代码。
再更新一下用户名:
UPDATE t_user SET user_name = 'alice_new' WHERE user_id = 1;
再查:
+---------+-----------+--------+---------------------+---------------------+
| user_id | user_name | status | created_at | updated_at |
+---------+-----------+--------+---------------------+---------------------+
| 1 | alice_new | active | 2026-07-05 10:00:00 | 2026-07-05 10:01:00 |
+---------+-----------+--------+---------------------+---------------------+
updated_at 自己变了。
GORM 不是会自动维护 UpdatedAt 吗,会不会和数据库冲突?
结构体不用动,更新时主动忽略这个字段就行。
模型结构体保留不动
gorm.Model 自带的 UpdatedAt 字段直接留着,查询、序列化、返回前端都正常用,啥也不用改:
type User struct {
gorm.Model
Name string `gorm:"size:32;comment:用户名"`
Status string `gorm:"size:20;comment:状态"`
}
关键一步:更新时 Omit 掉
部分 GORM 版本会默认把 UpdatedAt 拼进 UPDATE 语句里,这样就会覆盖数据库的自动赋值,等于白配置。
最稳妥的写法,所有更新统一加 Omit("updated_at"),彻底把这个字段交给数据库管:
db.Model(&user).Omit("updated_at").Updates(map[string]any{
"name": "alice_new",
"status": "disabled",
})
GORM 自动维护 updated_at?
如果 MySQL 中不加 ON UPDATE CURRENT_TIMESTAMP,把时间维护交给 GORM,只需要定义好结构体字段,什么都不用额外配置,GORM 就会在执行 UPDATE 时自动把当前时间拼进 SQL 中。
1. 使用 gorm.Model(自带支持)
如果你直接继承了 gorm.Model,里面已经包含了定义好的 UpdatedAt:
type User struct {
gorm.Model
Name string
Status string
}
2. 自定义结构体(使用 tag 或约定)
如果是自定义字段,只要字段叫 UpdatedAt(类型为 time.Time),或者加上 autoUpdateTime 标签即可:
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Status string
CreatedAt time.Time
// GORM 默认识别 UpdatedAt 字段,也可以显式声明 tag:
UpdatedAt time.Time `gorm:"autoUpdateTime"`
}
如果 GORM 与 Mysql 都进行了自动化 updata 了呢?
GORM 的update会“覆盖”MySQL 的时间
当你用 GORM 执行更新操作时(例如 db.Model(&user).Updates(...)):
- GORM 在应用层先生成一个时间:比如
2026-07-26 23:15:00.100。 - GORM 拼进 SQL 中:生成
UPDATE t_user SET name='alice', updated_at='2026-07-26 23:15:00' WHERE id=1;。 - MySQL 执行该 SQL:因为 SQL 语句里已经显式指定了
updated_at的值,MySQL 会直接使用你传过来的这个时间,不再触发ON UPDATE CURRENT_TIMESTAMP的自动计算。
最终落盘的时间是 Go 应用程序生成的时间。
如果你的项目主要是 Go 程序在读写数据库,直接交给 GORM 维护(去掉 MySQL 的 ON UPDATE 特性)反而更省心,也是大部分 Gorm 用户的标准做法。