1. 程式人生 > >Mysql 時間型別精度擷取的bug

Mysql 時間型別精度擷取的bug

mysql-connector-java版本升級出現的一次問題。涉及到了時間精度的擷取和四捨五入。

首先了解一點,timestamp,datetime如果不指定精度,預設的精度是秒。

當mysql-connector-java版本<=5.1.22時,db的客戶端會將Datetime,Timestamp秒以下的精度丟棄。版本>5.1.22後,秒以下的值將不會截斷

db的server端會對超出精度位數的資料進行四捨五入!!

舉個例子:在db建表時沒指定精度時,插入精確到毫秒級別的日期

如果使用mysql-connector-java版本<=5.1.22,在客戶端用'2018-04-02 23:59:59.999'插入日期,精度會在客戶端被擷取到秒,插入db裡是'2018-04-02 23:59:59'

如果升級版本,在db的客戶端用'2018-04-02 23:59:59.999'插入日期,精度在客戶端不會被截斷,db的server端會對超出精度位數的資料進行四捨五入,即插入db裡是'2018-04-03 00:00:00 '

所以說mysql-connector-java版本升級就帶了時間與原本不一致的問題,結合具體業務邏輯上的使用,可能會造成不同大小的影響。

要想證實這個觀點,可以分兩步:

  • 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第三條

相關推薦

Mysql 時間型別精度擷取bug

mysql-connector-java版本升級出現的一次問題。涉及到了時間精度的擷取和四捨五入。 首先了解一點,timestamp,datetime如果不指定精度,預設的精度是秒。 當mysql-connector-java版本<=5.1.22時,db的客戶端會將Datetime,Timestam

MySQL時間型別和long型別的轉換,日期格式化

MySQL中可以將時間型別儲存為long型別資料: UNIX時間戳轉換為日期用函式: FROM_UNIXTIME() select FROM_UNIXTIME(1536148611); 日期轉換為UNIX時間戳用函式: UNIX_TIMESTAMP() Select U

mysql 時間型別精確到毫秒、微秒及其處理

一、MySQL 獲得毫秒、微秒及對毫秒、微秒的處理 MySQL 較新的版本中(MySQL 6.0.5),也還沒有產生微秒的函式,now() 只能精確到秒。 MySQL 中也沒有儲存帶有毫秒、微秒的日期時間型別。 但,奇怪的是 MySQL 已經有抽取(extract)微秒的函式。例如: select mi

MySQL 時間型別datetime欄位 精確到毫秒問題

今天遇到一個小問題,計算一筆操作的執行時間,操作前先用LocalDateTime(本人是Java開發者)記錄開始時間且寫入資料庫,操作完成後計算操作完成時間和開始時間的時間差,結果算出來出現了負數結果。 在日誌中打印出來,發現結果是從資料庫取出的開始時間後三位毫秒都是0,而

[轉]MySQLMySQL日期資料型別MySQL時間型別使用總結

[轉自]http://www.blogjava.net/titanaly/archive/2009/08/17/291454.html MySQL:MySQL日期資料型別、MySQL時間型別使用總結 MySQL 日期型別:日期格式、所佔儲存空間、日期範圍 比較。

mysql 時間型別精確到毫秒、微秒及其處理

select * from test where DATE_FORMAT(timess,'%Y-%m-%d %T:%f') between DATE_FORMAT('2010-12-01 06:03:16.233','%Y-%m-%d %T:%f') and DATE_FORMAT('2010-12-01

mysql時間型別timestamp知識點

mysql日期時間型別 日期型別 位元組 最小值 最大值 DATE 4 1000-01-01 9999-12-31 DATETIME 8 1000-01-01 00:00:00 TimeS

一起MySQL時間精度引發的血案

寫在前面 最近工作中遇到兩例mysql時間戳相關的問題,一個是mysql-connector-java和msyql的精度不一致導致資料查不到;另一例是應用伺服器時區錯誤導致資料查詢不到。通過這篇文章,希望能夠解答關於mysql中時間戳的幾個問題: mysql中的DATETIME精度為什麼只支援到秒? mys

MySQL - 日期時間型別與格式化

【1】MySQL中的日期時間型別 MySQL中常用的幾種時間型別有:date、datetime、time、year、timestamp; ① 詳細解釋 Datetime : 時間日期型,格式是YYYY-mm-dd HH:ii:ss,表示的範圍是從1000到9999。但是有零值,0000-

Mysql時間戳格式和時間型別格式的裝換

首先我有這樣一個時間 將此時間轉換為時間戳格式: SELECT UNIX_TIMESTAMP(pst.ep_order_time) AS ep_order_time FROM prj_status_time pst WHERE pst.project_id='15414878732

Java與MySQL資料對接時的時間型別的資料

使用java.util.Date與MySQL資料庫的時間欄位對接時,獲取到的時間是格林格式的,還需要進行一下轉換才能使用為本地時間,偶然發現一篇部落格,這引用一下java.sql.date 以前從MySQL中查詢出來的時間日期型別,都放在java.util.Date型別裡面了。這樣帶

MySQL資料型別 -- 日期時間

在MySQL關係型資料庫中,MySQL支援的資料型別非常豐富。它主要分為3大類,即:數值型,日期時間性,字元型。而實際上這三類資料型別可以進一步的細分擴充套件,可以根據業務需要選擇最適合的一種。本文主要介紹日期時間型別,並演示其用法。 一、日期時間型 MySQL

mysql的資料型別之日期時間型別

常用的日期時間型別: DATE ;TIME; DATETIME ;TIMESTAMP 。 (1)根據實際需要選擇滿足應用的最小儲存的日期型別 (2)記錄年份比較久遠,最好要使用DATETIME。因為TIMESTAMP表示的日期範圍要短很多。 (3)如果讓不同的時區保持

mysql的五種日期和時間型別【轉載】

[mysql的五種日期和時間型別] mysql(5.5)所支援的日期時間型別有:DATETIME、 TIMESTAMP、DATE、TIME、YEAR。 幾種型別比較如下: 日期時間型別 佔用空間 日期格式 最小值 最大值 零值表示

【小家SQL】MySql資料型別---日期時間型別的使用(含datetime和timestamp的區別)

每篇一句 練武不練功,到老一場空。 程式設計師應該注重內功的修煉,那才是核心競爭力 說在前面 在這一路學習過來,每次不管看書還是網上看的資料,對於MySQL資料型別中的時間日期型別總是一掃而過,不曾停下來認認真真的研究學習。最近看了一本關於MySql的書

MySQL時間型別DATETIME、TIMESTAMP、DATE、TIME、YEAR

1.幾個的區別 詳細可以參考:https://www.cnblogs.com/Jie-Jack/p/3793304.html 2.針對時間型別的一些操作 nodejs支援多種格式轉換為時間戳: var str1 = "2017-01-19 13:00:00"; va

MySQL 幾種日期時間型別之間的區別

mysql(5.5)所支援的日期時間型別有:DATETIME、 TIMESTAMP、DATE、TIME、YEAR。 幾種型別比較如下: 日期時間型別 佔用空間 日期格式 最小值 最大值 零值表示  DATETIME  8 bytes  YYYY-MM-DD HH:MM:SS  1000-01-01

MySQL幾種資料型別精度和標度的情況

1、整型 int預設是int(11),建立欄位int(5),當儲存的資料長度大於5時,可以正常儲存,儲存的資料完整顯示; 2、浮點型 float(M,D)和double(M,D) 如果不寫精度和標度,則會按照實際精度值顯示,如果有精度和標度,則會自動將四捨五入的結果插入,不

一個 double 型別精度問題導致的 Bug

    在寫 Weex 的時候,遇到一個 Bug —— 顯示的訂單 ID 不對,和 API 測試平臺獲取的資料不一致。首先懷疑的是取錯欄位了,認真檢查了一下,不是這個原因。然後懷疑的是 Native 網路請求模組 JsonString 轉 Model 的時候出錯

Java、MySql時間型別與字串型別的相互轉換

          很多時候,我們在做專案的時候經常會遇到前臺傳回來的時間引數。有的時候,前臺會做處理,將文字框的字串轉化為時間型別的;有的時候,前臺就直接傳字串型別的,所以,作為後臺開發人員要知道,資料庫中時間型別與字串的相互轉換。 1.MySql中時間型別與字串型別相互