【曹工杂谈】Mysql-Connector-Java时区问题的一点理解--写入数据库的时间总是晚13小时问题
背景
去年写了一篇“【曹工杂谈】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,可以看到,没有漏洞字样:
版本差异
先给一份官方的:
其实可以看出来,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
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
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,也没影响。
还有些大家不用感知的,比如一些接口的包名发生变化,一些异常类被删除了,因为我们一般不会直接用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
,开始了第二次设置。
这个方法比较长,我分两三段来截图。
上图比较清楚,就是:
获取服务端的"time_zone"配置,如果“time_zone”为“system”,则获取“system_time_zone”的配置
我这边数据库吧,反正默认装好就是这样的,正好就是cst和system,也没动过,所以这也是为啥国内大家很多人遇到这个问题的原因。
获取客户端自身建立连接时候的配置,通俗来说,就是dbUrl里面那些connection property
如果客户端没配,则以服务端的为准
再接下来,就是以CST来设置成本次会话的默认时区。下面最后一行红框的,也就是这第二次设置。
解决问题的思路
通过上面,我们知道了,如果客户端没设置时区,就会用服务端的。所以,两种改法:
把服务端配置的system_time_zone和time_zone改成正确的,网上也有些教程,就是这样。但是我们这边公司大,数据库很多业务在用,这么改,怕影响到别人
客户端连接url中,指定时区
也就是这样指定serverTimezone:
jdbc:mysql://1.1.1.1:3306/test_ckl?useSSL=false&serverTimezone=Asia/Shanghai
我们改了客户端,再看看。
跑完程序,正常查询到数据:
id: 8; name:yyyy; time:22:49:27
扩展信息
这个整个交互中,一共有如下几次网络请求。
- tcp三次握手
- 登录请求,带着用户名、密码去登录
- 接下来,就是那次查询服务端各种配置参数的请求,包括time_zone等全局variable
- show warnings,这次请求应该就是看看服务端有没有什么警告信息
- 客户端发起的,"set names latin1"
- 客户端发起:“SET character_set_results = NULL”
- 客户端发起:SET autocommit=1
- 我们的业务查询请求
- 结束会话
- 4次挥手
具体可以看下面的红框部分:
总结
这个参数在服务端的配置我还没来得及去看,不过对客户端的影响,基本大致了解了。如果对大家也有些帮助,荣幸之至,谢谢大家。
【曹工杂谈】Mysql-Connector-Java时区问题的一点理解--写入数据库的时间总是晚13小时问题的更多相关文章
- java时区问题设置,new Date()和系统时间相差8个小时
出现这种问题有可能是服务时间没有修改. import java.text.DateFormat;import java.text.ParseException;import java.text.Sim ...
- 【曹工杂谈】Mysql客户端上,时间为啥和本地差了整整13个小时,就离谱
瞎扯一点非技术 本来今天上午就打算写的,结果中途被别的事吸引了注意力,公司和某保险公司合作推了一个医疗保险,让我们给父母买,然后我研究了半天条款:又想起来之前买的支付宝那个好医保,也买了两年多了,但是 ...
- Mybatis异常处理之MySQL Connector Java] will not be managed by Spring
很长时间没写后台代码有点生疏了,这不今天又出点小插曲,写个文章记录下. 由于要上传点数据到后台,顺手整了个mybatis+springmvc.在保存数据时出现了异常. Creating a new S ...
- 【曹工杂谈】Maven源码调试工程搭建
Maven源码调试工程搭建 思路 我们前面的文章<[曹工杂谈]Maven和Tomcat能有啥联系呢,都穿打补丁的衣服吗>分析了Maven大体的执行阶段,主要包括三个阶段: 启动类阶段,负责 ...
- JAVA处理Excel表格数据并写入数据库
package com.hncj.test; import java.io.FileInputStream; import java.sql.Connection; import java.sql.D ...
- 曹工杂谈--使用mybatis的同学,进来看看怎么在日志打印完整sql吧,在数据库可执行那种
前言 今天新年第一天,给大家拜个年,祝大家新的一年里,技术突突突,头发长长长! 咱们搞技术的,比较直接,那就开始吧.我给大家看看我demo工程的效果(代码下边会给大家的): 技术栈是mybatis/m ...
- 曹工杂谈:Java 类加载还会死锁?这是什么情况?
一.前言 今天事不是很多,正好在Java交流群里,看到一个比较有意思的问题,于是花了点时间研究了一下,这里做个简单的分享. 先贴一份测试代码,大家可以先猜测一下,执行结果会是怎样的: import j ...
- 曹工杂谈:手把手带你读懂 JVM 的 gc 日志
一.前言 今天下午本来在划水,突然看到微信联系人那一个红点点,看了下,应该是博客园的朋友.加了后,这位朋友问了我一个问题: 问我,这两块有什么关系? 看到这段 gc 日志,一瞬间脑子还有点懵,嗯,这个 ...
- 曹工杂谈:一例简单的Jar包冲突解决示例
Jar包冲突的相关文章: 了不得,我可能发现了Jar 包冲突的秘密 一.前言 jar包冲突分多种,简单理解来说,就是同package且同名的类在多个jar包内出现,如果两个jar包在同一个clas ...
随机推荐
- vue使用npm安装sass
npm install --save-dev sass-loader style-loader css-loader npm install --save-dev extract-text-webpa ...
- postman设置token等关联参数
登陆时登录成功后服务器会返回一个token,这个token作为第二步骤的入参:第二个步骤请求成功后服务器会返回一个新token,然后这个token作为第三步骤的入参!如此一来的话,要用postman做 ...
- Windows 和 Ubuntu 的网络能互相 ping 通之后,linux无法上网原因:①路由没设置好,②DNS 没设置好
确保 Windows 和 Ubuntu 的网络能互相 ping 通之后,如果 Ubuntu 无法上网,原因通常有 2 个:路由没设置好,DNS 没设置好. 如果执行以下命令不成功,表示路由没设置好: ...
- 微服务架构 | 3.3 Apache Zookeeper 注册中心
@ 目录 前言 1. Zookeeper 基础知识 1.1 Zookeeper 是什么 1.2 Zookeeper 的数据结构 1.3 Watcher 机制 1.4 常见应用场景分析 1.5 Zook ...
- 基于华为云服务器的FTP站点搭建
前言 主要介绍了华为云上如何使用弹性云服务器的Linux实例使用vsftpd软件搭建FTP站点.vsftpd全称是"very secure FTP daemon",是一款在Linu ...
- Javascript中定时器的使用方法
Javascript中定时器的使用方法 1.间隔定时器(每隔一段时间执行一次代码) 格式:setInterval(函数,时间) //时间单位是毫秒,每隔设置的时间执行函数里的内容一遍(一直执行) // ...
- winform设置所有窗体统一图标
class WindowHookerManager { static WindowHooker hooker = new WindowHooker(); public static void SetA ...
- 【记录一个问题】cv::cuda::dft()比cv::dft()慢很多
具体的profile调用图如下: 可以看见compute很快,但是构造函数很慢. nvidia官网看到几篇类似的帖子,但是没有讲明白怎么解决的: opencv上的参考文档:https://docs.o ...
- ros实例_百度语音+图灵
1 百度语音模块 参考http://blog.csdn.net/u011118482/article/details/55001444 1.1 百度语音识别包 git clonehttps://git ...
- CMake语法—内置变量
目录 CMake语法-内置变量 1 CMake变量分类 1.1 普通变量 1.2 缓存变量 1.3 环境变量 1.4 内置变量 2 CMake内置变量分类 2.1 提供信息的变量 2.2 改变行为的变 ...