一文彻底搞懂 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 是“最容易踩坑”的类型?

因为它同时具备三点:

  1. 看起来像时间点
  2. 实际上没有时区
  3. 又被 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-019999-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 字节
可读性 ⭐⭐⭐⭐⭐ (直观) ⭐⭐⭐⭐⭐ (直观) ⭐ (无法肉眼识别)
✅ 最终建议:
  1. 绝大多数业务场景(创建时间、修改时间、业务发生时间):
    请使用 DATETIME
  • 配合 Java 里的 LocalDateTime
  • 在代码层面或 JDBC 连接层面统一管理时区,不要依赖数据库帮你在底层偷偷转。
  1. 极高并发、跨国业务核心表、排序要求极高:
    考虑使用 BIGINT
  • 比如订单号生成、高频埋点数据。
  1. **不要使用 TIMESTAMP**,除非你是维护十年前的老系统,或者你极其确定这个系统会在 2038 年前报废。

我可以给你一个标准的建表模板,包含 created_atupdated_at 的自动更新写法(用 DATETIME 也能实现自动更新),需要吗?

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐