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

📅 2026/7/9 1:58:58
别再手动写 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_TIMESTAMPINSERT 的时候如果你没传这个字段数据库自动填当前时间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, })统一成三条规范团队直接执行新增数据不用手动设 UpdatedAt数据库默认填更新数据代码里绝对不出现UpdatedAt time.Now()所有更新语句统一 Omit (updated_at)几个容易踩的细节提前说清楚1. 用 DATETIME别用 TIMESTAMPTIMESTAMP 有 2038 年上限时区处理也一堆坑。DATETIME 完全支持 DEFAULT 和 ON UPDATE 特性生产环境直接选 DATETIME。2. 别加 autoUpdateTime 标签如果结构体字段上写了gorm:autoUpdateTimeGORM 会强制在代码层更新时间直接盖掉数据库规则。想用数据库自动维护这个标签必须删掉。3. 字段值没变化时不会更新ON UPDATE触发的前提是「行数据真的发生了改变」。如果你 UPDATE 的值和原值一模一样数据库会认为数据没变化updated_at不会刷新。这不是 bug是正常行为。如果业务上强制要求刷新手动在 SQL 里带上updated_at NOW()即可。最后说两句手动维护updated_at这种事本质就是业务代码在替底层兜底。吃力不讨好还容易出问题。MySQL 这个特性成熟得不能再成熟了零成本接入一劳永逸解决时间不准的问题。做后端开发有个很实用的原则能下沉到数据库、框架层做的事就别往业务代码里塞。代码越少、越专注业务坑就越少维护起来也越省心。