搞清‘’时区设置‘’以及Mysql的`DATETIME` 和 `TIMESTAMP`

在 MySQL 中,DATETIMETIMESTAMP 是两种常用的时间类型,它们都能存储日期和时间(格式为 'YYYY-MM-DD HH:MM:SS'),但在存储方式、时区处理、取值范围、自动更新行为等方面有重要区别。

UTCCoordinated 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。

问题本质:

  1. DATETIME 字段特性

    • 不存储时区信息 ,直接保存原始时间值。
    • 读写时依赖 JDBC 时区配置数据库服务器时区 进行隐式转换。
  2. 历史数据的两种来源

    • 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)。
  3. 修改 serverTimezone=Asia/Shanghai 后的影响

    • JDBC 驱动会认为数据库存储的是 UTC+8 时间。
    • new Date() 插入的数据 (原 UTC 时间)会被误认为是 UTC+8 时间,导致显示错误(如存储 02:38:16 → 显示为 02:38:16,而实际应显示 10:38:16)。
    • NOW() 插入的数据 (原 UTC+8 时间)显示正常。

2. 解决方案

步骤 1:修改 JDBC 连接时区

将连接参数从 serverTimezone=UTC 改为 serverTimezone=Asia/Shanghai

步骤 2:修正 new Date() 插入的历史数据

需要将历史数据中所有 UTC 时间存储的 DATETIME 转换为 UTC+8 时间(增加 8 小时)。

说起来容易,但是历史数据的区分有点困难,还好我们用的datetime(6) ,能精确到毫秒级精度,通过NOW()插入的都是秒级精度,以此能区分

3.DATETIMETIMESTAMP 对比

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 支持:

    sql 复制代码
    sql
    编辑
    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 也支持类似语法:

    sql 复制代码
    sql
    编辑
    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站@程序员鸡翅总结:

相关推荐
隔壁阿布都2 小时前
Docker 安装 MySQL 8.0
mysql·docker·容器
曹牧2 小时前
Java:String.startsWith 方法
java·开发语言
·云扬·2 小时前
MySQL中count(*)深度解析与性能优化实践
数据库·mysql·性能优化
jiayong232 小时前
海外求职平台与策略指南
java·spring
SadSunset2 小时前
(37)全注解式开发AOP
java·spring
秃然想通2 小时前
Java多态完全指南:深入理解“一个接口,多种实现”
java·开发语言
TT哇2 小时前
Optional<T>
java·spring boot·java-ee
李拾叁的摸鱼日常2 小时前
Java泛型基本用法与PECS原则详解
java·后端·面试
MediaTea2 小时前
Python:实例 __dict__ 详解
java·linux·前端·数据库·python