记一次mysql 查询timestap时间不一致的问题

技术 · 2020-11-16

问题描述:
数据库存储的时间字段类型为:timestamp
数据库为:mysql的5.7.31
现象:两个服务查询同一个字段,得出不同的结果。
例:字段值为:2020-11-16 09:46:25,查询结果为:2020-11-16 23:46:25
前提:使用的mysql驱动,一个为2.8.0.21,一个为2.5.1.37

排查思路:

  1. 首先怀疑是Calendar初始化的时候,Timezone不一致,追踪代码发现,Timezone初始化均使用jdk的路径判断,均为 Asia/Shanghai。
    // get the time zone ID from the system properties
    String zoneID = AccessController.doPrivileged(

         new GetPropertyAction("user.timezone"));
    

    // if the time zone ID is not set (yet), perform the
    // platform to Java time zone ID mapping.
    if (zoneID == null || zoneID.isEmpty()) {

     String javaHome = AccessController.doPrivileged(
             new GetPropertyAction("java.home"));
     try {
         zoneID = getSystemTimeZoneID(javaHome);
         if (zoneID == null) {
             zoneID = GMT_ID;
         }
     } catch (NullPointerException e) {
         zoneID = GMT_ID;
     }

    }

    /**

    • Gets the platform defined TimeZone ID.
      **/

    private static native String getSystemTimeZoneID(String javaHome);

  2. 于是怀疑是数据库的设置有问题,使用sql查询show global variables like'%time_zone%';发现数据库配置果然有误,但是不应该两个程序查到不同的结果。

    mysql> show global variables like'%time_zone%';
    Variable_nameValue
    system_time_zoneCST
    time_zoneSYSTEM
  3. rows in set (0.02 sec)
  4. 细细研究了下mysql驱动的源代码,发现了端倪。问题出现在ResultSetImpl.java类中。方法名为:getTimestap();
    2.5:
    public Timestamp getTimestamp(String columnName) throws SQLException {
    return this.getTimestamp(this.findColumn(columnName));
    }
    public Timestamp getTimestamp(int columnIndex) throws SQLException {
    return this.getTimestampInternal(columnIndex, (Calendar)null, this.getDefaultTimeZone(), false);
    }
    2.8:
    public Timestamp getTimestamp(int columnIndex) throws SQLException {
    try {

     this.checkRowPos();
     this.checkColumnBounds(columnIndex);
     return (Timestamp)this.thisRow.getValue(columnIndex - 1, this.defaultTimestampValueFactory);

    } catch (CJException var3) {

     throw SQLExceptionsMapping.translateException(var3, this.getExceptionInterceptor());

    }
    }
    this.defaultTimestampValueFactory = new SqlTimestampValueFactory(pset, (Calendar)null, this.session.getServerSession().getServerTimeZone());

mysql> SELECT @@GLOBAL.time_zone, @@SESSION.time_zone;
@@GLOBAL.time_zone@@SESSION.time_zone
SYSTEMSYSTEM

1 row in set (0.01 sec)
这就很明显了,2.5的默认使用的是服务器的Timezone,而2.8则使用session的Timezone,从而导致查询结果不一致。

结论:
使用数据库使用timestamp字段时,需要注意规定timezone,避免使用系统默认,而造成不同驱动下取值不同的问题。
方式(二选一即可):

  1. (建议)在数据库连接url中增加:serverTimezone=Asia/Shanghai
  2. 修改数据库的session:set global time_zone="+8:00"
Theme Jasmine by Kent Liao