Java与MySQL导致的时间不一致问题分析

 更新时间:2024年07月03日 09:06:30   作者:码畜c  
在使用MySQL的过程中,你可能会遇到时区相关问题,本文主要介绍了Java与MySQL导致的时间不一致问题分析,具有一定的参考价值,感兴趣的可以了解一下

时间戳与时区的关系

时间戳一般指的是Unix 时间戳:是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。

那么和时区又有什么关系呢?

public static void main(String[] args) throws ParseException {
    TimeZone bjTimeZone = TimeZone.getTimeZone("Asia/Shanghai");
    TimeZone utcTimeZone = TimeZone.getTimeZone("UTC");
    
    // 时间戳在不同时区下的日期
    Date date = new Date(0L);
    System.out.println("时间戳 0 对应的系统时间:" + date);
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    sdf.setTimeZone(bjTimeZone);
    System.out.println("时间戳 0 在东八时区下表达的时间:" + sdf.format(date));
    sdf.setTimeZone(utcTimeZone);
    System.out.println("时间戳 0 在UTC时区下表达的时间:" + sdf.format(date));
    
    // 日期在不同时区下的时间戳
    sdf.setTimeZone(bjTimeZone);
    System.out.println("2024-02-25 00:00:00 在东八时区下的时间戳:" + sdf.parse("2024-02-25 00:00:00").getTime());
    sdf.setTimeZone(utcTimeZone);
    System.out.println("2024-02-25 00:00:00 在UTC时区下的时间戳:" + sdf.parse("2024-02-25 00:00:00").getTime());
}

时间戳 0 对应的系统时间:Thu Jan 01 08:00:00 CST 1970
时间戳 0 在东八时区下表达的时间:1970-01-01 08:00:00
时间戳 0 在UTC时区下表达的时间:1970-01-01 00:00:00
2024-02-25 00:00:00 在东八时区下的时间戳:1708790400
2024-02-25 00:00:00 在UTC时区下的时间戳:1708819200

  • 一个时间戳在不同时区下所表达的时间是不一样的。
  • 一个日期在不同时区下的时间戳是不同的。东八时区(北京时间)与 UTC 世界协调时间相差了八小时。可以通过时间戳的差值进行计算验证:(1708819200 - 1708790400) / 60 / 60 = 8
  • 一个日期在不同时区下的时间戳的差值:时区间的偏移量
  • 两个时区不同,但时间相同的日期表达的意义也不一样,就如北京八点与美国八点的区别

查询、修改 Java 程序使用的时区

public static void main(String[] args) {
 	// 查看默认时区与时区ID
 	TimeZone defaultTimeZone = TimeZone.getDefault();
    ZoneId systemDefaultZoneId = ZoneId.systemDefault();
	// sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=29,lastRule=null]
    System.out.println(defaultTimeZone);
    // Asia/Shanghai
    System.out.println(systemDefaultZoneId);
    
    // 设置默认时区
    TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("UTC")));
    // sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
    System.out.println(TimeZone.getDefault());
}

查询、修改 MySQL 数据库使用的时区

查询:

show global variables like "%time_zone%";
Variable_nameValue
system_time_zone(系统时区)UTC
time_zone(会话时区)SYSTEM

系统时区:UTC,即比东八时区慢8个小时。可以通过 SELECT NOW() 查询当前时间对比 PC 上的时间验证:MYSQL: 2024-02-24 19:07:31 / PC: 2024-02-25 03:07:31。该值读取的就是 MySQL 服务所在的操作系统上使用的时区,以 Linux 系统为例,可通过 date -R 查看:Sat, 24 Feb 2024 19:31:56 +0000。

会话时区:采用系统时区,即 UTC。

修改:系统时区:修改系统时区,即修改 MySQL 服务所在服务器的时区,以 Linux 操作系统为例:cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime,将时区文件 copy 到 etc 目录下且命名为 localtime。

会话时区:

  • SQL 的方式:set global time_zone = '+8:00' or set global time_zone = 'Asia/Shanghai'
  • 修改 my.cnf 配置文件中的 default-time-zone=Asia/Shanghai 属性

JDBC 读取并设置 MySQL 服务使用的时区的流程

mysql-connector-j-8.0.33.jar 为例:

com.mysql.cj.protocol.a.NativeProtocol.configureTimeZone 设置MySQL服务使用的时区的流程:

  • 优先读取connectionTimeZoneserverTimezone jdbc 属性作为会话使用的时区
  • 若配置的属性为SERVER,则在第一次调用 ServerSession.getSessionTimeZone() 时读取数据库中配置的时区
  • 若没有配置则以本地服务器的时区作为 MySQL 服务器的时区

NativeProtocol.configureTimeZone

JDBC 如何应用的时区

说明:当前Java 程序东八时区,MySQL服务 UTC 时区。

存储日期数据时,com.mysql.cj.protocol.a.SqlTimestampValueEncoder.getString 对于 Date 类型字段值的处理:将Java程序时区下的日期的时间戳,转为MySQL服务时区下的日期(2024-02-25 11:52:56 > 2024-02-25 03:52:56

com.mysql.cj.protocol.a.SqlTimestampValueEncoder.getString

查询日期数据时,com.mysql.cj.result.SqlTimestampValueFactory.localCreateFromDatetime 对于 Date 类型字段的处理:将MySQL服务时区下的日期的时间戳,转为Java程序时区下的日期。(2024-02-24 21:34:55 > 2024-02-25 05:34:55

com.mysql.cj.result.SqlTimestampValueFactory.localCreateFromDatetime

根据源代码的实现可以发现一个规律:都是先将日期根据所属时区转换为时间戳后,在根据需要转换的时区转换为最终日期。

Java 程序时区与 MySQL 服务使用时区不一致导致的问题

在 JDBC 读取并设置 MySQL 服务使用的时区的流程 中说到:MySQL 服务使用的时区会受到 jdbc 参数的影响,也就是说可能会出现:实际的数据库时区与 jdbc 参数声明的时区是不一样的。最坏情况下会出现:Java 程序时区、jdbc 声明时区、实际数据库时区都是不同的。

不对每种情况的流程进行逐个分析。一般开发时遇到时间不一致时,大概都是分为以下的两种情况:

Java 程序时区 与 jdbc 参数时区一致,实际数据库时区不一致:这种情况下,会出现程序、数据库中展示日期都是相同的,但两个日期分别对应的时间戳不相同。看起来是没有问题,但实际上,数据库中存储的日期的时间戳已经不是Java程序中该日期所对应的时间戳了。如最开始说到的:一个日期在不同时区下的时间戳是不同的,那么表达的意义也不一样,就如北京八点与美国八点的区别。整理一下 JDBC 驱动包在这种情况下的处理流程:

1)存储:

2)读取(能够在转为 Java 程序中的正确日期):

  • 获取日期在 Java 程序时区下的时间戳
  • 根据 JDBC 参数时区将时间戳转为 MySQL 服务时区下的日期字符串(由于二者一致,所以结果没有变化)
  • 组装为数据包发送给实际的 MySQL 服务
  • MySQL 服务根据该日期字符串,转为实际数据库时区下的该日期(此时时间戳已经不同了)
  • 读取实际数据库中的日期字符串
  • 根据 JDBC 参数时区将日期转为对应时间戳
  • 将时间戳在根据 Java 程序时区转为对应的日期(由于与 JDBC 参数时区一致,最终程序中的日期还是相同的)

Java 程序时区 与 实际数据库时区不一致:这种情况下不考虑 jdbc 参数时区的影响,会出现程序、数据库中展示的日期不同,但两个不同日期的时间戳是一致的。捋一下 JDBC 处理流程:

1)存储:

  • 获取日期在 Java 程序时区下的时间戳
  • 获取实际 MySQL 服务的时区,并转为该时区下的日期字符串(由于二者不一致,所以转换结果日期也不同)
  • 组装为数据包发送给实际的 MySQL 服务
  • MySQL 服务根据该日期字符串,转为实际数据库时区下的该日期(此时展示的日期已经不同,但时间戳还是相同的)

2)读取(能够在转为 Java 程序中的正确日期):

  • 读取实际数据库中的日期字符串
  • 获取实际 MySQL 服务的时区,将日期转为对应时间戳
  • 将时间戳在根据 Java 程序时区转为对应的日期(虽然时区不同,但是时间戳还是同一个,能够在正确转为 Java 程序中的正确日期)

仅依靠数据库时区生成时间数据(这是一种特殊情况):
当我们在为创建、修改时间字段添加了以下自动获取时间属性时:

`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间'

1)存储:仅依赖数据库时区,不涉及时区转换的问题
2)读取(能够在转为 Java 程序中的正确日期):

  • 读取实际数据库中的日期字符串
  • 获取实际 MySQL 服务的时区,将日期转为对应时间戳
  • 将时间戳在根据 Java 程序时区转为对应的日期(虽然时区不同,但是时间戳没有发生变化,能够在正确转为 Java 程序中的正确日期)

解决方式

  • 将 Java 程序时区、jdbc 声明的参数时区、实际数据库时区设置为相同的时区
  • 将 Java 程序时区、实际数据库时区设置为相同的时区,且不设置 jdbc 声明的参数时区这一干扰配置

到此这篇关于Java与MySQL导致的时间不一致问题分析的文章就介绍到这了,更多相关Java MySQL时区不一致 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • springboot数据库操作图文教程

    springboot数据库操作图文教程

    本文以图文并茂的形式给大家介绍了springboot数据库操作,感兴趣的朋友一起看看吧
    2017-07-07
  • 页面设计之事件处理综合介绍

    页面设计之事件处理综合介绍

    页面设计之事件处理,当你把界面都设计好了,总需要添加相应的执行动作给组件,在有相应的时间处理机制
    2012-12-12
  • 深入理解Java设计模式之解释器模式

    深入理解Java设计模式之解释器模式

    这篇文章主要介绍了JAVA设计模式之解释器模式的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解
    2021-11-11
  • Java多线程中的原子类属性说明

    Java多线程中的原子类属性说明

    这篇文章主要介绍了Java多线程中的原子类属性说明,对多线程访问同一个变量,我们需要加锁,而锁是比较消耗性能的,JDk1.5之后,新增的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式,需要的朋友可以参考下
    2023-10-10
  • Java 异步回调机制实例分析

    Java 异步回调机制实例分析

    这篇文章主要介绍了Java 异步回调机制实例解析的相关资料,需要的朋友可以参考下
    2017-02-02
  • java实现的xml格式化实现代码

    java实现的xml格式化实现代码

    这篇文章主要介绍了java实现的xml格式化实现代码,需要的朋友可以参考下
    2016-11-11
  • Java8通过CompletableFuture实现异步回调

    Java8通过CompletableFuture实现异步回调

    这篇文章主要介绍了Java8通过CompletableFuture实现异步回调,CompletableFuture是Java 8 中新增的一个类,它是对Future接口的扩展,下文关于其更多相关详细介绍需要的小伙伴可以参考一下
    2022-04-04
  • Java常用工具类—集合排序

    Java常用工具类—集合排序

    这篇文章主要介绍了Java集合排序,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • SpringBoot配置拦截器的示例

    SpringBoot配置拦截器的示例

    这篇文章主要介绍了SpringBoot配置拦截器的示例,帮助大家更好的理解和使用springboot框架,感兴趣的朋友可以了解下
    2020-11-11
  • java 集合并发操作出现的异常ConcurrentModificationException

    java 集合并发操作出现的异常ConcurrentModificationException

    Map在遍历时候通常 现获得其键值的集合Set,然后用迭代器Iterator来对Map进行遍历。
    2009-06-06

最新评论