项目中遇到存储的时间戳与真实时间相差14小时的现象,以下为解决步骤.

问题

CREATE TABLE `incident` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`recovery_time` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4;

以上为数据库建表语句,其中created_time是插入记录时自动设置,recovery_time需要手动进行设置.

测试时发现,created_time为正确的北京时间,然而recovery_time则与设置时间相差14小时.

尝试措施

jvm时区设置

//设置jvm默认时间
System.setProperty("user.timezone", "UTC");

数据库时区查询

查看数据库时区设置:

show variables like '%time_zone%';
--- 查询结果如下所示:
--- system_time_zone: CST
--- time_zone:SYSTEM

查询CST发现其指代比较混乱,有四种含义(参考网址:https://juejin.im/post/5902e087da2f60005df05c3d):

  • 美国中部时间 Central Standard Time (USA) UTC-06:00
  • 澳大利亚中部时间 Central Standard Time (Australia) UTC+09:30
  • 中国标准时 China Standard Time UTC+08:00
  • 古巴标准时 Cuba Standard Time UTC-04:00

此处发现如果按照美国中部时间进行推算,相差14小时,与Bug吻合.

验证过程

MyBatis转换

代码中,时间戳使用Instant进行存储,因此跟踪package org.apache.ibatis.type下的InstantTypeHandler.

@UsesJava8
public class InstantTypeHandler extends BaseTypeHandler<Instant> { @Override
public void setNonNullParameter(PreparedStatement ps, int i, Instant parameter, JdbcType jdbcType) throws SQLException {
ps.setTimestamp(i, Timestamp.from(parameter));
} //...代码shenglve
}

调试时发现parameter为正确的UTC时.

函数中调用Timestamp.fromInstant转换为Timestamp实例,检查无误.

    /**
* Sets the designated parameter to the given <code>java.sql.Timestamp</code> value.
* The driver
* converts this to an SQL <code>TIMESTAMP</code> value when it sends it to the
* database.
*
* @param parameterIndex the first parameter is 1, the second is 2, ...
* @param x the parameter value
* @exception SQLException if parameterIndex does not correspond to a parameter
* marker in the SQL statement; if a database access error occurs or
* this method is called on a closed <code>PreparedStatement</code> */
void setTimestamp(int parameterIndex, java.sql.Timestamp x)
throws SQLException;

继续跟踪setTimestamp接口,其具体解释见代码注释.

Sql Driver转换

项目使用com.mysql.cj.jdbc驱动,跟踪其setTimestampClientPreparedStatement类下的具体实现(PreparedStatementWrapper类下实现未进入).

    @Override
public void setTimestamp(int parameterIndex, Timestamp x) throws java.sql.SQLException {
synchronized (checkClosed().getConnectionMutex()) {
((PreparedQuery<?>) this.query).getQueryBindings().setTimestamp(getCoreParameterIndex(parameterIndex), x);
}
}

继续跟踪上端代码中的getQueryBindings().setTimestamp()实现(com.mysql.cj.ClientPreparedQueryBindings).

    @Override
public void setTimestamp(int parameterIndex, Timestamp x, Calendar targetCalendar, int fractionalLength) {
if (x == null) {
setNull(parameterIndex);
} else { x = (Timestamp) x.clone(); if (!this.session.getServerSession().getCapabilities().serverSupportsFracSecs()
|| !this.sendFractionalSeconds.getValue() && fractionalLength == 0) {
x = TimeUtil.truncateFractionalSeconds(x);
} if (fractionalLength < 0) {
// default to 6 fractional positions
fractionalLength = 6;
} x = TimeUtil.adjustTimestampNanosPrecision(x, fractionalLength, !this.session.getServerSession().isServerTruncatesFracSecs()); //注意此处时区转换
this.tsdf = TimeUtil.getSimpleDateFormat(this.tsdf, "''yyyy-MM-dd HH:mm:ss", targetCalendar,
targetCalendar != null ? null : this.session.getServerSession().getDefaultTimeZone()); StringBuffer buf = new StringBuffer();
buf.append(this.tsdf.format(x));
if (this.session.getServerSession().getCapabilities().serverSupportsFracSecs()) {
buf.append('.');
buf.append(TimeUtil.formatNanos(x.getNanos(), 6));
}
buf.append('\''); setValue(parameterIndex, buf.toString(), MysqlType.TIMESTAMP);
}
}

注意此处时区转换,会调用如下语句获取默认时区:

this.session.getServerSession().getDefaultTimeZone()

获取TimeZone数据,具体如下图所示:

检查TimeZone类中offset含义,具体如下所示:

    /**
* Gets the time zone offset, for current date, modified in case of
* daylight savings. This is the offset to add to UTC to get local time.
* <p>
* This method returns a historically correct offset if an
* underlying <code>TimeZone</code> implementation subclass
* supports historical Daylight Saving Time schedule and GMT
* offset changes.
*
* @param era the era of the given date.
* @param year the year in the given date.
* @param month the month in the given date.
* Month is 0-based. e.g., 0 for January.
* @param day the day-in-month of the given date.
* @param dayOfWeek the day-of-week of the given date.
* @param milliseconds the milliseconds in day in <em>standard</em>
* local time.
*
* @return the offset in milliseconds to add to GMT to get local time.
*
* @see Calendar#ZONE_OFFSET
* @see Calendar#DST_OFFSET
*/
public abstract int getOffset(int era, int year, int month, int day,
int dayOfWeek, int milliseconds);

offset表示本地时间UTC时的时间间隔(ms).

计算数值offset,发现其表示美国中部时间,即UTC-06:00.

  • Driver推断Session时区为UTC-6;
  • DriverTimestamp转换为UTC-6String;
  • MySql认为Session时区在UTC+8,将String转换为UTC+8.

因此,最终结果相差14小时,bug源头找到.

解决方案

参照https://juejin.im/post/5902e087da2f60005df05c3d.

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服务,问题解决.

此外,作为防御措施,可以在jdbc url中设置时区(如此设置可以不用修改MySql配置):

jdbc:mysql://localhost:3306/table_name?useTimezone=true&serverTimezone=GMT%2B8

此时,就告知连接进行时区转换,并且时区为UTC+8.

PS:

如果您觉得我的文章对您有帮助,请关注我的微信公众号,谢谢!

SpringBoot时间戳与MySql数据库记录相差14小时排错的更多相关文章

  1. mysql数据库优化课程---14、常用的sql技巧

    mysql数据库优化课程---14.常用的sql技巧 一.总结 一句话总结:其实就是sql中那些函数的使用 1.mysql中函数如何使用? 选择字段 其实就是作用域select的选择字段 3.转大写: ...

  2. 【入门】Spring-Boot项目配置Mysql数据库

    前言 前面参照SpringBoot官网,自动生成了简单项目点击打开链接 配置数据库和代码遇到的问题 问题1:cannot load driver class :com.mysql.jdbc.Drive ...

  3. 通用mapper版+SpringBoot+MyBatis框架+mysql数据库的整合

    转:https://blog.csdn.net/qq_35153200/article/details/79538440 开发环境: 开发工具:Intellij IDEA 2017.2.3 JDK : ...

  4. springboot 时间戳和 数据库时间相差14个小时

    在 springboot 开发过程中遇到一个奇怪的问题,就是已经设置系统时间GMT+8, 但是时间到数据库后会减少14个小时.后来发现是 jvm 时区和数据库时区设置不一致的问题. jvm 设置的是 ...

  5. mysql数据库记录

    ON DELETE restrict(约束):当在父表(即外键的来源表)中删除对应记录时,首先检查该记录是否有对应外键,如果有则不允许删除. no action:意思同restrict.即如果存在从数 ...

  6. [PHP]全国省市区信息,mysql数据库记录

    下载地址: https://files.cnblogs.com/files/wukong1688/T_Area.zip 或者也可以复制如下内容: CREATE TABLE IF NOT EXISTS ...

  7. mysql 时区问题导致的时间相差14小时

    1.mysql 字段名称 类型 begin_time TIME begin_time=08:18:39 2.java数据库连接串 jdbc:mysql://x.x.x.x:3306/y?useUnic ...

  8. MySQL数据库---记录相关操作

    序 表中记录的相关操作一共四种:插入,更新,删除.查询.其中使用最多,也是最难的就是查询. 记录的插入 1. 插入完整数据(顺序插入) 语法一: INSERT INTO 表名(字段1,字段2,字段3… ...

  9. springboot后端时间到前端,相差8小时,时间格式不对

    spring boot后台时间正确,返回给前台的时间不正确,和后台差8个小时 { "code": 1, "msg": "SUCCESS", ...

随机推荐

  1. IIS处理并发请求设置

    一个ASP.NET项目在部署到生产环境时,当用户并发量达到200左右时,IIS出现了明显的请求排队现象,发送的请求都进入等待,无法及时响应,系统基本处于不可用状态.   当发现请求明显延迟,没有被即时 ...

  2. ArcMap中获取要素的Extent值

    新建xmax,xmin,ymax,ymin4个字段,右键,在[字段计算器]中选择Python,分别输入!shape.extent.XMax!.!shape.extent.XMin!.!shape.ex ...

  3. 022 包含min函数的栈

    1.题目 定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1)). 2.分析 最初想法是定义一个成员变量min来存放最小元素,但是当最小元素弹出后,min ...

  4. 做IT,必备的安全知识!

    以前的认知 以前刚接触IT行业,而我身为运维,我以为我所需要做的安全就是修改服务器密码为复杂的,ssh端口改为非22,还有就是不让人登录服务器就可以保证我维护的东西安全. 现在的认知 工作也好几年了, ...

  5. 四、自动化平台搭建-Django-如何做验证码

    前提:安装包   pip install pillow==3.4.1 1.打开booktest/views.py,创建视图verify_code. from PIL import Image, Ima ...

  6. ZOJ 2588 Burning Bridges 割边(处理重边)

    <题目链接> 题目大意: 给定一个无向图,让你尽可能的删边,但是删边之后,仍然需要保证图的连通性,输出那些不能被删除的边. 解题分析: 就是无向图求桥的题目,主要是提高一下处理重边的姿势. ...

  7. mongodbwindows安装过程附带安装包百度云

    1.mongodb安装包链接 链接:https://pan.baidu.com/s/1bxZ2oV-iJEs7RoH5kN6jVg 密码:ajuj   2.配置准备,创建文件夹及文件: 目录为:  \ ...

  8. CloseableHttpClient获取https请求不验证证书

    创建---调用 CloseableHttpClient httpclient = getHttpsClient(); /** * 获取https连接(不验证证书) * * @return */ pri ...

  9. spring-cloud-Zuul学习(四)【中级】--自定义zuul Filter详解【重新定义spring cloud实践】

      实现自定义zuul Filter 方法很简单,只要继承ZuulFilter跟加入到spring IOC容器即可,zuulFilter是一个抽象类,里面包含以下方法需要我们实现: String fi ...

  10. 【C语言】多项式加法(mooc第七周测试题)

    这个小题目吧我折磨的够呛,,主要在于特殊情况考虑不周,测试用例老是通不过.. 小结: 做法:用一个数组来存储多项式,用下标表示幂次数,数组元素值表示对应系数 输出特殊格式考虑:系数和幂次数为0,1,- ...