尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

MySQL时间类型选型避坑:timestamp与datetime的底层差异、时区陷阱与业务场景指南

MySQL时间类型选型避坑:timestamp与datetime的底层差异、时区陷阱与业务场景指南 1. 一次半夜爬起来改表的真实事故让我重新审视选型1.1 事故现场统计报表凭空少了 8 小时如果一个技术问题能让人半夜爬起来改表那它多半不是选 timestamp 还是 datetime这么简单。我接手某跨时区订单系统第二周运营就甩了一张日报截图过来昨天 00:00 到 08:00 的订单数据在报表里全是空白。后台订单表数据明明在created_at 字段长得也正常可页面和定时任务怎么都取不到那 8 个小时。当时第一反应是统计 SQL 写错了可单独把那串 SQL 拿到客户端跑一遍结果又能正常出来。几个人查了半小时没头绪最后某开发同学说了一句这表的时间列用的 timestamp会不会是时区问题我打开建表语句看了一眼果然created_at 用的是 TIMESTAMP 类型而统计 SQL 里用了一个固定字符串去卡时间边界。虽然当时还没完全定位但我心里基本有数了——问题大概率出在时间类型选型和部署环境时区的组合上两个因素一叠加就变成这种看起来哪里都没错、但结果全错的灵异事件。类似事故我在不同项目里见过至少三次表现形式各不相同有时是时间整体快了 8 小时有时是慢 8 小时有时是写入、读出的值不一致。抽丝剥茧之后几乎都会指向同一个源头——建表时对 timestamp 和 datetime 的理解只停留在一个 4 字节一个 8 字节没有真正搞懂它们对待时区的态度完全不同。1.2 为什么选时间类型这件小事值得专门写一篇很多同学觉得timestamp 和 datetime 的选型不就是查文档对比一下吗范围、字节数、时区规则文档写得清清楚楚。但文档不会告诉你的是这俩类型在真实部署环境里会被服务器时区、会话时区、JDBC 连接参数、应用框架本地时区至少四层因素反复影响。你选 timestamp 还是 datetime 只是第一步后面任何一层没对齐结果都可能被改得面目全非。另外这个选择还埋着几个容易忽略的坑timestamp 只能表示到 2038 年存生日、活动排期、长期合同时间都可能触发边界datetime 从 5.6.5 之后才支持自动初始化为当前时间早年用 MySQL 5.5、5.6 早期版本的老项目只能靠应用代码在插入前手动补时间需要毫秒级精度时两者都要加小数秒参数存储和查询写法也会跟着变。单独看都是小事凑到一起任何一个环节出错线上就可能在某个凌晨给你颜色看。所以我把这段时间积累的经验整理成了一套相对完整的选型避坑思路底层原理、部署陷阱、业务判断、完整排查链路、进阶注意事项都有。无论你是在设计新库还是在维护一个被时区折磨已久的老系统应该都能从中找到可用的部分。1.3 先说结论避免篇幅劝退我知道有人就想先要个答案。直接给一个基于大部分常规业务的默认建议优先考虑 DATETIME尤其是涉及跨时区、未来日期、长期归档的场景只有当你明确需要同一绝对时刻随用户时区自动切换显示并且能保证 2038 年之内不会用到才选 TIMESTAMP。当然这只是一个起点真实项目里还有各种边界条件后文我会把判断依据和例外情况都拆开讲清楚你可以根据自己的场景对照选择。2. timestamp 和 datetime 的底层差异一张表对齐2.1 存储空间和取值范围4 字节和 8 字节只是起点先把最基础的部分摆出来。很多人的记忆是timestamp 占 4 字节datetime 占 8 字节这个说法对老版本成立但对 MySQL 5.6.4 之后的版本需要修正。维度TIMESTAMPDATETIME存储大小无小数秒4 字节5.6.4 之前 8 字节5.6.4 起约 5 字节取值范围1970-01-01 00:00:01 UTC 至 2038-01-19 03:14:07 UTC1000-01-01 00:00:00 至 9999-12-31 23:59:59时区处理写入时按会话时区转成 UTC 存储读取时再转回会话时区显示不做时区转换存什么显示什么自动初始化很早就支持 DEFAULT CURRENT_TIMESTAMP5.6.5 起支持 DEFAULT CURRENT_TIMESTAMP、ON UPDATE CURRENT_TIMESTAMP存储空间不是简单的一句timestamp 更省。MySQL 5.6.4 之后DATETIME 不带小数秒时只需要约 5 字节比老的 8 字节明显降下来了而一旦要保留毫秒比如 DATETIME(3) 或 TIMESTAMP(3)两者都要在基础字节之上再加 1 到 3 字节。所以timestamp 一定更省空间这个说法只在无小数秒、海量数据场景下才有实际意义。取值范围则是真正的硬边界。TIMESTAMP 能表示的最晚时刻是 2038 年 1 月 19 日换算成东八区是那天 11:14:07。只要业务里要存 2038 年之后的日期例如长期合同到 2040 年、会员权益到 2050 年那就一个都不许碰 timestamp。这不是危言耸听是想清楚边界之后直接排除一个选项。2.2 时区处理机制为什么 timestamp 会擅自变脸这里是整个选型问题的核心也是绝大多数坑的发源地。TIMESTAMP 在存储层实际保存的是 UTC 时刻可以理解成一个绝对时间点。INSERT 的时候MySQL 先把写入值按当前会话时区换算成 UTC再存进 4 个字节SELECT 的时候再根据当前会话时区把 UTC 值转换成对应本地时间返回。打个比方TIMESTAMP 存的是地球自转到的绝对位置DATETIME 存的是墙上挂钟显示的那个数字。你在北京往表里写 2024-06-01 12:00:00如果用 TIMESTAMPMySQL 会先换算成 04:00:00 UTC 存起来在北京的会话时区下读出来又显示成 12:00:00看起来一切正常。可一旦有人把会话时区改成 Europe/London同一行数据读出来就变成了 05:00:00。DATETIME 没这个习惯写入 12:00:00任何会话时区下读出来都是 12:00:00。理解这一点很多灵异事件就说得通了代码没动、数据没变只是数据库服务器或连接串的时区设置变了TIMESTAMP 列读出来的值就整体平移了几个小时。反过来DATETIME 列虽然稳定但也意味着它无法自动表达同一个绝对时刻在不同时区的不同叫法。这是两个类型最本质的语义差异选型时首先要确认的就是你到底要记录那一刻还是那个数字。2.3 小数秒精度与默认值细节里的坑位再补充两个不容易注意的细节。第一小数秒精度。MySQL 5.6.4 开始TIMESTAMP 和 DATETIME 都支持毫秒、微秒精度类型名后面加圆括号数字范围 0 到 6例如 DATETIME(3) 表示精确到毫秒TIMESTAMP(6) 精确到微秒。对订单流水、日志、监控上报来说毫秒精度经常是刚需。但加了小数秒之后存储空间会增加查询写法也得更小心如果你用WHERE created_at 2024-06-01 12:00:00去匹配是匹配不到 12:00:00.123 这类带小数秒数据的必须用 BETWEEN 把上下边界写清楚。第二默认值。TIMESTAMP 很早就支持 DEFAULT CURRENT_TIMESTAMP插入时不传值就自动填当前时间DATETIME 在 MySQL 5.6.5 之前不支持很多老项目只能在应用层手动 set 时间。如果你维护的库还在 MySQL 5.5想给 DATETIME 加自动填充要么升级版本要么老老实实在代码里补。MySQL 8.0.13 之后 DATETIME 也可以写表达式默认值比如 DEFAULT (CURRENT_TIMESTAMP)。这里不展开全部写法但提醒一句如果哪天运维把 sql_mode 改成严格模式某些依赖隐式默认值的老表可能会直接报错升级之前先检查。3. 部署环境里的三层时区陷阱服务器、连接串、应用框架3.1 第一层服务器系统时区和 MySQL 全局时区类型本身再明白部署环境不干净照样出事。第一层最容易碰到的是 MySQL 所在服务器或容器的系统时区。MySQL 的全局 time_zone 默认是 SYSTEM意思是直接用操作系统时区。如果服务器是东八区全局时区就是东八区如果容器时区没设置很多基础镜像默认是 UTC那么 MySQL 的全局时区也是 UTC。问题在于大多数业务同学大脑默认都在东八区不会有人想到数据库服务器偷偷过的是 UTC 时间。你可以在客户端执行这几条命令快速确认SHOW VARIABLES LIKE time_zone; SELECT global.time_zone, session.time_zone; SELECT NOW();如果在服务器上执行date得到的系统时间和SELECT NOW()输出的结果差了好几个小时那第一层就已经歪了。对于 Docker 部署比较通用的做法是启动容器时显式指定时区环境变量比如-e TZAsia/Shanghai并且让 MySQL 配置里的default-time-zone也写到同一个值让全局时区从源头保持稳定。3.2 第二层连接会话时区与 JDBC 参数全局时区只决定了新建立的会话默认值真正影响读写的是会话时区。Java 程序通过连接池连 MySQL 时如果连接串里没有显式指定时区驱动通常又会基于应用所在 JVM 的默认时区或驱动本身的默认策略来设置会话。于是出现一个很常见的偏差MySQL 服务器是 UTC应用服务器是东八区连接串又没写 serverTimezone会话时区到底落到哪个值完全看驱动心情。以 MySQL Connector/J 8.x 为例连接串里常见的写法是jdbc:mysql://192.0.2.1:3306/dbname?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiserverTimezone 参数就是让驱动在建立连接时把会话时区设置为 Asia/Shanghai。曾经有个老版本驱动如果不配这个参数与 MySQL 8.0 连接时甚至可能直接抛异常后来版本改了默认策略但改出来的结果并不总是符合业务预期。所以我的建议是连接串里一定显式写时区不要靠驱动猜。另外一个隐蔽点是有些团队会用连接池初始化 SQL 的方式来固定会话时区-- 每次新建连接时执行 SET time_zone 08:00;这种方式在连接池场景下很有效但也要注意如果连接被复用很久或者有人在会话里手动改了时区后续查询都会受影响。排查的时候不要只盯 MySQL 配置文件要把每一层连接串、连接池配置、初始化 SQL 都翻一遍。3.3 第三层应用框架和 ORM 的隐性换算服务器和连接串都对齐了还有第三层应用框架自己可能在内存里做一轮时区换算。举个很典型的例子Java 应用里用 LocalDateTime 表示时间ORM 框架在写入数据库时会把 LocalDateTime 对象交给 JDBC 驱动。JDBC 驱动拿到之后是当作带时区的瞬时时间处理还是当作不带时区的本地字符串处理取决于驱动版本、连接参数和实体字段类型。如果实体里用的是 java.util.Date驱动通常会与 MySQL TIMESTAMP 做时区转换如果用的是 LocalDateTime有的驱动会直接按本地时间写出不再额外换算。这里的细微差别最终可能导致同一个时间值从应用层看是对的写进库之后却偏了若干小时。在这些框架层陷阱面前最有效的防御不是去背每个框架的转换规则而是统一约定让你们项目只有一种时间语义。我比较推荐的方式是应用内统一使用 UTC 的 Instant 或 LocalDateTime存 UTC来传递数据库里如果选 DATETIME 也存 UTC 数值展示层再按用户时区翻译。这样框架即使做了换算也只是在 UTC 到 UTC 之间原地打转不会引入偏差。三层时区陷阱总结如下层级常见偏差来源检查手段服务器/容器系统时区默认 UTCdate 命令、TZ 启动参数MySQL 全局/会话time_zone 未显式设置SHOW VARIABLES LIKE time_zone驱动/连接池serverTimezone 缺失或错误检查 JDBC URL、连接池初始化 SQL应用框架Date 与 LocalDateTime 语义混用统一 UTC 时间语义代码审查4. 业务场景下该怎么选按记录什么而非在哪个时区4.1 客观时刻和日历时间两种完全不同的业务语义绕完部署环境回到最初的问题业务到底该怎么选我的回答一直是不要问项目部署在哪个时区要问这一列到底要记录什么。这里把业务时间分成两大类。第一类是客观时刻指的是地球上某个绝对事件发生的时间点比如订单创建时间、支付成功时间、日志上报时间。这类时间不依赖任何人看它发送的那一刻是确定的。第二类是日历时间指的是某个日历上显示的数字比如用户的生日、课程表里的上课时间、门店的营业时间。这类时间描述的是在某个地区墙上的钟应该显示什么换到另一个时区它表示的实际时刻就变了但它的日历含义不变。TIMESTAMP 天然适合第一类因为它存的是绝对时刻读出来时可以按查看者的本地时区自动换算。DATETIME 天然适合第二类它老老实实存日历数字不随环境左右。如果你把第一类时间塞进 DATETIME就需要自己在应用层统一用 UTC 或其他约定否则就会出现东八区写入西五区读出同一行数据显示不同绝对时刻的混乱。反过来如果你把第二类时间塞进 TIMESTAMP用户生日在 2038 年之前还好说一旦遇到跨 2038 年直接溢出存不进去。4.2 一张场景对照表直接给你现成答案业务场景推荐类型理由订单创建时间、支付时间、流水时间TIMESTAMP 或 DATETIME(UTC)客观时刻若需要按用户时区展示TIMESTAMP 更省事日志、监控上报时间TIMESTAMP数据量大4 字节更省空间通常是客观时刻生日、纪念日DATETIME日历时间与时区无关且可能超出 2038活动开始时间、课程排期DATETIME属于日历/本地时间语义跨时区需要单独约定时区字段长期合同、会员到期日DATETIME避免 2038 边界对账、批量任务执行时间TIMESTAMP 或 DATETIME(UTC)尽量用 UTC 语义避免日切时间混乱分布式系统消息时间戳TIMESTAMP(3)/DATETIME(3)(UTC)需要毫秒精度并统一 UTC这张表是我个人在项目里反复验证过的结果但不是万能药。你会发现有几个场景我写了两个选项核心原因是用 DATETIME 统一存 UTC 值也是一种非常通用、稳妥的做法。很多中大型团队约定所有时间列都用 DATETIME并约定数值一律为 UTC这样即使未来要调整时区策略历史数据也不会因为 timestamp 自动换算而产生歧义。4.3 我的默认选型组合与理由如果今天让我从零设计一张表没有特殊约束我会给出这样一个组合与绝对时刻相关的时间列统一用 TIMESTAMP 或 DATETIME(UTC)并在字段注释里写明存储值统一为 UTC。与日历显示相关的列统一用 DATETIME注释里写明本地日历时间与时区无关。所有需要毫秒级别的列统一加 (3) 或 (6)不加小数秒的列查询边界一定写清楚。所有自动填充字段比如 created_at、updated_at优先选用能节省字节的类型但要确认 MySQL 版本支持。为什么不直接一刀切用 timestamp因为 2038 边界和连接时区依赖这件事在很多企业系统里根本没有能力做到全局可控。DATETIME 虽然多占一点空间但它对时区变化免疫在团队协作混乱、容器环境不统一的情况下DATETIME 加 UTC 约定往往是最少出问题的组合。这个判断基于我自己的踩坑经历后文排查链路里你会看到问题往往不是类型本身而是类型和部署环境的叠加。5. 完整排查链路从时间快了 8 小时到揪出三层根因5.1 第一轮先确认到底是谁在骗你回到开头的真实事故。当时排查的第一步不是改 SQL而是确认谁给的值是假的。我们在 MySQL 客户端执行了SELECT NOW()在应用服务器打印当前时间再把一条具体订单的 created_at 拿出来对比。结果很有意思MySQL 客户端看到的时间和服务器系统时间差 8 小时而应用通过连接池查出来的时间和系统时间一致。这说明 MySQL 服务器本身就在 UTC 时区下跑但应用连接里有东西把会话时区纠正成了东八区所以应用查出 TIMESTAMP 列时显示成东八区时间。也就是说同一列数据在不同客户端、不同连接里看到的值是不一致的典型的 timestamp 时区转换特征。这一步就把 DATETIME 类型排除了如果是 DATETIME任何连接看到的都应该一样。5.2 第二轮定位全局时区和会话时区确认是 TIMESTAMP 的问题之后开始按层拆。先看全局时区SELECT global.time_zone; -- 结果SYSTEM再看系统时区用 bash 命令date输出发现容器跑的是 UTC。于是全局链路里服务器层就已经偏了。继续看会话时区SELECT session.time_zone;在同一个 MySQL 客户端里会话时区继承全局时区也是 UTC。所以从纯 MySQL 的角度这套环境是自洽的——全局 UTC、会话 UTC、写入时按 UTC 转读取时按 UTC 转数字没有变化。可业务期望的是东八区于是 TIMESTAMP 列所有显示都比业务预期少了 8 小时在只读场景下看起来就是时间慢了 8 小时。但是前面说过应用连接查出来的数据是对的。这就要看第三层了。5.3 第三轮连接参数、容器时区、应用层时区的接力偏移查应用配置发现连接串里显式配了 serverTimezoneAsia/Shanghai这样应用连接建立后会话时区被驱动设置成东八区。于是读一条 TIMESTAMP 数据应用读出来自然就回到了东八区应用层看不出问题。但定时任务取数用的却是一个独立脚本脚本连的是同一个实例连接串里没有配 serverTimezone走的是会话默认值 UTC于是读出来就比应用层少了 8 小时。同一个库两种连接两种显示结果这就是报表空白的原因。再往下挖还发现了第四层。定时任务脚本所在机器本身是东八区脚本框架读取时间后又默认按东八区格式化但因为从库里拿到的 UTC 字符串已经被当成本地时间对待没有做一次从 UTC 到东八区的转换最终写进报表的就是实际时间减 8 小时。层层接力每次只差一点最后就成了整整 8 小时的偏差。5.4 修复方案与防复发措施修复动作分三步。第一步把 MySQL 全局时区显式设置为东八区让默认会话都走东八区在配置文件中加default-time-zone08:00然后重启实例或者用SET GLOBAL time_zone 08:00动态调整。第二步统一所有连接串显式带上 serverTimezoneAsia/Shanghai并检查连接池初始化 SQL 没有额外改时区。第三步把定时任务脚本里的时间处理逻辑改成统一 UTC 入参展示层再转本地彻底消除应用框架层的隐性换算。防复发也做了三件事一是给所有 TIMESTAMP 列写了字段注释标明以 UTC 存储、东八区展示二是监控里加了一个定时巡检每小时比对服务器的date和SELECT NOW()偏差超过一分钟就告警三是在表结构评审里加了强制检查项凡是新增时间列必须有明确的UTC 或本地日历语义标注。过了大半年再也没有出过类似的时间偏差事故。这套先确认谁在骗你、再逐层定位、最后统一语义的思路基本可以复用到任何时区问题排查里。6. 进阶场景毫秒精度、2038 问题、迁移与索引优化6.1 决定要不要毫秒精度之前先想清楚查询怎么写很多业务一开始没想清楚就把所有时间列建成了 DATETIME后来发现日志对账需要毫秒又开始改类型加 (3)。改类型本身不难麻烦的是查询习惯没有跟着升级。举个例子假设 created_at 是 DATETIME(3)某条数据写入时间是 2024-06-01 12:00:00.123你执行WHERE created_at 2024-06-01 12:00:00是查不到的MySQL 不会自动把整数秒匹配到小数秒。正确写法是WHERE created_at 2024-06-01 12:00:00 AND created_at 2024-06-01 12:00:01还有一种更省内存的方式是把毫秒单独抽出一列时间列仍用秒级精度查询时用两列联合过滤。但大多数业务不值得这么复杂把查询边界写对就行。另外提醒一点如果时间列上建了索引像上面的范围查询是能正常走索引的但如果你写成WHERE DATE(created_at) 2024-06-01函数包住时间列MySQL 在多数版本下就无法直接利用索引只能扫全表。宁可用两个比较条件拼一天的范围也不要图省事写函数。6.2 2038 年问题不是危言耸听TIMESTAMP 最多存到 2038-01-19 03:14:07 UTC这是 4 字节整数秒的上限。很多同学觉得离现在还很远但金融合同、保险单、会员期限这类业务日期早就排到了 2040 甚至 2050。如果建表时随手选了 TIMESTAMP等业务录入 2040 年的数据时MySQL 会直接报错甚至可能因为非法日期异常影响写入链路。我的建议是任何表示未来日期的字段一律用 DATETIME只有表示事件发生的绝对时刻的字段才允许考虑 TIMESTAMP。万一历史表里已经用了 TIMESTAMP 并且未来会写入超范围日期尽早安排迁移。另外即便数据库这层用 DATETIME 避开了 2038应用层某些语言或旧组件自身也可能存在 32 位时间戳上限排查时要连同应用代码一起看。6.3 跨类型迁移的风险与步骤从 datetime 迁移到 timestamp或者反过来都不是简单的 ALTER TABLE 改类型就能收工。先说反向风险从 DATETIME 迁 TIMESTAMP原本合理的日历时间一旦超出 2038 范围直接失败而且如果 DATETIME 里存的是本地日历时间而不是 UTC迁移后 TIMESTAMP 又会按会话时区做一次换算数据可能整体偏移变成一场灾难。比较稳妥的迁移步骤是先写查询把源表里每个时间列的绝对 UTC 时刻和显示值分别统计出来确认语义再用ALTER TABLE ... MODIFY COLUMN在低峰期执行执行完立即抽样对比迁移前后的绝对时刻而不是只对比显示值。如果是千万级大表直接 ALTER 会锁很长时间建议用在线变更工具分批处理避免影响线上写入。这类操作我在某交易系统里做过一次最大的教训就是迁移前没有对齐时区语义导致上线后有两小时的数据显示偏移回滚和修复比迁移本身还痛苦。6.4 时间列的索引和分区别让常见写法废掉索引时间列是索引和分区的常客但很多人写查询时不注意函数包裹。比如统计某天的数据WHERE DATE(create_time) 2024-06-01表面直观实际会让 create_time 上的索引失效改成create_time 2024-06-01 00:00:00 AND create_time 2024-06-02 00:00:00就既能利用索引语义也完全一致。这是索引优化里最便宜的收益。分区方面按时间 RANGE 分区对归档、清理很友好。如果分区键用的是 TIMESTAMP要注意分区边界的判定和会话时区有关一旦全局时区变更已有分区的数据归属可能发生变化这在跨时区系统里是个隐藏的大坑。用 DATETIME 做分区键时数值就是纯粹的日历时间边界直观、不受时区影响。所以我的经验是分区键优先用 DATETIME配合 UTC 约定或明确时区字段比 TIMESTAMP 更不容易在运维时翻车。7. 一张选型检查清单照着打钩避免拍脑袋7.1 五个问题速查表到了真正建表的时候与其背记忆模糊的文档不如按下面五个问题逐项打钩检查问题答案是时的选择答案否时的选择这一列是否记录事件发生的绝对时刻优先 TIMESTAMP或 DATETIME(UTC)DATETIME按日历时间处理是否需要存入 2038 年之后的日期必须 DATETIME可按需选 TIMESTAMP是否需要毫秒/微秒精度类型名加 (3)/(6)可不加省空间是否需要随查看者时区自动转换显示TIMESTAMP 更省事DATETIME 更稳海量历史数据且内存敏感TIMESTAMP无小数秒存储差异不大以语义为主这套表的逻辑不是哪个类型更好而是你的业务语义更适合哪个类型。只要团队能把每个时间列的语义说清楚自然能选出合适的方案。如果你还是拿不准我建议保守选择 DATETIME至少它不会因为时区设置变化而产生数据歧义。7.2 建表时的推荐配置模板顺手给一个参考模板适合大多数业务系统的默认场景CREATE TABLE biz_record ( id BIGINT NOT NULL AUTO_INCREMENT, occurred_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 事件发生时刻UTC存储展示按会话时区, calendar_date DATETIME NOT NULL COMMENT 本地日历时间与时区无关, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 创建时间毫秒精度, updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT 更新时间, PRIMARY KEY (id), KEY idx_occurred_at (occurred_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里我把客观时刻和日历时间分开用两列表达注释写清楚语义比把一列又当 UTC 又当本地时间用要靠谱得多。需要强调的是DEFAULT CURRENT_TIMESTAMP(3) 在 DATETIME 上的支持依赖 MySQL 版本5.6.5 以上基本没问题更老版本请先确认。7.3 最后说一个我的个人习惯文章最后分享一个我个人的小习惯每次接到新需求我都会先在设计文档里用一句话写清楚每个时间列的业务语义比如这个字段表示用户下单那一刻的 UTC 时间或者这个字段表示门店所在时区的营业日期。如果这句话写不清楚那说明需求本身还没理清我宁愿先找产品对齐也不会急着建表。在 MySQL 时间类型上踩过的坑越多我越觉得选 timestamp 还是 datetime 从来不是一个纯粹的技术问题而是业务语义、部署环境、团队约定三者之间的平衡。技术文档不会教你这些但线上事故会而且每次都教得很贵。希望这篇整理能帮你少付几次学费。
返回列表