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时区不一致 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • 详解java中的byte类型

    详解java中的byte类型

    Java也提供了一个byte数据类型,并且是基本类型。java byte是做为最小的数字来处理的,因此它的值域被定义为-128~127,也就是signed byte。下面这篇文章主要给大家介绍了关于java中byte类型的相关资料,需要的朋友可以参考下。
    2017-02-02
  • SpringCloud Zuul网关功能实现解析

    SpringCloud Zuul网关功能实现解析

    这篇文章主要介绍了SpringCloud Zuul网关功能实现解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • 深入浅析Java中的final关键字

    深入浅析Java中的final关键字

    在Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量),下面通过本篇文章给大家介绍java中的final关键字,对java fina关键字相关知识感兴趣的朋友一起看看吧
    2015-12-12
  • IDEA配置Vue项目启动器的过程

    IDEA配置Vue项目启动器的过程

    文章介绍了两种启动方式一是通过控制台手动输入指令并用Ctrl+C终止;二是配置npm启动器以简化测试流程,提升效率
    2025-07-07
  • 在SpringBoot项目中实现读写分离的流程步骤

    在SpringBoot项目中实现读写分离的流程步骤

    SpringBoot作为一种快速开发框架,广泛应用于Java项目中,在一些大型应用中,数据库的读写分离是提升性能和扩展性的一种重要手段,本文将介绍如何在SpringBoot项目中优雅地实现读写分离,并通过适当的代码插入,详细展开实现步骤,同时进行拓展和分析
    2023-11-11
  • Java中避免过多if-else的几种方法

    Java中避免过多if-else的几种方法

    这篇文章主要介绍了Java中避免过多if-else的几种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • Java中读取文件时间属性之创建时间、修改时间、访问时间的跨平台实现

    Java中读取文件时间属性之创建时间、修改时间、访问时间的跨平台实现

    在工作时候我们有时候需要获取到文件的最后更新时间,根据最近更新时间,来处理其他业务,这篇文章主要介绍了Java中读取文件时间属性之创建时间、修改时间、访问时间的跨平台实现,需要的朋友可以参考下
    2025-08-08
  • Java yield()线程让步实现过程解析

    Java yield()线程让步实现过程解析

    这篇文章主要介绍了Java yield()线程让步实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • Springboot集成Quartz实现定时任务代码实例

    Springboot集成Quartz实现定时任务代码实例

    这篇文章主要介绍了Springboot集成Quartz实现定时任务代码实例,任务是有可能并发执行的,若Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题,而JobDetail & Job方式,Scheduler都会根据JobDetail创建一个新的Job实例,这样就可以规避并发访问问题
    2023-09-09
  • Java实现获取内网的所有IP地址

    Java实现获取内网的所有IP地址

    这篇文章主要介绍了如何利用Java语言实现获取内网的所有IP地址,文中的示例代码讲解详细,对我们学习有一定的参考价值,快跟随小编一起学习一下吧
    2022-06-06

最新评论