序
测试接口时,代码中使用LocalDateTime.now()生成时间戳,并insert到db中类型为datetime的字段中,查看db后发现存入的时间与系统生成的时间不同,多了一秒,打断点发现new 出来的Date为2019-06-14 13:02:58.789
,存入的数据为2019-06-14 13:02:59
。经过排查解决后,记录一下此次问题。
排查思路
首先这很明显可以看出是因为时间的精度丢失问题,所以想到了两个排查思路:
- 客户端代码(即mysql-connector-jar)进行了四舍五入,导致丢失了精度。
- server端即数据库进行了四舍五入,导致丢失了精度。
client端
查看了驱动包中的代码,发现如果server端支持的话,是会拼上毫秒的精度的。
#com.mysql.cj.ClientPreparedQueryBindings
public void setTimestamp(int parameterIndex, Timestamp x, TimeZone tz) {
if (x == null) {
setNull(parameterIndex);
} else {
if (!this.sendFractionalSeconds.getValue()) {
x = TimeUtil.truncateFractionalSeconds(x);
}
if (this.tsdf == null) {
//这里截断了精度
this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss", Locale.US);
}
this.tsdf.setTimeZone(tz);
StringBuffer buf = new StringBuffer();
buf.append(this.tsdf.format(x));
//如果server支持fractional seconds的话,就加上毫秒的精度
if (this.session.getServerSession().getCapabilities().serverSupportsFracSecs()) {
buf.append('.');
buf.append(TimeUtil.formatNanos(x.getNanos(), true));
}
buf.append('\'');
setValue(parameterIndex, buf.toString(), MysqlType.TIMESTAMP);
}
}
查资料发现时从5.1.21版本之后,才可以拼接上毫秒的精度。当前版本为8.0.11。
server端
执行如下sql
create table my_dev.test_date (
table_id varchar(10) ,
datetime0 datetime,
)ENGINE=INNODB DEFAULT CHARSET=utf8;
insert into my_dev.test_date values (1,'2019-02-22 13:02:58.789');
发现结果为:
然后就发现了问题的所在。
思考
经过查阅资料发现,如果创建表示字段类型为datetime或者timestamp时,默认精度是到秒的,也就是说如果想保存毫秒的精度,需要创建为如下:
create table my_dev.test_date (
table_id varchar(10) ,
datetime0 datetime,
datetime1 datetime(1),
datetime2 datetime(2),
datetime3 datetime(3)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
insert into my_dev.test_date values (1,'2019-02-22 13:02:58.789','2019-02-22 13:02:58.789','2019-02-22 13:02:58.789','2019-02-22 13:02:58.789');
执行结果为:
以上。