DATETIME存储字面值、无视时区,TIMESTAMP存储UTC时间戳、绑定时区自动转换;前者读写不变,后者写入转UTC、查询按会话时区转回;TIMESTAMP范围受限(1970–2038),DATETIME更广(1000–9999)。

MySQL中DATETIME和TIMESTAMP的本质区别是什么?
DATETIME存的是字面值,不带时区信息,读写都按客户端传入的原始值处理;TIMESTAMP存的是UTC时间戳,写入时自动转为UTC,查询时再按当前会话时区转回本地时间。这不是“转换问题”,而是设计逻辑差异——TIMESTAMP天生绑定时区,DATETIME天生无视时区。
常见错误现象:SELECT NOW(), CURRENT_TIMESTAMP在不同时区客户端看到相同结果,但INSERT INTO t(dt) VALUES ('2024-01-01 12:00:00')插入后,用不同time_zone查dt字段始终显示2024-01-01 12:00:00,而查ts字段却可能变成2024-01-01 04:00:00(UTC-8)或2024-01-01 20:00:00(UTC+8)。
- 不要指望
DATETIME自动适配客户端时区——它不会 -
TIMESTAMP的“自动转换”依赖time_zone变量,包括全局、会话级、甚至单条语句级(如SET time_zone = '+08:00') -
CONVERT_TZ()函数只对DATETIME有效,对TIMESTAMP调用等于白费——它内部已转过一次了
如何安全地把服务器存储的TIMESTAMP转成指定时区的DATETIME?
如果你需要固定输出为东八区时间,又不想改会话time_zone(比如多租户场景下不能全局设+08:00),就别依赖TIMESTAMP的隐式转换,直接用CONVERT_TZ()显式转:
SELECT CONVERT_TZ(ts, '+00:00', '+08:00') AS dt_beijing FROM t;
注意参数顺序:CONVERT_TZ(datetime_expr, from_tz, to_tz),第一个参数必须是DATETIME类型。所以如果ts是TIMESTAMP列,MySQL会先把它当作UTC时间解释,再转目标时区——这恰好符合预期。
-
from_tz填'+00:00'最稳妥,避免依赖系统时区表(如SYSTEM可能指向服务器本地时区,而非UTC) - 别用
CONVERT_TZ(ts, 'UTC', 'Asia/Shanghai')——除非你确认MySQL时区表已加载且Asia/Shanghai映射准确 - 如果字段是
DATETIME且存的是UTC值,同样可用CONVERT_TZ(dt, '+00:00', '+08:00');但如果它存的是东八区本地时间,再这么转就重复转换了
应用层写入时,DATETIME和TIMESTAMP该选哪个?
选TIMESTAMP的前提是你明确接受“所有时间按UTC统一存储+按会话时区动态展示”。否则,绝大多数业务场景更适合DATETIME,配合应用层自行管理时区。
- 日志类、审计类字段(如
created_at)用TIMESTAMP没问题——只要前端/报表统一按用户本地时区渲染 - 业务含义强的时间(如“用户预约明天下午3点”),必须用
DATETIME,并确保应用层写入前已转为目标时区(比如用户选的是北京时间,就写'2024-01-02 15:00:00') -
TIMESTAMP有年份范围限制(1970–2038),DATETIME支持1000–9999年,金融、档案类系统必须避开TIMESTAMP - MySQL 5.6.4+起
TIMESTAMP支持毫秒,但DATETIME(3)更通用,兼容性更好
time_zone配置被忽略的常见原因
设置了SET time_zone = '+08:00',但NOW()还是返回UTC时间?大概率是连接池或ORM框架重置了会话变量。
- Java JDBC连接串加
serverTimezone=GMT%2B8,否则驱动默认用JVM时区,与MySQL服务端不一致 - Python
pymysql需显式执行cursor.execute("SET time_zone = '+08:00'"),且每次新连接都要重设 - PHP PDO中
PDO::ATTR_INIT_COMMAND => "SET time_zone = '+08:00'"可解决,但注意连接复用时可能失效 - MySQL配置文件里
default-time-zone = '+08:00'只影响新连接的默认值,已有连接不受影响
真正容易被忽略的点:时区转换发生在**查询解析阶段**,不是存储阶段。哪怕你把time_zone设成+08:00,SELECT ts FROM t返回的仍是按该时区转换后的值——但如果你用SELECT UNIX_TIMESTAMP(ts),拿到的永远是UTC秒数,跟time_zone无关。


















