mysql时间戳14小时_SpringBoot时间戳与MySql数据库记录相差14小时排错

news/2025/2/26 7:05:11

项目中遇到存储的时间戳与真实时间相差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 {

@Override

public void setNonNullParameter(PreparedStatement ps, int i, Instant parameter, JdbcType jdbcType) throws SQLException {

ps.setTimestamp(i, Timestamp.from(parameter));

}

//...代码shenglve

}

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

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

/**

* Sets the designated parameter to the given java.sql.Timestamp value.

* The driver

* converts this to an SQL TIMESTAMP 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 PreparedStatement */

void setTimestamp(int parameterIndex, java.sql.Timestamp x)

throws SQLException;

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

Sql Driver转换

项目使用com.mysql.cj.jdbc驱动,跟踪其setTimestamp在ClientPreparedStatement类下的具体实现(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数据,具体如下图所示:

2377cd57e01f3146f3a3e6a46b3298ac.png

检查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.

*

* This method returns a historically correct offset if an

* underlying TimeZone 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 standard

* 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;

Driver将Timestamp转换为UTC-6的String;

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:

如果您觉得我的文章对您有帮助,可以扫码领取下红包,谢谢!

ee0bcf6037002524638213156d0b1537.png

内容来源于网络如有侵权请私信删除


http://www.niftyadmin.cn/n/4390357.html

相关文章

sql插入日期_SQL入门基础-了解SQL

通过上述的四个步骤&#xff0c;来进入sql的学习。什么是数据库&#xff1f;什么是SQL?数据库是用来存储数据的工具。那和excel有什么区别呢&#xff1f;excel像一个移动硬盘&#xff0c;而数据库像一个网盘&#xff0c;可供多人在上面操作。我们接下来聊的数据库主要是关系数…

输出英文字母的后继字母 c语言,Java 【打印俄文的英文字母】

俄文的的字符可以用 A 到 Я 。public class main {public static void main(String args[]) {char S А, C Я;System.out.println("俄文字母共有:" ((int) C - (int) S 1) "个");for (char i S; i < C; i) {System.out.print(i " ")…

python requests 发起http POST 请求

python requests 发起http POST 请求&#xff0c;带参数&#xff0c;带请求头&#xff1a; #!/usr/bin/env python # -*- coding: utf-8 -*-import requests import jsonurl http://official-account/app/messages/group body {"type": "text", "co…

aprioir算法_DW-DM实验(李向东)Clementine7(关联规则9章).doc

Clementine 关联规则【流9(9.2).str】9.2.3 Apriori算法的应用示例这里&#xff0c;利用Clementine提供的超市顾客个人信息和他们的一次购买商品数据&#xff0c;讲解Aprioir算法的具体操作。数据文件名为BASKETS.txt&#xff0c;为文本格式文件。数据包括两大部分的内容&#…

java script和java有什么区别_Java和JavaScript有什么区别?

程序员和用户经常在Java和JavaScript之间感到困惑。认为Java和JavaScript相同。如果从表面看&#xff0c;它们可能看起来一样。 由于Java和JavaScript均指相同的OOP语言&#xff0c;并且共享相同的控制结构和运算符。但是&#xff0c;当您单独学习它们时&#xff0c;您将了解它…

k8s mysql 持久化_K8s——MySQL实现数据持久化

1、搭建nfs存储[rootdocker-k8s01 ~]# yum -y install nfs-utils[rootdocker-k8s01 ~]# mkdir /nfsdata/mysql -p[rootdocker-k8s01 ~]# cat /etc/exports/nfsdata *(rw,sync,no_root_squash)[rootdocker-k8s01 ~]# systemctl restart nfs-server[rootdocker-k8s01 ~]# systemc…

java导出csv文件_【干货分享】DM数据库使用UTL_FILE读写文件方法

UTL_FILE包可以用来读写操作系统上的文件&#xff0c;提供了在客户端操作服务器端文件的功能。它提供一套严格的使用标准操作系统文件I/O方式&#xff1a;OPEN、 PUT、 GET和 CLOSE操作&#xff1b;其中&#xff0c;GET方法用于读文件&#xff0c;PUT方法用于写文件。当用户读取…

第一章:Xamarin.Forms如何适应?(5)

安装在编写Xamarin.Forms的应用程序之前&#xff0c;您需要在Mac&#xff0c;PC或两者上安装Xamarin平台&#xff08;如果您正在使用该设置&#xff09;。 请参阅Xamarin网站上的文章&#xff1a;https://developer.xamarin.com/guides/cross-platform/getting_started/install…