从手动 updated_at = NOW () 到 Mysql自动处理 ON UPDATE CURRENT_TIMESTAMP 又交给 GORM 维护

315 阅读4分钟

从手动 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(...)):

  1. GORM 在应用层先生成一个时间:比如 2026-07-26 23:15:00.100
  2. GORM 拼进 SQL 中:生成 UPDATE t_user SET name='alice', updated_at='2026-07-26 23:15:00' WHERE id=1;
  3. MySQL 执行该 SQL:因为 SQL 语句里已经显式指定updated_at 的值,MySQL 会直接使用你传过来的这个时间,不再触发 ON UPDATE CURRENT_TIMESTAMP 的自动计算。

最终落盘的时间是 Go 应用程序生成的时间

如果你的项目主要是 Go 程序在读写数据库,直接交给 GORM 维护(去掉 MySQL 的 ON UPDATE 特性)反而更省心,也是大部分 Gorm 用户的标准做法。