根本原因在于datetime存的是分字段编码的本地时间快照(5–8字节),不涉及时区转换;timestamp存的是4字节utc秒数偏移量,读写时自动按会话时区转换,本质是带时区的时间戳。

MySQL 的 DATETIME 和 TIMESTAMP 底层存储逻辑完全不同
根本原因在于:一个是「存字符串式时间快照」,一个是「存整数型 UTC 偏移量」。这不是设计冗余,而是为解决两类完全不同的问题——绝对时间记录 vs 时区感知的统一时间点。
TIMESTAMP 存的是 4 字节整数(Unix 时间戳)
它本质上等价于一个 BIGINT,只保存从 1970-01-01 00:00:01 UTC 开始的秒数(或微秒数,取决于精度)。这意味着:
- 写入时,MySQL 自动把客户端传来的本地时间(比如
'2026-09-30 12:00:00')按当前会话time_zone转成 UTC 再存; - 读取时,再用当前会话
time_zone把这个整数反向转回可读格式; - 所以同一行数据,在东八区查是
2026-09-30 12:00:00,切到 UTC 会话就变成2026-09-30 04:00:00; - 存储空间小(4 字节),但被 2038 年问题锁定上限。
DATETIME 存的是 5–8 字节二进制结构(非字符串)
虽然显示像字符串 'YYYY-MM-DD HH:MM:SS',但内部是分字段编码的:年、月、日、时、分、秒各占固定位(MySQL 5.6.4+ 支持微秒,共 5–8 字节)。关键点是:
- 不经过任何时区转换,
INSERT进什么,磁盘就存什么; - 哪怕你把服务器时区从
+08:00改成+00:00,已存的DATETIME值显示不变,但其代表的「真实世界时刻」已经偏了 8 小时; - 范围大(支持公元 1000 年到 9999 年),适合存历史日期、固定排期等不依赖时区语义的场景;
- 没有隐式自动更新行为(除非显式加
ON UPDATE CURRENT_TIMESTAMP)。
为什么不能统一用一种?—— 时区处理成本不可忽略
如果全用 DATETIME,跨国系统就得在应用层自己做时区转换,容易漏掉日志、统计、定时任务等环节;如果全用 TIMESTAMP,2038 年后无法存新订单,历史归档数据也得提前迁移。真正容易被忽略的是:同一个表里混用两者会导致时区逻辑割裂——比如 created_at 用 TIMESTAMP,delivery_date 用 DATETIME,后续做时间差计算(如 TIMESTAMPDIFF)可能因隐式转换出错,且难以审计。











