Java与MySQL导致的时间不一致问题分析
作者:码畜c
时间戳与时区的关系
时间戳一般指的是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_name | Value |
---|---|
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'
orset 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服务使用的时区的流程:
- 优先读取
connectionTimeZone
或serverTimezone
jdbc 属性作为会话使用的时区 - 若配置的属性为
SERVER
,则在第一次调用ServerSession.getSessionTimeZone()
时读取数据库中配置的时区 - 若没有配置则以本地服务器的时区作为 MySQL 服务器的时区
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.result.SqlTimestampValueFactory.localCreateFromDatetime
对于 Date 类型字段的处理:将MySQL服务时区下的日期的时间戳,转为Java程序时区下的日期。(2024-02-24 21:34:55 > 2024-02-25 05:34:55
)
根据源代码的实现可以发现一个规律:都是先将日期根据所属时区转换为时间戳后,在根据需要转换的时区转换为最终日期。
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时区不一致 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!