JDBC6.x版本与MySQL相差13个小时,CST时区导致

star2017 7月前 ⋅ 667 阅读

问题

新项目中的 JDBC 驱动使用了较新的 6.0.x 版本,写入数据库的时间与客户端当前时间相差 13 个小时。

以前碰到过相差 8 个小时的,是因为未配时区默认为UTC 标准时间,即UTC 0 时区的时间,即伦敦时间,与北京正好相差 8 个小时,这好解决。而相差 13 小时就让人疑惑了。

排查

  1. 排查确定客户端系统与服务器的系统时间一致,都为中国时区相同的时间。
  2. 排序查程序 new Date() 时间与当前系统时间一致。
  3. 排查数据库获取到的时间与服务器的系统时间一致。

以前问题确定基本可以排除服务器系统时间不一致问题。记得老项目使用使用的JDBC驱动是 5.1.x 版本,没出现这样的问题,就将6.0.x 降到 5.1.x ,问题解决。也就是说问题出在新的 6.0.x 版本 JDBC 驱动上。

解决

  1. 降低 JDBC 驱动版本为 5.1.37/5.1.38/5.1.39。 5.x 版本取的时区是系统时区,所以没有问题。
  2. 修改 Mysql 服务器上的默认的时区的配置,指定为 UTC 时区 东八区,中国即为 GMT+0800。
    执行SQL修改,但数据库服务重启后会恢复默认。

     mysql> set global time_zone = '+08:00';
    
     mysql> set time_zone = '+08:00';
    

    修改/etc/my.cnf中,在 [mysqld]节点下增加default-time-zone='+08:00'配置。数据库服务重启后仍有效。

     [mysqld]
     default-time_zone = '+8:00'
    
  3. 在 JDBC 的连接串中添加配置:&serverTimezone=Asia/Shanghai(北京时区)。

     // UTC 时区:serverTimezone=UTC,写入到数据库的时间会比当前北京时间早8个小时
     // 香港时区:serverTimezone=Hongkong
    
     // 指定时间为东八区:serverTimezone=GMT%2B8 
     spring.datasource.url=jdbc:log4jdbc:mysql://localhost:3306/dev?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&autoReconnect=true&serverTimezone=GMT%2B8
    
     // 指定使用 亚洲/上海 时间:serverTimezone=Asia/Shanghai
     spring.datasource.url=jdbc:log4jdbc:mysql://localhost:3306/dev?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&autoReconnect=true&serverTimezone=Asia/Shanghai
    

    关闭 SSL 连接警告:

     useSSL=false
    

    原因

    根本原因是出在新版本 JDBC 修改了与 MySQL 协商时区取值导致的。

    MySQL 时区

    mysql> SHOW VARIABLES LIKE '%time_zone%';
    +------------------+--------+
    | Variable_name    | Value  |
    +------------------+--------+
    | system_time_zone | CST    |
    | time_zone        | SYSTEM |
    +------------------+--------+
    2 rows in set (0.04 sec)
    

    MySQL 采用的是系统时区,系统时区是 CST,中文操作系统默认的 CST 时区为中国标准时间,即为 UTC 的东八区时间(CST +0800) 。

CST 时区

CST 时区是一个很混乱的时区,在与 MySQL 协商会话时区时,Java 会误以为是 CST -0500,而非 CST +0800。

CST 时区可以为如下4个不同的时区的缩写:

美国中部时间:Central Standard Time (USA) UT-6:00
澳大利亚中部时间:Central Standard Time (Australia) UT+9:30
中国标准时间:China Standard Time UT+8:00
古巴标准时间:Cuba Standard Time UT-4:00

假如今天是4月28日。美国从3月11日至11月7日实行夏令时,美国中部时间改为 UTC-05:00,与 UTC+08:00 相差 13 小时。

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 值作为协调时区。
问题核心:当 configuredTimeZoneOnServer 获取到的是CST, Java 会误认为是 CST -0500,因此 TimeZone.getTimeZone(canonicalTimezone) 会给出的就不是我们想要的北京时区(GMT+8)。

相关参考

更多内容请访问:IT源点

相关文章推荐

全部评论: 0

    我有话说: