mysql中的时区问题

mysql server和client之间使用string而非long传递时间戳,因此这个string显然是经过时区转换得到的,在这个过程中有三个地方设置了时区:

1.mysql中的time_zone属性

2.jdbc.url中的userServerTimezone属性

3.jvm中的user.timezone属性

在jdbc.url中使用了useLegacyDatetimeCode=false后,mysql设置timestamp的核心代码如下:

    private void newSetTimestampInternal(int parameterIndex, Timestamp x, Calendar targetCalendar) throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            if (this.tsdf == null) {
                this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss", Locale.US);
            }

            if (targetCalendar != null) {
                this.tsdf.setTimeZone(targetCalendar.getTimeZone());
            } else {
                this.tsdf.setTimeZone(this.connection.getServerTimezoneTZ());
            }

            StringBuffer buf = new StringBuffer();
            buf.append(this.tsdf.format(x));
            buf.append('.');
            buf.append(TimeUtil.formatNanos(x.getNanos(), this.serverSupportsFracSecs, true));
            buf.append('\'');

            setInternal(parameterIndex, buf.toString());
        }
    }

mysql会将timestamp字段转换为特定时区下的string,而这个特定时区有两个选择,targetCalendar字段不为null时,选择targetCalendar中的时区信息,该字段可由PreparedStatement类的setTimestamp方法传入;否则选择serverTimezoneTZ字段的值,设置该字段的代码如下:

private void configureTimezone() throws SQLException {
        String configuredTimeZoneOnServer = this.serverVariables.get("timezone");
        
        if (configuredTimeZoneOnServer == null) {
            configuredTimeZoneOnServer = this.serverVariables.get("time_zone");

            if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) {
                configuredTimeZoneOnServer = this.serverVariables.get("system_time_zone");
            }
        }

        //获取jdbc.url中serverTimezone的值
        String canonicalTimezone = getServerTimezone();

        if ((getUseTimezone() || !getUseLegacyDatetimeCode()) && 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 SQLError.createSQLException(iae.getMessage(), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
                }
            }
        }

        if (canonicalTimezone != null && canonicalTimezone.length() > 0) {
            this.serverTimezoneTZ = TimeZone.getTimeZone(canonicalTimezone);
            if (!canonicalTimezone.equalsIgnoreCase("GMT") && this.serverTimezoneTZ.getID().equals("GMT")) {
                throw SQLError.createSQLException("No timezone mapping entry for '" + canonicalTimezone + "'", SQLError.SQL_STATE_ILLEGAL_ARGUMENT,
                        getExceptionInterceptor());
            }

            this.isServerTzUTC = !this.serverTimezoneTZ.useDaylightTime() && this.serverTimezoneTZ.getRawOffset() == 0;
        }
    }

serverTimezoneTZ首先去找jdbc.url中的serverTimezone,若找不到再找mysql server中time_zone属性的值。注意当time_zone为"SYSTEM"时,意为与system_time_zone的值保持一致

从上述分析可知:jvm中的user.timezone属性并不影响mysql server与client之间的传值。

该值其实影响的是controller和前端(外界)之间的传值,在这里不做讨论

为了验证剩下两个属性的作用,做如下测试:

1.client向server发送数据

  private Connection connection;

  @Before
  public void init() throws Exception {
    Class.forName("com.mysql.jdbc.Driver");
    connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?characterEncoding" +
      "=UTF-8&useLegacyDatetimeCode=false&serverTimezone=UTC", "root", "12345678");

  }

  @Test
  public void test() throws SQLException {
    //mysql表中time_stamp字段类型为timestamp
    String insert = "INSERT INTO mysql_time_test(time_stamp) VALUES (?)";
    PreparedStatement preparedStatement = connection.prepareStatement(insert);
    System.out.println(System.currentTimeMillis());
    preparedStatement.setTimestamp(1,new Timestamp(System.currentTimeMillis()));
    preparedStatement.executeUpdate();
  }

  @After
  public void destory() throws SQLException {
    connection.close();
  }

在serverTimezone=UTC,time_zone=’+08:00‘的条件下

控制台(client)输出的当前时间戳为1545630400778,对应UTC时区下的“24/12/2018 05:46:40”

mysql表(server)中time_stamp字段存储的时间戳(使用unix_timestamp函数获得)为1545601601,对应于’+08:00‘时区下的“24/12/2018 05:46:41”

mysql client将当前时间戳按照UTC时区转成string发送到mysql server,而mysql server将收到的string用’+08:00‘解析成时间戳进行保存。(两端解析出来的时间string总是差一秒,原因有待考证)

2.client从server拉取数据

将上面insert代码改为select:

    String get = "select time_stamp from mysql_time_test order by create_time desc limit 1";
    ResultSet resultSet = connection.createStatement().executeQuery(get);
    if(resultSet.next()) {
      System.out.println(resultSet.getTimestamp(1).getTime());
    }

在serverTimezone=UTC,time_zone=’+08:00‘的条件下

控制台(client)输出的当前时间戳为1545667055000,对应UTC时区下的“24/12/2018 15:57:35”

mysql表(server)中time_stamp字段存储的时间戳(使用unix_timestamp函数获得)为1545638255,对应’+08:00‘时区下的“24/12/2018 15:57:35”

mysql server将保存的时间戳按照’+08:00‘时区转成string发送到mysql client,而mysql client将收到的string用UTC时区解析成时间戳。

将以上mysql表字段类型由timestamp改成datetime,发现datetime与timestamp的不同之处在于,从server中拉取的时间string不受time_zone的影响,不论插入数据后如何修改time_zone,返回的时间string始终保持不变。

需要注意的是,虽然server端在处理datetime类型的字段时不受mysql中time_zone的影响,但是若使用java.sql.Timestamp类型接收datetime类型的值,且jdbc.url中未明确定义serverTimezone的值,那么client在收到server发来时间string之后,还是会将其按照time_zone所设置的时区转换为时间戳。

猜你喜欢

转载自blog.csdn.net/qq_37720278/article/details/85228849