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

问题

  1. CREATE TABLE `incident` (
  2. `id` int(11) NOT NULL AUTO_INCREMENT,
  3. `created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  4. `recovery_time` timestamp NULL DEFAULT NULL,
  5. PRIMARY KEY (`id`)
  6. ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4;

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

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

尝试措施

jvm时区设置

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

数据库时区查询

查看数据库时区设置:

  1. show variables like '%time_zone%';
  2. --- 查询结果如下所示:
  3. --- system_time_zone: CST
  4. --- 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.

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

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

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

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

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

Sql Driver转换

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

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

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

  1. @Override
  2. public void setTimestamp(int parameterIndex, Timestamp x, Calendar targetCalendar, int fractionalLength) {
  3. if (x == null) {
  4. setNull(parameterIndex);
  5. } else {
  6. x = (Timestamp) x.clone();
  7. if (!this.session.getServerSession().getCapabilities().serverSupportsFracSecs()
  8. || !this.sendFractionalSeconds.getValue() && fractionalLength == 0) {
  9. x = TimeUtil.truncateFractionalSeconds(x);
  10. }
  11. if (fractionalLength < 0) {
  12. // default to 6 fractional positions
  13. fractionalLength = 6;
  14. }
  15. x = TimeUtil.adjustTimestampNanosPrecision(x, fractionalLength, !this.session.getServerSession().isServerTruncatesFracSecs());
  16. //注意此处时区转换
  17. this.tsdf = TimeUtil.getSimpleDateFormat(this.tsdf, "''yyyy-MM-dd HH:mm:ss", targetCalendar,
  18. targetCalendar != null ? null : this.session.getServerSession().getDefaultTimeZone());
  19. StringBuffer buf = new StringBuffer();
  20. buf.append(this.tsdf.format(x));
  21. if (this.session.getServerSession().getCapabilities().serverSupportsFracSecs()) {
  22. buf.append('.');
  23. buf.append(TimeUtil.formatNanos(x.getNanos(), 6));
  24. }
  25. buf.append('\'');
  26. setValue(parameterIndex, buf.toString(), MysqlType.TIMESTAMP);
  27. }
  28. }

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

  1. this.session.getServerSession().getDefaultTimeZone()

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

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

  1. /**
  2. * Gets the time zone offset, for current date, modified in case of
  3. * daylight savings. This is the offset to add to UTC to get local time.
  4. * <p>
  5. * This method returns a historically correct offset if an
  6. * underlying <code>TimeZone</code> implementation subclass
  7. * supports historical Daylight Saving Time schedule and GMT
  8. * offset changes.
  9. *
  10. * @param era the era of the given date.
  11. * @param year the year in the given date.
  12. * @param month the month in the given date.
  13. * Month is 0-based. e.g., 0 for January.
  14. * @param day the day-in-month of the given date.
  15. * @param dayOfWeek the day-of-week of the given date.
  16. * @param milliseconds the milliseconds in day in <em>standard</em>
  17. * local time.
  18. *
  19. * @return the offset in milliseconds to add to GMT to get local time.
  20. *
  21. * @see Calendar#ZONE_OFFSET
  22. * @see Calendar#DST_OFFSET
  23. */
  24. public abstract int getOffset(int era, int year, int month, int day,
  25. 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.

  1. mysql> set global time_zone = '+08:00';
  2. Query OK, 0 rows affected (0.00 sec)
  3. mysql> set time_zone = '+08:00';
  4. Query OK, 0 rows affected (0.00 sec)

告知运维设置时区,重启MySql服务,问题解决.

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

  1. 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. Win10 iot 修改日期时间

    发现树莓派的日期是错的 iot 下可以使用 Set-Date 来设置日期和时间 文档:https://docs.microsoft.com/en-us/previous-versions/window ...

  2. nand flash和nor flash的区别

    NOR和NAND是现在市场上两种主要的非易失闪存技术. Intel于1988年首先开发出NOR flash技术,彻底改变了原先由EPROM和EEPROM一统天下的局面. 东芝于1989年开发出NAND ...

  3. 我的Python笔记04

    摘要: 声明:本文整理借鉴金角大王的Python之路,Day4 - Python基础4 (new版)   本节内容 迭代器&生成器 装饰器 Json & pickle 数据序列化 软件 ...

  4. 自动化设计模式Page Object

    https://blog.csdn.net/qq_37546891/article/details/79052054#t1

  5. MySQL系列

    目录: 一.初识数据库 二.库相关操作 三.表相关操作 四.记录相关操作 五.数据备份.pymysql模块 六.视图.触发器.事务.存储过程.函数 七.ORM框架SQLAlchemy 八.索引原理与慢 ...

  6. linux去除\r(window中编辑的文本)

    vim -b file 二进制贷款文件:%s/^M//g         # 注意这里使用Ctrl+V+M输入^M 上面的方法我就不行,但是下面的可以: 如果不行可以使用 :%s/\r//

  7. vue中使用stylus

    1.创建完成一个初始项目后,通过 npm install stylus -D命令,在项目内安装stylus.(注意:命令结尾 -D 即是 --save-dev 的简写形式) 2.需要安装loader, ...

  8. C# 后台通过网络地址访问百度地图取回当前在地图上的经纬度,并将取回的复杂Json格式字符串反序列化(Newtonsoft.Json)

    直接上代码:解释都在代码中 ak 要自己去百度地图申请. 其中申请ak的时候,有个属性render直接填*就行. namespace HampWebControl 是我的空间命名! namespace ...

  9. Jenkins pipeline job 根据参数动态获取触发事件的分支

    此文需要有Jenkins pipeline job 的简单使用经验 场景 我们日常的测试函数, 一般是不能仅仅在本地跑的,还需要一个公共的跑测试的环境,作为合并新的PR的依据. 如果用Jenkins ...

  10. 自制vbs消息轰炸机

    自制消息轰炸机 目标 做一个简单的,可以自己输入参数的vbs程序 准备 电脑qq 脚本设计成了可以指定发给某个好友轰炸的形式,在写好以后容错性比较强,但这意味着你想换人的话,需要重新改代码 vbs脚本 ...