背景

去年写了一篇“【曹工杂谈】Mysql客户端上,时间为啥和本地差了整整13个小时,就离谱”,结果最近还真就用上了。

不是我用上,是组内一位同事,他也是这样:有个服务往数据库insert记录,记录里有时间,比如时间A。然后写进数据库后,数据库里的时间是A-13,晚了13小时。然后就改了这么个地方:

写进去的数据,就是正确的时间了。

后边,他还有一个查询服务,要去查写进去那条记录,比如记录有个创建时间字段,字段值是2022-02-19 00:00:00. 然后假设我查的时候,就根据这个时间来查,传个2022-02-19 00:00:00。结果发现,查不到。为啥呢,因为参数里的时间也被减了13个小时,导致和服务器端记录的时间匹配不上了。

其实,两个问题,是同一个问题,最终的解决办法也是一样的。

这个问题,抽象一下,就是,在mysql-connector-java 8.0.x版本下,我们发送给服务器的时间,为啥会少了13个小时。

    <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>

关于mysql-connector-java

主要版本

现在主流的版本,有两个,5.1.x系列和8.0.x系列,5.1.x系列最新的一个版本是5.1.49.

大家看下图,有红色字样的 "1 vulnerability",表示有漏洞,这也是为什么我们同事为啥要升级或者是被安全组逼着升级到8.0.x版本的原因。

8.0.x的最新版本是8.0.28,可以看到,没有漏洞字样:

版本差异

  1. 先给一份官方的:

    其实可以看出来,5.1和8.0的兼容性都不错,都支持mysql server端:5.6/5.7/8/0,差异无非是对jre和jdk的版本不一样。

    这里多说一句,mysql-connector-java是jdbc规范的一个实现,jdbc规范相关接口(java.sql和javax.sql里的就是,比如java.sql.Driver),跟随jdk一起发布。

    jdbc规范版本 jdk
    4.0 jdk 6
    4.1 jdk 7
    4.2 jdk 8
    4.3 jdk 9及以后

    可参考:https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/package-summary.html

  2. connection property发生了变化,什么是connection property,举例:

    jdbc:mysql://1.1.1.1:3306/test?useSSL=false&serverTimezone=Asia/Shanghai

    上面的useSSL、serverTimezone就是connection property。

    具体变化:https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-properties-changed.html

  3. mysql driver的类名也发生了变化,5.1.x版本是叫 "com.mysql.jdbc.Driver",8.0.x里面是 "com.mysql.cj.jdbc.Driver",而且,8.0版本不需要我们自己再去写这种代码:

    // 注册 JDBC 驱动
    String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    Class.forName(JDBC_DRIVER);

    当然了,8.0版本对5.1版本做了兼容,你即使加载5.1的driver,也没影响。

  4. 还有些大家不用感知的,比如一些接口的包名发生变化,一些异常类被删除了,因为我们一般不会直接用mysql-connector-java去编程,我们都是用jdbc接口嘛,实现类再怎么变,也没什么影响

    https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-exceptions-changes.html

错误的时间,是客户端发送前就错了,还是服务端错了

界定问题范围

问一下自己这个问题,主要是界定问题发生的地方。这个也容易界定,最理想的方式就是网络抓包,wireshark或者tcpdump自己选吧。

这里先看下我的测试程序要做的事:

数据库有下面这一条记录,我要做的,就是根据时间参数,把记录查出来。

程序如下:

我如果实际执行这个demo,是查不出结果的,为啥呢,我网络抓包的截图给大家看看:

至于这个错误的时间,是怎么来的,那可能确实需要慢慢去debug。

debug过程

看看我们前面的代码,设置时间参数主要是下面这一行:

Timestamp timestamp = new Timestamp(simpleDateFormat.parse("2022-02-17 22:49:27").getTime());
preparedStatement.setTimestamp(1, timestamp);

那我们直接一点,就在这行打上断点,开始调试:

这里看得出来,是给this.query这个对象,设置相关的绑定参数。我们继续跟进:

此时,时间依然还是正确的。我们传了4个参数到setTimestamp方法,注意,第三个参数targetCalendar为null,这个参数会影响内部的分支。

看上图,这里因为targetCalendar为null,所以会去获取当前这个mysql会话中的时区字段。

这个时区是啥呢,就是CST。

也就是说,2022-02-17 22:49:27 这个时间,在CST时区下,就是 2022-02-17 08:49:27。

这里CST说是有好几个时区都是这个缩写,比如:

  • Central Standard Time, North America's Central Time Zone: UTC−06:00,这个时间基本就是北美中部时间,北美中部包括了:美国、加拿大、墨西哥的中部地区
  • China Standard Time: UTC+08:00,这个就是中国的北京时间了,但感觉CST一般还是指:北美中部时间
  • Cuba Standard Time: UTC−04:00,这个其实点链接,会跳转进入美洲东部时间的wiki,因为古巴也是在北美东部位置,包括了:美国、加拿大、墨西哥东南、巴拿马、哥伦比亚、厄瓜多尔、秘鲁等(这里也有中美洲的一些地区)

可能国际上来说,看到CST,首先是任务是美国中部时区Central Standard Time(USA)UTC-06:00。一般不是是另外两个时区,中国那肯定就是Asia/Shanghai,古巴这种小国,存在感也较弱

这个时区,是零时区 - 6(美国冬令时,从11月7日3月11日)或者是零时区 - 5(夏令时,从“3月11日”至“11月7日”),因为现在是美国的冬令时,所以这里差14小时(我们是东八区嘛,8 + 6)。

ok,言归正传,反正问题就是出现在:会话的时区不对,为啥是CST啊,能不能改?

会话中的时区变量,怎么是CST,什么时候设置的

第一次设置(初始化)

targetCalendar != null ? null : this.session.getServerSession().getDefaultTimeZone()

这里面其实是获取了:

com.mysql.cj.protocol.a.NativeServerSession#getDefaultTimeZone    

private TimeZone defaultTimeZone = TimeZone.getDefault();
public TimeZone getDefaultTimeZone() {
return this.defaultTimeZone;
}

我们可以在这个字段上打个断点,看看这个值什么时候被设置:

然后重新debug整个程序,看看什么时候进入该field断点。我们会发现,第一次进入,就是在new这个类的对象时,

可以看看这个堆栈,基本就是获取connection的时候,相当于就是建立一个会话,所以这里会去new一个会话出来。

我看了下,在我机器上,初始化后,是东八区。

在第一次设置和第二次设置之间

这之间发生了一次重要的网络请求,

客户端向服务端请求各种服务端的variable,也就是服务端的配置。上面有两个时区相关的,system_time_zone和time_zone。

第二次设置

接下来,运行到了com.mysql.cj.protocol.a.NativeProtocol#configureTimezone,开始了第二次设置。

这个方法比较长,我分两三段来截图。

上图比较清楚,就是:

  1. 获取服务端的"time_zone"配置,如果“time_zone”为“system”,则获取“system_time_zone”的配置

    我这边数据库吧,反正默认装好就是这样的,正好就是cst和system,也没动过,所以这也是为啥国内大家很多人遇到这个问题的原因。

  2. 获取客户端自身建立连接时候的配置,通俗来说,就是dbUrl里面那些connection property

  3. 如果客户端没配,则以服务端的为准

再接下来,就是以CST来设置成本次会话的默认时区。下面最后一行红框的,也就是这第二次设置。

解决问题的思路

通过上面,我们知道了,如果客户端没设置时区,就会用服务端的。所以,两种改法:

  1. 把服务端配置的system_time_zone和time_zone改成正确的,网上也有些教程,就是这样。但是我们这边公司大,数据库很多业务在用,这么改,怕影响到别人

  2. 客户端连接url中,指定时区

    也就是这样指定serverTimezone:

    jdbc:mysql://1.1.1.1:3306/test_ckl?useSSL=false&serverTimezone=Asia/Shanghai

我们改了客户端,再看看。

跑完程序,正常查询到数据:

id: 8; name:yyyy; time:22:49:27

扩展信息

这个整个交互中,一共有如下几次网络请求。

  1. tcp三次握手
  2. 登录请求,带着用户名、密码去登录
  3. 接下来,就是那次查询服务端各种配置参数的请求,包括time_zone等全局variable
  4. show warnings,这次请求应该就是看看服务端有没有什么警告信息
  5. 客户端发起的,"set names latin1"
  6. 客户端发起:“SET character_set_results = NULL”
  7. 客户端发起:SET autocommit=1
  8. 我们的业务查询请求
  9. 结束会话
  10. 4次挥手

具体可以看下面的红框部分:

总结

这个参数在服务端的配置我还没来得及去看,不过对客户端的影响,基本大致了解了。如果对大家也有些帮助,荣幸之至,谢谢大家。

【曹工杂谈】Mysql-Connector-Java时区问题的一点理解--写入数据库的时间总是晚13小时问题的更多相关文章

  1. java时区问题设置,new Date()和系统时间相差8个小时

    出现这种问题有可能是服务时间没有修改. import java.text.DateFormat;import java.text.ParseException;import java.text.Sim ...

  2. 【曹工杂谈】Mysql客户端上,时间为啥和本地差了整整13个小时,就离谱

    瞎扯一点非技术 本来今天上午就打算写的,结果中途被别的事吸引了注意力,公司和某保险公司合作推了一个医疗保险,让我们给父母买,然后我研究了半天条款:又想起来之前买的支付宝那个好医保,也买了两年多了,但是 ...

  3. Mybatis异常处理之MySQL Connector Java] will not be managed by Spring

    很长时间没写后台代码有点生疏了,这不今天又出点小插曲,写个文章记录下. 由于要上传点数据到后台,顺手整了个mybatis+springmvc.在保存数据时出现了异常. Creating a new S ...

  4. 【曹工杂谈】Maven源码调试工程搭建

    Maven源码调试工程搭建 思路 我们前面的文章<[曹工杂谈]Maven和Tomcat能有啥联系呢,都穿打补丁的衣服吗>分析了Maven大体的执行阶段,主要包括三个阶段: 启动类阶段,负责 ...

  5. JAVA处理Excel表格数据并写入数据库

    package com.hncj.test; import java.io.FileInputStream; import java.sql.Connection; import java.sql.D ...

  6. 曹工杂谈--使用mybatis的同学,进来看看怎么在日志打印完整sql吧,在数据库可执行那种

    前言 今天新年第一天,给大家拜个年,祝大家新的一年里,技术突突突,头发长长长! 咱们搞技术的,比较直接,那就开始吧.我给大家看看我demo工程的效果(代码下边会给大家的): 技术栈是mybatis/m ...

  7. 曹工杂谈:Java 类加载还会死锁?这是什么情况?

    一.前言 今天事不是很多,正好在Java交流群里,看到一个比较有意思的问题,于是花了点时间研究了一下,这里做个简单的分享. 先贴一份测试代码,大家可以先猜测一下,执行结果会是怎样的: import j ...

  8. 曹工杂谈:手把手带你读懂 JVM 的 gc 日志

    一.前言 今天下午本来在划水,突然看到微信联系人那一个红点点,看了下,应该是博客园的朋友.加了后,这位朋友问了我一个问题: 问我,这两块有什么关系? 看到这段 gc 日志,一瞬间脑子还有点懵,嗯,这个 ...

  9. 曹工杂谈:一例简单的Jar包冲突解决示例

    Jar包冲突的相关文章: 了不得,我可能发现了Jar 包冲突的秘密   一.前言 jar包冲突分多种,简单理解来说,就是同package且同名的类在多个jar包内出现,如果两个jar包在同一个clas ...

随机推荐

  1. LINUX学习-Mysql集群-主主备份

    接着主从备份继续. 1.编辑主从服务器 vim /etc/my.cnf 在server-id下添加一句 忽略一些信息 binlog-ignore-db=mysql 2.从服务器也授权给主服务器 gra ...

  2. kafka学习笔记(七)kafka的状态机模块

    概述 这一篇随笔介绍kafka的状态机模块,Kafka 源码中有很多状态机和管理器,比如之前我们学过的 Controller 通道管理器 ControllerChannelManager.处理 Con ...

  3. 【linux】Ubuntu20.04使用apt下载和卸载openJDK

    Ubuntu20.04使用apt下载和卸载openJDK 前言 由于最近电脑装了ubuntu和win双系统,想再ubuntu上学习.在成功配置完系统之后,开始了配学习环境的旅程.... 这次的是使用u ...

  4. Tomcat服务器和Servlet版本的对应关系

    Tomcat服务器和Servlet版本的对应关系 Servlet 程序从2.5版本是现在世面使用最多的版本(xml配置) 到了Servlet3.0后.就是注解版本的Servlet使用

  5. zabbix_proxy3.4安装

    server ip:172.16.2.116 proxy ip:172.16.2.241 1.创建proxy数据库,proxy用户 mysql> create database zabbix_p ...

  6. Typora + picgo + Chevereto 优雅地上传图片

    需求:由于经常使用typora写文章,但是配合picgo使用的sm.ms图床,github图床等,速度不稳定,因此我们想要利用自己的服务器搭建自己的图床,将自己文章的图片上传到自己的服务器中,优雅地写 ...

  7. JVM内存模型(五)

    一.JVM内存模型 1.1.与运行时数据区     前面讲过了运行时数据区那接下来我们聊下内存模型,JVM的内存模型指的是方法区和堆:在很多情况下网上讲解会把内存模型和运行时数据区认为是一个东西,这是 ...

  8. vite搭建vue项目-集成别名@、router、vuex、scss就是这样简单

    为什么要使用vite 当我们开始构建越来越大型的应用时, 需要处理的 JavaScript 代码量也呈指数级增长. 包含数千个模块的大型项目相当普遍. 这个时候我们会遇见性能瓶颈 使用 JavaScr ...

  9. IDE集成git

    目录 简介 Git安装 IDE集成Git IDE集成Git代码的创建分享上传 代码的下载和普通上传 分子的创建以及合并 代码的回滚 查看历史版本 简介 Git 是一个开源的分布式版本控制软件,用以有效 ...

  10. java getSource()和 getActionCommand()区别

    感谢大佬:https://blog.csdn.net/LIU_YANZHAO/article/details/72740011?utm_source=blogxgwz1 比如说 按纽的事件,同一个JF ...