前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >mysql时区问题的一点理解--写入数据库的时间总是晚13小时问题

mysql时区问题的一点理解--写入数据库的时间总是晚13小时问题

作者头像
低级知识传播者
发布2022-03-28 18:59:11
2.3K0
发布2022-03-28 18:59:11
举报
文章被收录于专栏:低级Java知识传播者

mysql时区问题的一点理解--写入数据库的时间总是晚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个小时。

代码语言:javascript
复制
    <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,举例:

代码语言:javascript
复制
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版本不需要我们自己再去写这种代码:

代码语言:javascript
复制
// 注册 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过程

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

代码语言:javascript
复制
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,什么时候设置的

第一次设置(初始化)

代码语言:javascript
复制
targetCalendar != null ? null : this.session.getServerSession().getDefaultTimeZone()

这里面其实是获取了:

代码语言:javascript
复制
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,也没动过,所以这也是为啥国内大家很多人遇到这个问题的原因。
  1. 获取客户端自身建立连接时候的配置,通俗来说,就是dbUrl里面那些connection property
  2. 如果客户端没配,则以服务端的为准

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

解决问题的思路

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

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

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

也就是这样指定serverTimezone:

代码语言:javascript
复制
jdbc:mysql://1.1.1.1:3306/test_ckl?useSSL=false&serverTimezone=Asia/Shanghai

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

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

代码语言:javascript
复制
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次挥手

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

总结

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-02-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 低级知识传播者 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 关于mysql-connector-java
    • 主要版本
      • 版本差异
      • 错误的时间,是客户端发送前就错了,还是服务端错了
        • 界定问题范围
          • debug过程
            • 会话中的时区变量,怎么是CST,什么时候设置的
              • 第一次设置(初始化)
              • 在第一次设置和第二次设置之间
              • 第二次设置
            • 解决问题的思路
            • 扩展信息
            • 总结
            相关产品与服务
            云数据库 SQL Server
            腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档