0
点赞
收藏
分享

微信扫一扫

MySql数据库记录相差14小时排错,使用Java访问Mysql数据库时出现时区异常的解决方案

浮游图灵 2023-07-17 阅读 38


 

最近遇到1个大坑,A系统迁移到B系统,2边系统 同1个字段 createTime 看到的不一致。

表象:

A系统: 2019-6-10 17:34

B系统: 2019-6-11 ....

 

再次尝试:

通过A系统创建,数据库的时间 和当前时间一致。没毛病。

通过B系统创建,数据库的时间 也和当前时间一致,没毛病。

 

但是,查询的时候有问题了。

A系统创建的,A系统查询没问题。

B系统创建的,B系统查询也没问题。

A查询B创建的,B查询A创建的 都有问题。

 

之前一个同事,遇到过类似问题,只有个大概结论:时区问题。

date 时区
show variables like "%time_zone%";
数据库时间,看的是“2019-05-01 11:11:11”,数据库查询出来可能是“2019-05-02”。
时区问题导致的。
time_zone  SYSTEM
system_time_zone CST

 

最初,我也以为是 mysql安装时的时区问题。

但是,后来发现,mysql都是这个。

阿里云的mysql,数据库时区也是 CST。

这说明,公司安装的mysql  CST不是根本原因。

 

项目用到了MybatisPlus,也曾经考虑过MybatisPlus的问题,但是总觉得不会,没有相关答案。

Mybatis也不太可能。 JDBC和数据源的时区 或 mysql  的配置 应该是重点。

然后网上搜了下,看到几篇文字。

 

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

查询CST发现其指代比较混乱,有四种含义(参考网址:):

  • 美国中部时间 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吻合.

原文链接:

 

有了个大概认识。

 

还看到1篇文章:

JDBC连接MySQL数据库出现的时区不一致的解决办法

首先把我的jar包放出来给你们看一下,提前说一下,版本高的jar包约束太多,这就是你连接出现问题的原因,而配置两个时间要一致太繁琐了。所以,我给出如下解决办法。

MySql数据库记录相差14小时排错,使用Java访问Mysql数据库时出现时区异常的解决方案_java

jar包我一开始下载的是官网最新的8.0.11版本的,结果,如下,说服务器程序和JDBC的时区时间不一致,连接失败。

 

看到这哥们的问题,使我猛然一惊。

这真的很可能是问题原因了。

 

在项目迁移的时候,数据库 数据源 驱动 从5.x升级到 8.x。

为什么会手动升级?

用SpringBoot的时候,

 <!-- mysql -->
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
           <!--  <version>8.0.13</version> 千万不能用这个版本,这是一个经典的时区CST问题-->
        </dependency>

The managed version is 5.1.46 The artifact is managed in org.springframework.boot:spring-boot-dependencies:1.5.13.RELEASE

 

在启动后,有个提示,让我升级mysql 驱动版本。(提醒:上下文的若干描述不一定正确

com.mysql.cj.jdbc.Driver

MySQL 8.0 以上版本:

驱动包版本 mysql-connector-java-8.0.12.jar

数据库 URL 需要声明是否使用 SSL 安全验证及指定服务器上的时区:

static final String DB_URL = jdbc:mysql://localhost:3306/runoob?useSSL=false&serverTimezone=UTC;
conn = DriverManager.getConnection(DB_URL,USER,PASS);

原本的驱动器是:

Class.forName("com.mysql.jdbc.Driver");

在 IDEA 里面提示是: Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary

意思是说原本的驱动器不赞成 或者 是废弃了,自动换成了新的驱动器 com.mysql.cj.jdbc.Driver

Class.forName("com.mysql.cj.jdbc.Driver");

原文链接:

 

 

结论:(经过实践之后)

SpringBoot默认 5.x驱动

 <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

 

手动升级后,8.x

       <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
           <!--  <version>8.0.13</version> 千万不能用这个版本,这是一个经典的时区CST问题-->
        </dependency>

 

单独使用任何1个驱动,都没有问题。

但是,如果2个系统,分别用5.x和8.x,查询出来的时间 就不一致了。

根本原因:

8.X版本的mysql数据库连接驱动引起的时区问题

原文链接:

8.0版本以上的mysql驱动需要在url后面加上时区。

?serverTimezone=GMT或者UTC。问题解决。

GMT%2B8:中国时区

UTC:国际标准时区

 

注:新版本的driver不再使用com.mysql.jdbc.Driver,需要使用新的com.mysql.cj.jdbc.Driver
-------------以下为copy的文章-------- 
1、8.X版本的mysql数据库连接驱动引起的时区问题

原文链接:

核心内容:

今天在创建spring boot项目,引入mybatis之后,运行项目报了如下错误:

java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) ~[mysql-connector-java-8.0.15.jar:8.0.15]
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.15.jar:8.0.15]
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:89) ~[mysql-connector-java-8.0.15.jar:8.0.15]
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:63) ~[mysql-connector-java-8.0.15.jar:8.0.15]
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:73) ~[mysql-connector-java-8.0.15.jar:8.0.15]
    at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:76) ~[mysql-connector-java-8.0.15.jar:8.0.15]

错误意思就是:不能连接数据库,没办法识别服务器时区,因为返回的是多个,必须配置服务器或者jdbc driver。

这是我使用的mysql驱动。

查询资料之后发现是因为引入最新的mysql驱动引起的,8.0版本以上的mysql驱动需要在url后面加上时区。

?serverTimezone=GMT或者UTC。问题解决。

GMT%2B8:中国时区

UTC:国际标准时区

 

注:新版本的driver不再使用com.mysql.jdbc.Driver,需要使用新的com.mysql.cj.jdbc.Driver
--------------------- 
作者:nedjie 
来源:CSDN 
原文: 
版权声明:本文为博主原创文章,转载请附上博文链接!

 

2、【使用新版mysql驱动的改变】---记忆犹新

原文链接:

关于 版本的问题:

之前安装数据库的时候  安装的mysql 8.0  脚本文件来自5.7 的数据库

maven版本3.5.3

jdk1.8

tomcat 9

 

跑maven项目的时候  遇到各种问题 :1、数据库连接失败、数据库拒接链接 、数据库连接超时……

 

最后安装 mysql5.7  tomcat8  之后 解决问题 

 

究其原因  是因为  数据库版本过高  jdbc驱动的改变

 

 jdbc.driver_class=com.mysql.cj.jdbc.Driver&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC  

 

可以参考这篇文章 使用新版mysql驱动的改变

还有这篇文章  maven管理工程,引入MyBatis、MySQL遇到的问题

 

MySQL 8.0 以上版本:

驱动包版本 mysql-connector-java-8.0.12.jar

数据库 URL 需要声明是否使用 SSL 安全验证及指定服务器上的时区:

static final String DB_URL = jdbc:mysql://localhost:3306/runoob?useSSL=false&serverTimezone=UTC;
conn = DriverManager.getConnection(DB_URL,USER,PASS);

原本的驱动器是:

Class.forName("com.mysql.jdbc.Driver");

在 IDEA 里面提示是: Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary

意思是说原本的驱动器不赞成 或者 是废弃了,自动换成了新的驱动器 com.mysql.cj.jdbc.Driver

Class.forName("com.mysql.cj.jdbc.Driver");

 

3、JDBC连接MySQL数据库出现的时区不一致的解决办法

原文链接:

首先把我的jar包放出来给你们看一下,提前说一下,版本高的jar包约束太多,这就是你连接出现问题的原因,而配置两个时间要一致太繁琐了。所以,我给出如下解决办法。

jar包我一开始下载的是官网最新的8.0.11版本的,结果,如下,说服务器程序和JDBC的时区时间不一致,连接失败。

后来我在网上找了一大堆资料,最后自己琢磨勒一下,发现是因为我下载的jar包的版本太高了,会出现这种时区不一致的问题,因为新版本约束多,最后,我就换掉了jar包,换成5.1.36版本的,结果如下,成功连接:

--------------------- 
作者:lzhpo 
来源:CSDN 
原文: 
版权声明:本文为博主原创文章,转载请附上博文链接!

 

4、使用Java访问Mysql数据库时出现时区异常的解决方案

原文链接:

问题来源:将Mysql5.6版本升级到8.0.12版本后,Java在访问Mysql数据库时出现如下异常:

java.sql.SQLException: The server time zone value '???ú±ê×??±??' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129)
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:89)
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:63)
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:73)
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:76)
	at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:832)
	at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:456)
	at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:240)
	at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:207)
	at com.mchange.v2.c3p0.DriverManagerDataSource.getConnection(DriverManagerDataSource.java:135)
	at com.mchange.v2.c3p0.WrapperConnectionPoolDataSource.getPooledConnection(WrapperConnectionPoolDataSource.java:182)
	at com.mchange.v2.c3p0.WrapperConnectionPoolDataSource.getPooledConnection(WrapperConnectionPoolDataSource.java:171)
	at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool$1PooledConnectionResourcePoolManager.acquireResource(C3P0PooledConnectionPool.java:137)
	at com.mchange.v2.resourcepool.BasicResourcePool.doAcquire(BasicResourcePool.java:1014)
	at com.mchange.v2.resourcepool.BasicResourcePool.access$800(BasicResourcePool.java:32)
	at com.mchange.v2.resourcepool.BasicResourcePool$AcquireTask.run(BasicResourcePool.java:1810)
	at com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunner.java:547)

这是因为Mysql服务端的使用的时区与客户端使用的时区不一致导致的,解决方案如下:

在连接数据库的参数中追加上serverTimezone=GMT%2B8参数,如下图所示:

MySql数据库记录相差14小时排错,使用Java访问Mysql数据库时出现时区异常的解决方案_mysql_02

表示使用GMT+8时区,该时区为北京时区。

Mysql5.6不存在该问题。

注意升级到Mysql8.0.12时MySql数据库驱动包也需要升级,否则会出现异常,如下图所示:

<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.12</version>
            <scope>runtime</scope>
        </dependency>

 

5、SpringBoot时间戳与MySql数据库记录相差14小时排错

原文链接:

项目中遇到存储的时间戳与真实时间相差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发现其指代比较混乱,有四种含义(参考网址:):

  • 美国中部时间 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数据,具体如下图所示:

MySql数据库记录相差14小时排错,使用Java访问Mysql数据库时出现时区异常的解决方案_mysql_03

检查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源头找到.

解决方案

参照.

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.

 

 

举报

相关推荐

0 条评论