一文彻底搞懂 MySQL 的 DATETIME 和 TIMESTAMP
一文彻底搞懂 MySQL 的 DATETIME 和 TIMESTAMP
——为什么你总是“少 8 小时”
结论先行:
90% 的时间问题,不是代码写错,而是 DATETIME 和 TIMESTAMP 用反了。
这篇文章只解决一件事:
👉 MySQL 的 DATETIME 和 TIMESTAMP 到底存的是什么?
👉 Java / JDBC / JSON 是怎么把时间“搞乱”的?
👉 工程里到底该选哪个?
一、先把最容易混淆的一点说清楚
时间系统里有两种完全不同的东西:
① 绝对时间点(Instant)
-
全世界同一时刻
-
不依赖时区
-
例如:
“2026-02-02 11:09:48 UTC 这一刻”
② 本地日历时间(Local Date-Time)
-
依赖时区
-
没有“绝对含义”
-
例如:
“晚上 7 点开会”
MySQL 的两个类型,正好各站一边。
二、TIMESTAMP:存的是“时间点”
1️⃣ TIMESTAMP 的本质
TIMESTAMP 存的是一个“绝对时间点”
MySQL 内部做了这些事:
- 写入时:
会话时区 → UTC → 存储 - 读取时:
UTC → 会话时区 → 返回
📌 注意:MySQL 表面上显示的是时间,但内部是 UTC。
2️⃣ Java + JDBC + TIMESTAMP 会发生什么?
写入流程(简化):
Java Date / Instant (epoch millis)
↓
JDBC Driver(serverTimezone)
↓
MySQL TIMESTAMP(内部 UTC)
读取流程:
MySQL UTC
↓
JDBC Driver(serverTimezone)
↓
Java Timestamp(epoch millis)
所以:
-
TIMESTAMP 的前提是:你明确知道这是“同一瞬间”
-
它非常适合:
- 创建时间
- 更新时间
- 日志时间
- 跨时区系统
3️⃣ 为什么你看到“少 8 小时”?
因为:
-
Java 内部拿到的是 epoch millis
-
Jackson 默认 按 UTC 序列化
-
你在浏览器/接口里看到的是:
2026-02-02T11:09:48Z
👉 这不是错,是 UTC 的正确展示。
三、DATETIME:存的是“日历字面值”
1️⃣ DATETIME 的本质
DATETIME 存的是一个“年月日时分秒的字面值”
MySQL 对 DATETIME 的态度是:
“我不管你在哪个时区,
你给我2026-02-02 19:09:48,
我就原样存。”
📌 MySQL 不会帮你做任何时区转换。
2️⃣ Java + JDBC + DATETIME 会发生什么?
写入时:
-
Java 的
Date/Timestamp只有 epoch millis -
JDBC 必须选一个时区把它解释成:
yyyy-MM-dd HH:mm:ss -
通常用的是 JVM 默认时区
于是:
- JVM 是上海 → 存
19:09 - JVM 是 UTC → 存
11:09
⚠️ 环境一换,落盘的“时间字面值”就变了
3️⃣ 读取时更危险
MySQL 返回:
2026-02-02 19:09:48
JDBC 必须把它算回 epoch millis,于是:
- 它假设这是 JVM 默认时区下的时间
- 再算 millis
👉 如果读和写的 JVM 时区不一致,时间直接错 8 小时
四、serverTimezone 到底管谁?
很多人以为:
serverTimezone = “整个系统的时区”
这是错的。
正确答案是:
serverTimezone 只在 JDBC Driver ↔ MySQL(TIMESTAMP)时生效
| 场景 | serverTimezone 是否关键 |
|---|---|
| TIMESTAMP | ✅ 非常关键 |
| DATETIME | ⚠️ 基本不关键 |
| Java 打印 | ❌ 无关 |
| JSON 输出 | ❌ 无关 |
五、为什么 DATETIME 是“最容易踩坑”的类型?
因为它同时具备三点:
- 看起来像时间点
- 实际上没有时区
- 又被 Java/JDBC 强行塞进 Timestamp
它不是错,而是极度依赖“使用约定”。
六、工程实践:到底该怎么选?
场景 1:这是一个“全球唯一时刻”
例如:
- 创建时间
- 日志时间
- 操作时间
✅ 推荐:
MySQL: TIMESTAMP
Java : Instant / OffsetDateTime
JSON : UTC(前端自己转)
场景 2:这是一个“业务本地时间”
例如:
- 预约时间
- 上课时间
- 每天 19:00 执行的任务
✅ 推荐:
MySQL: DATETIME
Java : LocalDateTime
JSON : 字符串(不要再转 Instant)
❌ 不要用 Date / Timestamp
七、一个“永远不乱”的心智模型
TIMESTAMP = 时间点
DATETIME = 字面时间
只要你在建表时先回答一句话:
“这个时间,是不是全世界同一瞬间?”
你就不会再选错类型。
八、最后一句(送你一句工程真理)
时间问题,80% 是建表时已经决定的。
后面 Java、JDBC、JSON 做的,
只是把这个决定放大。
TIMESTAMP 和 DATETIME 选哪个
在现在的企业级开发(尤其是使用 MySQL 5.7 或 8.0+)中,DATETIME 已经逐渐成为主流选择,而 TIMESTAMP 因为其致命的局限性正在被逐步放弃。
也有不少高并发或金融业务场景直接使用 BIGINT(存毫秒数)。
以下是深度的对比和选型建议:
1. 为什么 TIMESTAMP 越来越不受欢迎?
曾经 TIMESTAMP 很流行,因为它省空间(4字节),且自带时区转换。但现在它有两个致命硬伤:
-
⚠️ 2038年问题 (Year 2038 Problem)
TIMESTAMP存储的是 32 位整数,能表示的最大时间是 2038-01-19 03:14:07。 -
现在已经是 2026 年了,对于房贷、保险、长期合约等业务,这个时间点已经非常近了。如果你现在的系统要运行超过 12 年,用
TIMESTAMP就是给自己埋雷。 -
时区转换的“双刃剑”
TIMESTAMP存进去时会把当前时区转为 UTC,取出来时再转回当前连接的时区。 -
优点:国际化方便。
-
缺点:如果服务器迁移(比如从中国机房迁到新加坡 AWS),或者 JDBC 连接参数配置失误,取出来的时间会立刻变掉,造成严重的生产事故(比如订单时间对不上)。
2. 为什么 DATETIME 是现在的主流?
在 MySQL 5.6.4 之后,DATETIME 进行了优化,现在的企业首选它:
-
范围极广:从
1000-01-01到9999-12-31。完全不用担心时间溢出。 -
所见即所得 (WYSIWYG):
-
存进去
2026-02-02 10:00:00,无论你在美国还是日本读取,它永远是10:00:00。 -
这对于数据归档、日志排查非常友好,不会因为连接参数不同而产生歧义。
-
空间不再是瓶颈:
-
早期的
DATETIME占 8 字节,现在(MySQL 5.6.4+)优化到了 5 字节。 -
相比
TIMESTAMP的 4 字节,每行只多 1 字节,这在现代存储成本下可以忽略不计。
3. 第三种选择:BIGINT (存毫秒/秒)
很多互联网大厂(如阿里、美团的部分核心系统)喜欢直接用 BIGINT 存时间戳(System.currentTimeMillis())。
-
优点:
-
极致性能:比较大小、建立索引的效率是所有类型里最高的(纯数字比较)。
-
绝对时区无关:全球统一,前端展示时自己转成当地时间即可。
-
没有 2038 限制。
-
缺点:
-
可读性差:DBA 或者开发人员直接看数据库,看到的是
1738503034000,根本不知道是几月几号,必须手动转换。
💡 总结与选型建议
| 特性 | DATETIME (推荐) | TIMESTAMP (慎用) | BIGINT (特定场景) |
|---|---|---|---|
| 存储范围 | 1000 ~ 9999 年 | 1970 ~ 2038 年 (硬伤) | 几乎无限 |
| 时区敏感 | 不敏感 (存啥读啥) | 敏感 (自动转 UTC) | 不敏感 (纯数字) |
| 存储空间 | 5 字节 (新版) | 4 字节 | 8 字节 |
| 可读性 | ⭐⭐⭐⭐⭐ (直观) | ⭐⭐⭐⭐⭐ (直观) | ⭐ (无法肉眼识别) |
✅ 最终建议:
- 绝大多数业务场景(创建时间、修改时间、业务发生时间):
请使用DATETIME。
- 配合 Java 里的
LocalDateTime。 - 在代码层面或 JDBC 连接层面统一管理时区,不要依赖数据库帮你在底层偷偷转。
- 极高并发、跨国业务核心表、排序要求极高:
考虑使用BIGINT。
- 比如订单号生成、高频埋点数据。
- **不要使用
TIMESTAMP**,除非你是维护十年前的老系统,或者你极其确定这个系统会在 2038 年前报废。
我可以给你一个标准的建表模板,包含 created_at 和 updated_at 的自动更新写法(用 DATETIME 也能实现自动更新),需要吗?
更多推荐


所有评论(0)