一次JDBC与MySQL因“CST”时区协商误解导致时间差了13或14个小时
一、知识扫盲
JDBC:全称是java DataBase Connectivity
中文意思是java数据库连接
解释就是用于java编程语言和数据库之间的数据库无关的标准java API
二、错误展示
错误不好描述,直接看图:
三、分析原因
在服务器上执行“date”,看到时间,看到有CST字样
[root@dabiaoge ~]# date
Wed Jan 9 21:54:23 CST 2019
继续知识点扫盲:
CST时区
名为CST的时区是一个很混乱的时区,有四种含义:美国中部时区Central Standard Time(USA)UTC-06:00
澳大利亚中部时区中央标准时间(澳大利亚)UTC + 09:30
中国标准时中国标准时区UTC + 08:00
古巴标准时古巴标准时区UTC-04:00原因:CST的时区是一个很混乱的时区,在与MySQL协商会话时区时,Java会误以为是CST -0500或者CST -0600,而非CST +0800
解释原因是什么意思:
先来了解下美国的时区变化历史,美国规定每年从“3月11日”至“11月7日”实行夏令时,美国中部时区改为UTC-05:00;而“11月7日”至“3月11日”实行冬令时,美国中部时区改为UTC-06:00,博主的线上问题发现的时间是2019年1月9日,而此时美国中部的时区是UTC0600,而我们的时区是UTC0800,所以6+8=14个小时,因此线上的错误时间相隔14个小时。
四、排错过程
在项目中,偶然发现数据库中存储的 Timestamp 字段的 unix_timestamp() 值比真实值少了 14 个小时。通过调试追踪,发现了 com.mysql.cj.jdbc 里的时区协商有问题。
当 JDBC 与 MySQL 开始建立连接时,会调用 com.mysql.cj.jdbc.ConnectionImpl.initializePropsFromServer() 获取服务器参数,其中我们看到调用 this.session.configureTimezone() 函数,它负责配置时区。
public void configureTimezone() { String configuredTimeZoneOnServer = getServerVariable("time_zone"); if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) { configuredTimeZoneOnServer = getServerVariable("system_time_zone"); } String canonicalTimezone = getPropertySet().getStringReadableProperty(PropertyDefinitions.PNAME_serverTimezone).getValue(); if (configuredTimeZoneOnServer != null) { // user can override this with driver properties, so don't detect if that's the case if (canonicalTimezone == null || StringUtils.isEmptyOrWhitespaceOnly(canonicalTimezone)) { try { canonicalTimezone = TimeUtil.getCanonicalTimezone(configuredTimeZoneOnServer, getExceptionInterceptor()); } catch (IllegalArgumentException iae) { throw ExceptionFactory.createException(WrongArgumentException.class, iae.getMessage(), getExceptionInterceptor()); } } } if (canonicalTimezone != null && canonicalTimezone.length() > 0) { this.serverTimezoneTZ = TimeZone.getTimeZone(canonicalTimezone); // The Calendar class has the behavior of mapping unknown timezones to 'GMT' instead of throwing an exception, so we must check for this... if (!canonicalTimezone.equalsIgnoreCase("GMT") && this.serverTimezoneTZ.getID().equals("GMT")) { throw ... } } this.defaultTimeZone = this.serverTimezoneTZ; }
追踪代码可知,当 MySQL 的 time_zone 值为 SYSTEM 时,会取 system_time_zone 值作为协调时区。
让我们登录到 MySQL 服务器验证这两个值:
mysql> show variables like '%time_zone%'; +------------------+--------+ | Variable_name | Value | +------------------+--------+ | system_time_zone | CST | | time_zone | SYSTEM | +------------------+--------+ 2 rows in set (0.00 sec)
重点在这里!若 String configuredTimeZoneOnServer 得到的是 CST 那么 Java 会误以为这是 CST -0600 ,因此 TimeZone.getTimeZone(canonicalTimezone) 会给出错误的时区信息。debug variables
本机默认时区是 Asia/Shanghai +0800 ,误认为服务器时区为 CST -0600 ,实际上服务器是 CST +0800 。我们会想到,即便时区有误解,如果 Timestamp 是以 long 表示的时间戳传输,也不会出现问题,下面让我们追踪到 com.mysql.cj.jdbc.PreparedStatement.setTimestamp()
public void setTimestamp(int parameterIndex, Timestamp x) throws java.sql.SQLException { synchronized (checkClosed().getConnectionMutex()) { setTimestampInternal(parameterIndex, x, this.session.getDefaultTimeZone()); } }
注意到这里 this.session.getDefaultTimeZone() 得到的是刚才那个 CST -0600
private void setTimestampInternal(int parameterIndex, Timestamp x, TimeZone tz) throws SQLException { if (x == null) { setNull(parameterIndex, MysqlType.TIMESTAMP); } else { if (!this.sendFractionalSeconds.getValue()) { x = TimeUtil.truncateFractionalSeconds(x); } this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = MysqlType.TIMESTAMP; if (this.tsdf == null) { this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss", Locale.US); } this.tsdf.setTimeZone(tz); StringBuffer buf = new StringBuffer(); buf.append(this.tsdf.format(x)); if (this.session.serverSupportsFracSecs()) { buf.append('.'); buf.append(TimeUtil.formatNanos(x.getNanos(), true)); } buf.append('\''); setInternal(parameterIndex, buf.toString()); } }
原来 Timestamp 被转换为会话时区的时间字符串了。问题到此已然明晰:
JDBC 误认为会话时区在 CST-6
JBDC 把 Timestamp+0 转为 CST-6 的 String-6
MySQL 认为会话时区在 CST+8,将 String-6 转为 Timestamp-14最终结果相差 14个小时!如果处在夏令时还会相差 13个小时
五、解决方法
解决办法非常的简单,手动明确指定 MySQL 数据库的时区,不使用引发误解的 CST:
临时生效:
mysql> set global time_zone = '+08:00';
Query OK, 0 rows affected (0.00 sec)mysql> set time_zone = '+08:00';
Query OK, 0 rows affected (0.00 sec)永久生效(修改后需要重启mysql):
修改 my.cnf 文件,在 [mysqld] 节下增加 default-time-zone = '+08:00'
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
终于搞定了回家车票
快要春节了,老板发话:'提前完成工作可以提前回家';于是老猫每天加班加点赶进度,估计提前1周回家;正当老猫沉浸在幸福之时,老板过来关心问我:'老猫,车票买了吗,买不到晚几天走吧,那会好买';忽然有种被算计的感觉!!人无远虑必有近忧,车票是个大问题,老猫要买的车票这两天放票;于是每天发动同事帮我抢票,可是连续两次都没抢到;老猫有点慌了,买不到车票怎么办? 那么多车票,为什么好几个人抢连续两天都抢不到;老猫分析其中可能存在原因,分析过程如下: 购票流程分析: 1>老猫买的是比较紧张的车次,抢票人数远远大于出票数量;2>每次抢票和网速,手速有一定关系;3>虽然感觉每次手动购票速度很快,但是购票人数多,手速快的也很多,对比而言也就不快了。 购票中存在影响因素: 老猫又仔细分析了网页购票过程: 1>登录,验证码与密码登录;2>刷票,选择始发站,终点站,日期,车次;3>出票后点击预购;4>等待排队,选择乘车人; 理论上大家刷票过程都是一样的,但是几个因素会影响我们购票结果: 1>第一步登录,这个没有问题,老猫和大家都会提前登录;2>第二步:先看...
- 下一篇
Centos7 搭建LDAP并启用TLS加密
简介 LDAP(轻量级目录访问协议,Lightweight Directory Access Protocol)是为了实现目录服务的信息服务。 目录服务是一种特殊的数据库系统,其专门针对读取,浏览和搜索操作进行了特定的优化。在网络中应用了LDAP后,用户只需要使用一个账号和密码就可以轻松访问网络中的所有服务,实现用户身份的统一认证。 简单来说:拿LDAP来统一管理一些账号,例如: Gitlab,JenKins,Samba,SVN,Zabbix等。 关于SSL/TLS LDAP over SSL # LDAP over SSL 也就是 ldaps # ldap默认不加密情况下是走的389端口 # 当使用ldaps的时候走的就是636端口了 # 可以简单理解成http和https的关系 # 当然ldaps已经淘汰了,不然也不会有LDAP over TLS出来 LDAP over TLS # TLS可以简单理解为ldaps的升级 # 它默认走389端口,但是会通讯的时候加密 # 客户端连接LDAP时,需要指明通讯类型为TLS,所以他可以跟不加密的模式一样,任意端口都行 对比一下连接方式: l...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS7设置SWAP分区,小内存服务器的救世主
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS关闭SELinux安全模块
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS8编译安装MySQL8.0.19