在 MySQL 中,DATETIME 和 TIMESTAMP 是两种常用的时间类型,它们都能存储日期和时间(格式为 'YYYY-MM-DD HH:MM:SS'),但在存储方式、时区处理、取值范围、自动更新行为等方面有重要区别。
UTC (Coordinated Universal Time ,协调世界时)是全球通用的标准时间参考系统 ,用于统一世界各地的时间表示,避免因时区差异造成混乱。所有其他时区都以 UTC ± N 小时 的形式定义。
例如:
markdown
- 北京时间(CST) = **UTC+8**
- 伦敦冬令时(GMT) = **UTC+0**
- 纽约东部时间(EST) = **UTC-5**
1.问题导出:
最近在平台页面展示时间上发现处理时间不少是在夜里0:00~8:00之间的异常时间点。通过new Date() 形式insert的时间数据是异常的(早了8小时);在sql中使用NOW()的update更新的时间是正常时间;于是查了数据库的时区设置,是UTC+8,没啥问题。字段类型是datetime。排查过程如下:
(1)查看数据库时区:
sql
SELECT @@global.time_zone AS global_time_zone, @@session.time_zone AS session_time_zone;
#结果如下:
global_time_zone | session_time_zone | |
| ---------------- | ----------------- | - |
| +08:00 | +08:00
(2)查看系统时间:
less
@Test public void testTime() {
ZonedDateTime zonedDateTime = ZonedDateTime.now(); System.out.println(zonedDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
LocalDateTime localDateTime = LocalDateTime.now(); System.out.println(localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
Instant instant = Instant.now(); System.out.println(instant.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
Date date = new Date(); System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)); }
打印结果都是正确的北京当前时间:
2025-12-26 10:37:55
2025-12-26 10:37:59
2025-12-26 10:38:06
2025-12-26 10:38:16
(3)查JDBC时区设置:后面检查jdbc设置发现设置的时区是:serverTimezone=UTC,而不是serverTimezone=Asia/Shanghai(UTC+8),所以使用new Date() 插入时,jdbc将时间转换成了UTC,也就是北京时间-8。
问题本质:
-
DATETIME字段特性- 不存储时区信息 ,直接保存原始时间值。
- 读写时依赖 JDBC 时区配置 和数据库服务器时区 进行隐式转换。
-
历史数据的两种来源
-
new Date()插入 :Date本质是 UTC 时间戳(如2025-12-26 10:38:16 UTC+8→ 存储为2025-12-26 02:38:16)。- 原 JDBC 配置
serverTimezone=UTC会将Date的毫秒数转换为 UTC 时间存入数据库。
-
NOW()插入 :- MySQL 的
NOW()返回服务器本地时间(UTC+8),直接存入DATETIME(如2025-12-26 10:38:16)。
- MySQL 的
-
-
修改
serverTimezone=Asia/Shanghai后的影响- JDBC 驱动会认为数据库存储的是
UTC+8时间。 new Date()插入的数据 (原 UTC 时间)会被误认为是UTC+8时间,导致显示错误(如存储02:38:16→ 显示为02:38:16,而实际应显示10:38:16)。NOW()插入的数据 (原 UTC+8 时间)显示正常。
- JDBC 驱动会认为数据库存储的是
2. 解决方案
步骤 1:修改 JDBC 连接时区
将连接参数从 serverTimezone=UTC 改为 serverTimezone=Asia/Shanghai:
步骤 2:修正 new Date() 插入的历史数据
需要将历史数据中所有 UTC 时间存储的 DATETIME 值 转换为 UTC+8 时间(增加 8 小时)。
说起来容易,但是历史数据的区分有点困难,还好我们用的datetime(6) ,能精确到毫秒级精度,通过NOW()插入的都是秒级精度,以此能区分
3.DATETIME 和 TIMESTAMP 对比
3.1、基本对比表
| 特性 | DATETIME |
TIMESTAMP |
|---|---|---|
| 存储格式 | 'YYYY-MM-DD HH:MM:SS' |
同左(显示格式相同) |
| 存储空间 | 8 字节 | 4 字节 |
| 取值范围 | '1000-01-01 00:00:00' 到 '9999-12-31 23:59:59' |
'1970-01-01 00:00:01' UTC 到 '2038-01-19 03:14:07' UTC |
| 时区敏感 | ❌ 不涉及时区,存储的就是你给的值 | ✅ 存储时转换为 UTC,读取时转回当前会话时区 |
| 自动初始化/更新 | MySQL 5.6.5+ 支持(需显式声明) | 默认支持(如 DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP) |
| 跨时区一致性 | 不一致(原样存储) | 一致(自动转换) |
3.2. 时区处理
-
DATETIME:完全与时区无关。你插入
'2025-12-25 18:00:00',它就存这个值,无论服务器或客户端时区如何变化,读出来还是'2025-12-25 18:00:00'。 -
TIMESTAMP:-
插入时:将当前会话时区 下的时间转换为 UTC 存储。
-
查询时:将 UTC 时间转换回当前会话时区再显示。
-
举例:
- 服务器时区是
+08:00(中国),你插入'2025-12-25 18:00:00',MySQL 实际存的是 UTC 时间'2025-12-25 10:00:00'。 - 如果之后把会话时区改为
+00:00,再查这条记录,会显示为'2025-12-25 10:00:00'。
- 服务器时区是
-
适合需要"全球统一时间记录"的场景(如日志、交易时间)。
注意:
TIMESTAMP的行为依赖于time_zone设置,而DATETIME不受影响。
示例:
sql
sql
编辑
CREATE TABLE time_demo (
id INT,
dt DATETIME,
ts TIMESTAMP
);
-- 假设当前时区是 +08:00
INSERT INTO time_demo VALUES (1, '2025-12-25 18:00:00', '2025-12-25 18:00:00');
-- 查询结果(时区 +08:00):
-- dt: 2025-12-25 18:00:00
-- ts: 2025-12-25 18:00:00
-- 切换时区到 UTC
SET time_zone = '+00:00';
-- 再次查询:
-- dt: 2025-12-25 18:00:00 (不变)
-- ts: 2025-12-25 10:00:00 (自动转为 UTC)
3.3 取值范围
TIMESTAMP受限于 Unix 时间戳(32 位整数),最大到 2038 年(即"2038 年问题")。DATETIME范围极大,适用于历史或远期日期。
如果你的应用需要处理 2038 年之后的时间(比如合同、计划),不要用
TIMESTAMP。
3.4 自动默认值与更新
-
在旧版本 MySQL(<5.6.5)中,只有
TIMESTAMP支持:sqlsql 编辑 CREATE TABLE t ( id INT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -
从 MySQL 5.6.5 开始 ,
DATETIME也支持类似语法:sqlsql 编辑 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
3.5、如何选择?
| 场景 | 推荐类型 |
|---|---|
| 需要记录事件发生的绝对本地时间(如用户注册时间、文章发布时间),且不关心时区转换 | DATETIME |
| 需要跨时区一致的时间记录(如系统日志、API 请求时间),希望自动处理时区 | TIMESTAMP |
| 时间可能超过 2038 年 | DATETIME |
| 存储空间敏感(但通常影响不大) | TIMESTAMP(省 4 字节) |
附b站@程序员鸡翅总结:


