原文地址php
當JVM時區和數據庫時區不一致的時候,會發生什麼?這個問題也許你歷來沒有注意過,可是當把Java程序容器化的時候,問題就浮現出來了,由於目前幾乎全部的Docker Image的時區都是UTC。本文探究了MySQL及其JDBC驅動對於時區的處理方式,並嘗試給出最佳實踐。html
DATE
和TIME
類型不支持時區轉換。對於TIMESTAMP
類型,MySQL會正確的根據connection時區(對於JDBC來講就是JVM時區)/服務端時區作轉換。java
DATE_FORMAT()
),由於返回的結果是服務端的時區,而不是connection的時區(對於JDBC來講就是JVM時區)。CURRENT_TIMESTAMP()
, CURRENT_TIME()
, CURRENT_DATE()
能夠安全的使用,返回的結果會轉換成connection時區(對於JDBC來講就是JVM時區)。CURRENT_TIME()
有一個不知道是否是BUG的Bug #92453。MySQL - The DATE, DATETIME, and TIMESTAMP Types:mysql
MySQL convertsTIMESTAMP
values from the current time zone to UTC for storage, and back from UTC to the
current time zone for retrieval. (This does not occur for other types such asDATETIME
.)
By default, the current time zone for each connection is the server's time. The time zone can be set on
a per-connection basis.
As long as the time zone setting remains constant, you get back the same value you store.
If you store aTIMESTAMP
value, and then change the time zone and retrieve the value, the retrieved value
is different from the value you stored. This occurs because the same time zone was not used for conversion
in both directions.
簡而言之就是兩句話:git
TIMESTAMP
類型所返回的值,會根據connection的時區(對於JDBC來講就是JVM時區)作轉換TIMESTAMP
類型會作時區轉換爲了驗證這個結論,我寫了一段程序來實驗,這個程序作了三件事情:github
Asia/Shanghai
時區構造一個日期java.util.Date
:2018-09-14 10:00:00
,而後插入到數據庫裏(表:test,列:timestamp類型)Asia/Shanghai
時區把這個值再查出來,看看結果。Asia/Shanghai
時區,得到這個字段的格式化字符串(使用DATE_FORMAT()
函數)。Europe/Paris
時區重複第2-3步的動做在運行程序以前,咱們先用Docker啓動一個MySQL,它所在的MySQL的時區是UTC(除非特別設定,全部Docker Image時區都默認爲UTC):sql
docker run --name mysql-timezone-test \ -e MYSQL_RANDOM_ROOT_PASSWORD=yes \ -e MYSQL_DATABASE=testdb \ -e MYSQL_USER=tz \ -e MYSQL_PASSWORD=tz \ -p 3306:3306 \ -d mysql:8
下面是結果:docker
Insert data, Time Zone : 中國標準時間 java.util.Date : 2018-09-14 10:00:00 Insert into timestamp column : 2018-09-14 10:00:00 -------------------- Retrieve data, Time Zone : 中國標準時間 Retrieve java.util.Date : 2018-09-14 10:00:00 Retrieve formatted string : 2018-09-14 02:00:00 -------------------- Retrieve data, Time Zone : 中歐時間 Retrieve java.util.Date : 2018-09-14 04:00:00 Retrieve formatted string : 2018-09-14 02:00:00
能夠看到Retrieve java.util.Date
返回的結果根據JVM時區作了轉換的。而Retrieve formatted string
返回的結果則是UTC時間。數據庫
MySQL與"當前日期時間"相關的函數有這麼些,MySQL - Date and Time Functions:安全
TheCURRENT_TIMESTAMP()
,CURRENT_TIME()
,CURRENT_DATE()
, andFROM_UNIXTIME()
functions return values
in the connection's current time zone, which is available as the value of the time_zone system variable.
並且根據文檔所講,它們返回的結果匹配當前鏈接所設定的時區。
爲了驗證這個結論,一樣寫了一段程序,分別使用Asia/Shanghai
和Europe/Paris
來調用CURRENT_TIMESTAMP()
、CURRENT_TIME()
、CURRENT_DATE()
。
下面是運行結果:
JVM Time Zone : 中國標準時間 Test CURRENT_DATE() : 2018-09-18 Test CURRENT_TIME() : 10:55:41 Test CURRENT_TIMESTAMP() : 2018-09-18 10:55:41.0 -------------------- JVM Time Zone : 中歐時間 Test CURRENT_DATE() : 2018-09-18 Test CURRENT_TIME() : 03:56:02 Test CURRENT_TIMESTAMP() : 2018-09-18 04:56:02.0
能夠看到結果是基本符合文檔裏的說明的,可是要注意,在Europe/Paris
時區,CURRENT_TIME()
和CURRENT_TIMESTAMP()
的時間部分相差一小時。
看上去CURRENT_TIMESTAMP()
返回的是UTC DST offset結果,而CURRENT_TIME()
返回的是UTC offset結果,關於這個我登記了Bug #92453。
關於Europe/Paris
的DST信息能夠在這裏找到Wiki - List of tz database time zones。
-- 查詢系統時區和session時區 SELECT @@global.time_zone, @@session.time_zone; -- 設置session時區 SET time_zone = 'Asia/Shanghai';
詳見:MySQL Server Time Zone Support
你能夠在docker啓動的時候設定MySQL容器的時區,好比這樣-e TZ=Asia/Shanghai
。
這個方法有問題,會出現時間錯亂,workaround是root用戶鏈接到MySQL,而後執行SET GLOBAL time_zone = 'Asia/Shanghai';
。
這樣客戶端鏈接MySQL時,查詢的時間的時區都是Asia/Shanghai
了。