别再手动写 updated_at了,这坑我踩过

别再手动写 updated_at = NOW () 了,这坑我踩过

做后端写业务表,updated_at 几乎是标配。谁都知道它用来记数据最后修改时间,排查问题、对账、做增量同步都靠它。

但绝大多数人写代码的习惯,其实从一开始就埋了雷:每次 UPDATE 都手动塞一句 updated_at = NOW(),或者在 Go 里写 user.UpdatedAt = time.Now()

我早年也这么干,直到线上出过好几次「数据明明改了,更新时间却没动」的破事,排查到最后发现要么是同事写脚本漏了,要么是运维直接跑 SQL 改的数据,业务代码根本兜不住。

这篇不说废话,就讲一件事:把 updated_at 彻底扔给 MySQL 自己维护,省代码、少踩坑、还靠谱。

先看最常见的写法,问题藏在哪

拿 Go + GORM 举例,几乎每个项目都能看到这种代码:

go 复制代码
// 修改用户信息
user.Name = "新名字"
user.Avatar = "/imgs/new.png"
user.UpdatedAt = time.Now() // 每次都要记得加

db.Model(&user).Updates(user)

单看这一段没毛病。但项目一迭代、人一多,问题全出来了。

第一个坑:总有地方会忘写

更新逻辑散得到处都是:接口里改、定时任务批量刷、MQ 消费回调改、导入脚本批量更新。只要有一个人漏写一次赋值,就会出现「数据变了,时间没变」。后面查变更记录、做数据审计,直接抓瞎。

第二个坑:很多场景代码根本管不到

手动赋值只能覆盖业务代码主动更新的情况。运维线上直接执行 UPDATE、数据库触发器联动修改、数据同步工具从别的库同步过来、甚至有人直接在客户端改表...... 这些场景完全不走业务代码,updated_at 永远躺在那不动。

最后表里面时间有的准有的不准,字段等于白建。

第三个坑:纯纯的重复垃圾代码

一个项目几十上百个更新方法,每处都要重复写一遍赋值。这行代码不产生任何业务价值,既不实现功能,也不优化逻辑,就是纯粹的模板代码。写得多,维护成本就高,毫无意义。

根治方案:MySQL 原生自带,一行配置搞定

MySQL 本身就支持字段自动更新时间,不需要触发器、不需要框架钩子,建表时字段上加个属性就行。

标准写法长这样,生产环境直接抄:

sql 复制代码
`updated_at` DATETIME NOT NULL 
    DEFAULT CURRENT_TIMESTAMP 
    ON UPDATE CURRENT_TIMESTAMP 
    COMMENT '最后更新时间'

就两个规则,跑一遍就记住了:

  • DEFAULT CURRENT_TIMESTAMP:INSERT 的时候如果你没传这个字段,数据库自动填当前时间
  • ON UPDATE CURRENT_TIMESTAMP:这一行任何字段的值发生变化,数据库自动把这个字段刷成最新时间

说白了就是:时间维护这件事,从业务代码手里接过来,下沉到数据库层去做。底层机制永远比人工兜底靠谱。

跑一下看实际效果

拿一张用户表演示,先建表:

sql 复制代码
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
);

插一条数据,不指定时间:

sql 复制代码
INSERT INTO t_user (user_name, status) VALUES ('alice', 'active');

查一下:

plaintext 复制代码
+---------+-----------+--------+---------------------+---------------------+
| user_id | user_name | status | created_at          | updated_at          |
+---------+-----------+--------+---------------------+---------------------+
|       1 | alice     | active | 2026-07-05 10:00:00 | 2026-07-05 10:00:00 |
+---------+-----------+--------+---------------------+---------------------+

两个时间都自动填上了,不用你写任何代码。

再更新一下用户名:

sql 复制代码
UPDATE t_user SET user_name = 'alice_new' WHERE user_id = 1;

再查:

plaintext 复制代码
+---------+-----------+--------+---------------------+---------------------+
| 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 自己变了。UPDATE 语句里完全没提这个字段,数据库帮你做了。

不管你是代码执行、脚本跑、手动敲 SQL,只要行数据改了,时间一定会刷新。这才是真正的万无一失。

Go + GORM 怎么适配,别踩冲突的坑

很多人顾虑:GORM 不是会自动维护 UpdatedAt 吗,会不会和数据库冲突?

结论先说:结构体不用动,更新时主动忽略这个字段就行

模型结构体保留不动

gorm.Model 自带的 UpdatedAt 字段直接留着,查询、序列化、返回前端都正常用,啥也不用改:

go 复制代码
type User struct {
    gorm.Model
    Name   string `gorm:"size:32;comment:用户名"`
    Status string `gorm:"size:20;comment:状态"`
}

关键一步:更新时 Omit 掉

部分 GORM 版本会默认把 UpdatedAt 拼进 UPDATE 语句里,这样就会覆盖数据库的自动赋值,等于白配置。

最稳妥的写法,所有更新统一加 Omit("updated_at"),彻底把这个字段交给数据库管:

go 复制代码
db.Model(&user).Omit("updated_at").Updates(map[string]any{
    "name":   "alice_new",
    "status": "disabled",
})

统一成三条规范,团队直接执行

  1. 新增数据:不用手动设 UpdatedAt,数据库默认填
  2. 更新数据:代码里绝对不出现 UpdatedAt = time.Now()
  3. 所有更新语句统一 Omit ("updated_at")

几个容易踩的细节,提前说清楚

1. 用 DATETIME,别用 TIMESTAMP

TIMESTAMP 有 2038 年上限,时区处理也一堆坑。

DATETIME 完全支持 DEFAULT 和 ON UPDATE 特性,生产环境直接选 DATETIME。

2. 别加 autoUpdateTime 标签

如果结构体字段上写了 gorm:"autoUpdateTime",GORM 会强制在代码层更新时间,直接盖掉数据库规则。

想用数据库自动维护,这个标签必须删掉。

3. 字段值没变化时,不会更新

ON UPDATE 触发的前提是「行数据真的发生了改变」。

如果你 UPDATE 的值和原值一模一样,数据库会认为数据没变化,updated_at 不会刷新。

这不是 bug,是正常行为。如果业务上强制要求刷新,手动在 SQL 里带上 updated_at = NOW() 即可。

最后说两句

手动维护 updated_at 这种事,本质就是业务代码在替底层兜底。吃力不讨好,还容易出问题。

MySQL 这个特性成熟得不能再成熟了,零成本接入,一劳永逸解决时间不准的问题。

做后端开发有个很实用的原则:能下沉到数据库、框架层做的事,就别往业务代码里塞。代码越少、越专注业务,坑就越少,维护起来也越省心。

相关推荐
ShineWinsu1 小时前
对于Linux:网络基础的解析
linux·网络·面试·udp·笔试·ip·tcp
zhangxingchao1 小时前
AI大模型核心七:从 Workflow、RAG、记忆治理到幂等性
前端·人工智能·后端
咖啡星人k2 小时前
从Vibe Coding到Prompt Engineering:AI编程的新技能树
面试·职场和发展·ai编程
犀利豆3 小时前
AI in Harness(二)
java·人工智能·后端
IT_陈寒3 小时前
Python装饰器竟然偷偷改了函数名?这个坑我爬了三天
前端·人工智能·后端
bkl_92133 小时前
GPT-Image-2 文生图:如何稳定生成完整的人物全身照?
后端·gpt·restful
jy3 小时前
支付后端研发的 AI 工作流实践:巡检、排查、开发全流程落地
后端·ai编程
GoGeekBaird4 小时前
我最近在写 BeeWeave,想把 Agent 用过的上下文留住
后端·github·ai编程