今天生産環境出現了一個 BUG ,清單中有一列是儲存時間,該字段的時間值比實際值少了8個小時。
問題分析
檢查程式配置與資料庫配置
首先聽說這個 BUG 的時候,認為是項目沒有配置資料庫連接配接的時區造成,或者是資料庫的時區配置不對。
檢查生産環境的項目配置後發現,資料庫的連接配接中配置有
serverTimezone=Asia/Shanghai
這一項,于是去檢查資料庫的時區配置。
show variables like'%time_zone'
--------------------------
system_time_zone CST
time_zone SYSTEM
--------------------------
select now()
--------------------------
2020-08-20 17:42:15.0
--------------------------
最終發現資料庫的時區以及伺服器的時區時間都正常。
檢查入資料的程式
由于查詢的程式與入庫的程式并不是一個服務,而查詢的服務雖然配置了
serverTimezone=Asia/Shanghai
,但是入庫的程式并沒有配置,是以懷疑是否是因為入庫時間時沒配置時區導緻。
為了确認是否是因為這個原因,我寫了一個 demo 程式,分别測試了幾種情況,得到以下測試結果。
-
連接配接 TiDB,未配置時區進行插入。
目前時間:2020-08-20 18:13:22.0 資料庫入庫資料:2020-08-20 18:13:22.0
未配置時區查詢:2020-08-20T10:13:22.000+00:00
配置時區後查詢:2020-08-20T10:13:22.000+00:00
該結果與 BUG 現象一緻。
-
連接配接 TiDB,配置時區進行插入。
目前時間:2020-08-20 18:19:01.0 資料庫入庫資料:2020-08-20 18:19:01.0
未配置時區查詢:2020-08-20T10:19:01.000+00:00
配置時區後查詢:2020-08-20T10:19:01.000+00:00
從上面的結果看,對于 TiDB 而言,無論入資料的程式配置不配置時區,資料庫中的資料均正常。無論配置不配置時區,查詢出來的結果都早8小時。
接下來對 MySQL 進行測試。
-
連接配接 MySQL,未配置時區進行插入。
目前時間:2020-08-20 18:29:18.0 資料庫入庫資料:2020-08-20 05:29:18.0
未配置時區查詢:2020-08-20T10:29:18.000+00:00
配置時區後查詢:2020-08-19T21:29:18.000+00:00
-
連接配接 MySQL,配置時區進行插入。
目前時間:2020-08-20 18:32:52.0 資料庫入庫資料:2020-08-20 18:32:52.0
未配置時區查詢:2020-08-20T23:32:52.000+00:00
配置時區後查詢:2020-08-20T10:32:52.000+00:00
從上面的結果看,對于 MySQL 而言,若不進行時區配置,将導緻入庫時間不對。另一方面,無論時區配置與否,都不能查詢到正确的時間,也就是說資料源的時區配置隻能保證入庫時間沒問題。
發現是 jackson 序列化對象時的問題
由于出現問題的接口都是通過 Controller 直接傳回實體對象,通過 @RestController 注解将對象解析為 json 資料傳回,是以,懷疑是 jackson 序列化時的時區導緻。對 demo 程式添加 jackson 時區配置如下,然後重寫進行查詢。
-
連接配接 TiDB
未配置時區查詢:2020-08-20T18:13:22.000+08:00
配置時區後查詢:2020-08-20T18:13:22.000+08:00
-
連接配接 MySQL
未配置時區查詢:2020-08-21T07:32:52.000+08:00
配置時區後查詢:2020-08-20T18:32:52.000+08:00
到此為止,确認是 jackson 在将對象轉為 json 時的問題。在加上上面的配置後問題得到修複,此外還可以通過代碼的形式自定義序列化方式。
不過這次的問題之前并沒有出現過,也就是說之前是好使的。
斷定是 springboot1.5 更新 2.x 後的問題
此次問題是在服務進行 springboot 版本更新之後才有的,在這之前的應用都是正常的。于是我嘗試更換 springboot 版本,上面的實驗的版本為 springboot-2.3.0,接下來更換為 springboot-1.5.9後進行測試。
-
連接配接 TiDB
未配置時區查詢:1597918402000,解析該時間戳得到2020-08-20 18:13:22
配置時區後查詢:1597918402000,解析該時間戳得到2020-08-20 18:13:22
未配置時區插入,目前時間:2020-08-21 10:12:34.0,資料庫資料:2020-08-21 10:12:34.0
配置時區後插入,目前時間:2020-08-21 10:12:34.0,資料庫資料:2020-08-21 10:12:34.0
-
連接配接 MySQL
未配置時區查詢:1597919572000,解析該時間戳得到2020-08-20 18:32:52
配置時區後查詢:1597919572000,解析該時間戳得到2020-08-20 18:32:52
未配置時區插入,目前時間:2020-08-21 10:09:21.0,資料庫資料:2020-08-21 10:09:21.0
配置時區插入,目前時間:2020-08-21 10:10:52.0,資料庫資料:2020-08-21 10:10:52.0
至此,可以斷定是由于 springboot-2.x 的 jackson 的序列化方式與 springboot-1.5 不同導緻。
另外,對于 TiDB 而言,時區配置無效。對于 MySQL 而言,springboot-2.x 以下版本時區配置無效;springboot-2.x 以上版本中必須配置時區,否則入庫資料異常。
在 springboot-2.x 以下版本中,Date 類型将序列化為時間戳;在 springboo-2.x 以上,Date 類型将序列化為 datetime 日期。在 springboot-2.x 以上版本必須配置 jackson 時區,否則 jackson 序列化後資料異常。
參考資料
[1] Spring Boot更新到2.x,Jackson對Date時間類型序列化的變化差點讓項目暴雷