mysql-connector-java版本升級出現的一次問題。涉及到了時間精度的截取和四捨五入。html
首先了解一點,timestamp,datetime若是不指定精度,默認的精度是秒。java
當mysql-connector-java版本<=5.1.22時,db的客戶端會將Datetime,Timestamp秒如下的精度丟棄。版本>5.1.22後,秒如下的值將不會截斷mysql
db的server端會對超出精度位數的數據進行四捨五入!!sql
舉個例子:在db建表時沒指定精度時,插入精確到毫秒級別的日期session
若是使用mysql-connector-java版本<=5.1.22,在客戶端用'2018-04-02 23:59:59.999'插入日期,精度會在客戶端被截取到秒,插入db裏是'2018-04-02 23:59:59'app
若是升級版本,在db的客戶端用'2018-04-02 23:59:59.999'插入日期,精度在客戶端不會被截斷,db的server端會對超出精度位數的數據進行四捨五入,即插入db裏是'2018-04-03 00:00:00 'this
因此說mysql-connector-java版本升級就帶了時間與本來不一致的問題,結合具體業務邏輯上的使用,可能會形成不一樣大小的影響。code
要想證明這個觀點,能夠分兩步:orm
- server端是否會四捨五入
- 客戶端代碼不一樣版本對精度是否有不一樣的處理方式
來實際測一下server會不會四捨五入:server
CREATE TABLE `time_test` ( `id` int(11) NOT NULL AUTO_INCREMENT , `create_time` timestamp NOT NULL DEFAULT '1971-01-01 00:00:00' , `end_time` timestamp NOT NULL DEFAULT '1971-01-01 00:00:00' , PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; insert into time_test (create_time,end_time) values('2018-04-02 23:59:59','2018-04-02 23:59:59.999'); select * from time_test;
看一下記錄:
+----+---------------------+---------------------+
| id | create_time | end_time |
+----+---------------------+---------------------+
|
2
|
2018
-
04
-
02
23
:
59
:
59
|
2018
-
04
-
03
00
:
00
:
00
|
+----+---------------------+---------------------+
能夠看出db的server端果真會進行四捨五入。
再看一下mysql驅動裏是怎麼寫的,是否真的是截斷精度了。
Mysql對於時間精度的處理在com.mysql.jdbc.PreparedStatement#setTimestampInternal這個方法中
翻一下5.1.21的源碼看一下:
private void setTimestampInternal(int parameterIndex, Timestamp x, Calendar targetCalendar, TimeZone tz, boolean rollForward) throws SQLException { synchronized (checkClosed()) { if (x == null) { setNull(parameterIndex, java.sql.Types.TIMESTAMP); } else { checkClosed(); if (!this.useLegacyDatetimeCode) { newSetTimestampInternal(parameterIndex, x, targetCalendar); } else { String timestampString = null; Calendar sessionCalendar = this.connection.getUseJDBCCompliantTimezoneShift() ? this.connection.getUtcCalendar() : getCalendarInstanceForSessionOrNew(); synchronized (sessionCalendar) { x = TimeUtil.changeTimezone(this.connection, sessionCalendar, targetCalendar, x, tz, this.connection .getServerTimezoneTZ(), rollForward); } if (this.connection.getUseSSPSCompatibleTimezoneShift()) { doSSPSCompatibleTimezoneShift(parameterIndex, x, sessionCalendar); } else { synchronized (this) { if (this.tsdf == null) { //這裏,截斷秒如下的精度 this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss''", Locale.US); //$NON-NLS-1$ } timestampString = this.tsdf.format(x); //這裏永遠不會執行添加秒如下的精度 if (false) { // not so long as Bug#50774 is around StringBuffer buf = new StringBuffer(); buf.append(timestampString); int nanos = x.getNanos(); if (nanos != 0) { buf.append('.'); buf.append(formatNanos(nanos)); } buf.append('\''); } setInternal(parameterIndex, timestampString); // SimpleDateFormat is not // thread-safe } } } this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.TIMESTAMP; } } }
再看下5.1.32的實現:
private void setTimestampInternal(int parameterIndex, Timestamp x, Calendar targetCalendar, TimeZone tz, boolean rollForward) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (x == null) { setNull(parameterIndex, java.sql.Types.TIMESTAMP); } else { checkClosed(); if (!this.useLegacyDatetimeCode) { newSetTimestampInternal(parameterIndex, x, targetCalendar); } else { Calendar sessionCalendar = this.connection.getUseJDBCCompliantTimezoneShift() ? this.connection.getUtcCalendar() : getCalendarInstanceForSessionOrNew(); synchronized (sessionCalendar) { x = TimeUtil.changeTimezone(this.connection, sessionCalendar, targetCalendar, x, tz, this.connection .getServerTimezoneTZ(), rollForward); } if (this.connection.getUseSSPSCompatibleTimezoneShift()) { doSSPSCompatibleTimezoneShift(parameterIndex, x, sessionCalendar); } else { synchronized (this) { //一樣截斷精度 if (this.tsdf == null) { this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss", Locale.US); //$NON-NLS-1$ } StringBuffer buf = new StringBuffer(); buf.append(this.tsdf.format(x)); //這裏,若是server支持fractional seconds的話,就加上毫秒的精度 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 } } } this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.TIMESTAMP; } } }
看來果真是個bug...看一下mysql官網的描述:參見bugFix第三條