2ff34e647e2e3cdfd8dca593e17d9b0a.png

b159c2313c6e53dc003ab4e657d5f1d7.png

背景

业务上需要将MySQL表中的时间精度由原来的“年月日 时分秒”精确到毫秒,但是发现通过MySQL客户端update语句可以更新毫秒成功,但是通过程序却不行,毫秒数始终为0;

原因

经过以上背景描述,第一反应是用程序更新数据库有问题,将精度给搞丢了;我们数据库组件这块用的:Mybatis+C3p0+MySQL Connector,首先将更新语句日志打出来:

2017-12-22 17:27:21,719-[TS] DEBUG main updateByPrimaryKeySelective - ==> Preparing: update schedule SET version = version + 1, apply_time = ? where id = ?

2017-12-22 17:27:21,732-[TS] DEBUG main updateByPrimaryKeySelective - ==> Parameters: 2017-12-22 17:27:19.579(Timestamp), 1049(Long)

2017-12-22 17:27:30,033-[TS] DEBUG main updateByPrimaryKeySelective - <== Updates: 1

发现在Mybatis这一层,精度是传递下去了;

所以去Debug下,看看到底在哪一行把这精度给搞丢了,当我们一步步跟到MySQL JDBC Driver这一个方法时,发现:

private synchronized void setTimestampInternal(int parameterIndex, Timestamp x, Calendar targetCalendar, TimeZone tz, boolean rollForward) throws SQLException {

if(x == null) {

this.setNull(parameterIndex, 93);

} else {

this.checkClosed();

if(!this.useLegacyDatetimeCode) {

this.newSetTimestampInternal(parameterIndex, x, targetCalendar);

} else {

String timestampString = null;

Calendar sessionCalendar = this.connection.getUseJDBCCompliantTimezoneShift()?this.connection.getUtcCalendar():this.getCalendarInstanceForSessionOrNew();

synchronized(sessionCalendar) {

x = TimeUtil.changeTimezone(this.connection, sessionCalendar, targetCalendar, x, tz, this.connection.getServerTimezoneTZ(), rollForward);

}

if(this.connection.getUseSSPSCompatibleTimezoneShift()) {

this.doSSPSCompatibleTimezoneShift(parameterIndex, x, sessionCalendar);

} else {

synchronized(this) {

if(this.tsdf == null) {

this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss''", Locale.US);

}

timestampString = this.tsdf.format(x);

this.setInternal(parameterIndex, timestampString);

}

}

}

this.parameterTypes[parameterIndex - 1 + this.getParameterIndexOffset()] = 93;

}

}

此处有一个格式化代码,将时间对象都以’yyyy-MM-dd HH:mm:ss’形式给处理了,自然将我们的毫秒数给抛弃了,此时查看我们的JDBC Driver版本,是5.1.19,难道数据库支持毫秒而驱动不支持?不太可能,所以怀疑是我们的驱动版本过低,因为支持毫秒的特性是MySQL 5.6.4开始有的,所以将JDBC Driver升级一下,这里我用的5.1.39,再跑一遍程序,毫秒数已经可以插入到数据库,再次Debug下,可以看到在相同的方法中,MySQL已经加上了毫秒数的支持:

if (this.connection.getUseSSPSCompatibleTimezoneShift()) {

doSSPSCompatibleTimezoneShift(parameterIndex, x);

} else {

synchronized (this) {

if (this.tsdf == null) {

this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss", Locale.US);

}

StringBuffer buf = new StringBuffer();

buf.append(this.tsdf.format(x));

if (this.serverSupportsFracSecs) {

int nanos = x.getNanos();

if (nanos != 0) {

buf.append('.');

buf.append(TimeUtil.formatNanos(nanos, this.serverSupportsFracSecs, true));

}

}

buf.append(''');

setInternal(parameterIndex, buf.toString()); // SimpleDateFormat is not

// thread-safe

}

}

其实就是判断一下数据版本,如果支持毫秒精度特性,就把毫秒数加上;

解决

上边排查到原因,解决方法就好了,直接升级更新的驱动版本;

思考

其实这种问题在日常开发中很常见,保持一颗刨根问底的心很重要,太阳底下无新事、源码之下无黑盒,一步步追踪下去,会找到问题所在的。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐