久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

mysql的timestamp存在時區問題怎么解決

138次閱讀
沒有評論

共計 8674 個字符,預計需要花費 22 分鐘才能閱讀完成。

本篇內容介紹了“mysql 的 timestamp 存在時區問題怎么解決”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓丸趣 TV 小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

眾所周知,mysql 中有兩個時間類型,timestamp 與 datetime,但當在網上搜索 timestamp 與 datetime 區別時,會發現網上有不少與時區有關的完全相反的結論,主要兩種:

timestamp 沒有時區問題,而 datetime 有時區問題,原因是 timestamp 是以 UTC 格式存儲的,而 datetime 存儲類似于時間字符串的形式

timestamp 也有時區問題

兩種觀點讓人迷惑,那 timestamp 到底會不會有時區問題呢?

基本概念

時區:

由于地域的限制,人們發明了時區的概念,用來適應人們在時間感受上的差異,比如中國的時區是東 8 區,表示為 +8:00,或 GMT+8,而日本的時區是東 9 區,表示為 +9:00,或 GMT+9,當中國是早上 8 點時,日本是早上 9 點,即東 8 區的 8 點與東 9 區的 9 點,這兩個時間是相等的。

另外時間還有如下兩個概念:

絕對時間:

如 unix 時間綴,是 1970-01-01 00:00:00 開始到現在的秒數,如:1582416000,這種表示是絕對時間,不受時區影響,也叫紀元時 epoch。

本地時間:

相對于某一時區的時間,是本地時間,比如東 8 區的 2020-02-23 08:00:00,是中國人的本地時間,而在此時,日本人的本地時間是 2020-02-23 09:00:00,所以本地時間都是與某一時區相關的,脫離時區看本地時間,是沒有意義的,因為你并不知道這具體是指的什么時間點。

比如在 Java 中,Date 對象是絕對時間,通過 SimpleDateFormat 格式化出來的 yyyy-MM-dd HH:mm:ss 形式的時間字符串,是本地時間,如果 SimpleDateFormat 沒有調用 setTimeZone() 顯示指定時區,那么默認用的是 jvm 運行在的操作系統上的時區,我們開發機上的時區基本都是 GMT+8。

timestamp 與 datetime 區別

如下,我創建了一張表,里面 time_stamp 是 timestamp 類型,date_time 是 datetime 類型,create_timestamp、create_datetime 是 timestamp 與 datetime 類型,但是它們可以由數據庫自動生成。

CREATE TABLE `time_test` (
 `id` bigint unsigned,
 `time_stamp` timestamp,
 `date_time` datetime,
 `create_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT  創建時間 ,
 `create_datetime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT  創建時間 ,
 PRIMARY KEY (`id`)
)

1、首先將數據庫時區設置為 +8:00,即中國的東 8 區

2、然后如下手動插入一個固定時間的數據,以及用 now() 函數插入當前時間

3、當插入完數據后,然后我們修改當前會話的時區為 +9:00,即日本的東 9 區,然后再次查看數據

4、如上,定義為 timestamp 類型的列 time_stamp、create_timestamp 不管是手動插入的,還是 now() 函數插入的,東 9 區都比東 8 區的時間大 1 個小時,這是正確的,說明 timestamp 類型是時區相關的,然而定義為 datetime 類型的 date_time、create_datetime 字段,時間都沒有變化,這說明 datetime 類型是時區無關的。

結論:

timestamp 在存儲上是包含時區的,而 datetime 是不包含時區,說明網上的第一種說法是對的。

再看個例子

我們將東 8 區的的 2020-02-23 08:00:00 轉換為 unix 時間綴(絕對時間),再插入數據庫試試?

如下,使用 linux 的 date 命令轉換時間串為 unix 時間綴:

$  date  --date= 2020-02-23 08:00:00 +08:00  +%s
1582416000

然后用 mysql 的 from_unixtime() 函數,將 unix 時間綴轉換為 mysql 時間類型來插入數據。

如上,查詢出來的時間,也是東 9 區的 9 點,時間也是正確的。

為什么網上又說 timestamp 類型存在時區問題?

我發現網上說 timestamp 有時區問題,都是應用端插入數據,然后到數據庫中去看,結果發現時間不一樣,因此我打算在 Java 中寫個 Demo 試一下,看能不能重現這個問題。

1、首先,下面是 Java 中 Entity 的定義,與上面的 time_test 表對應,注意,這里面時間屬性都是用 Date 類型定義的,如下:

2、然后,我寫了兩個接口 /insert 與 /queryAll 來插入與查詢數據,如下:

3、然后我把數據庫的時區設置為 +09:00 時區,即日本的東 9 區,如下:

4、然后調用 /insert 接口插入數據,注意我接口傳入的時間是東 8 區的 8 點,如下:

5、插入完后,去數據庫中查詢一把,如下:

可以看到,time_stamp 字段時間是 9 點,且我已將數據庫時區設置為東 9 區,東 9 區的 9 點與東 8 區的 8 點,這兩個時間實際是相等的,因此時間數據沒錯。

6、然后我使用 /queryAll 接口將數據查詢出來,如下:

mysql 的 timestamp 存在時區問題怎么解決

timeStamp 屬性是 1582416000000,這是毫秒級的時間綴,秒級則是 1582416000,對應是東 8 區的 2020-02-23 08:00:00,時間數據也沒錯!

7、然后我又將 mysql 時區修改回 +8:00,并重啟我們的 java 應用,如下:

mysql 的 timestamp 存在時區問題怎么解決

8、再查詢一下數據,如下:

mysql 的 timestamp 存在時區問題怎么解決

timeStamp 屬性還是 1582416000000,時間沒有變化,這也是正確的。

那為什么網上會說 timestamp 存在時區問題?

經過一翻查看,我發現他們都提到了 jdbc 的 serverTimezone,會不會是這個配置錯誤導致的呢?就先試試吧!

1、如圖,我把數據庫時區修改回 +9:00 時區,然后故意把 jdbc 的 url 上的 serverTimezone 配置為與數據庫不一致的 GMT+ 8 時區,然后重啟 java 應用,如下:

mysql 的 timestamp 存在時區問題怎么解決

url: jdbc:mysql://localhost:3306/testdb?serverTimezone=GMT%2B8 useUnicode=true characterEncoding=utf8

其中 GMT%2B8 就是 GMT+8,因為在 url 上需要 urlencode,所以就變成了 GMT%2B8。

2、重新插入數據,注意插入的時間還是東 8 區的 8 點,如下:

mysql 的 timestamp 存在時區問題怎么解決

3、然后,我再到數據庫中查詢一把,如下:

mysql 的 timestamp 存在時區問題怎么解決

time_stamp 中時間竟然是 8 點!要知道我們雖然插入的是東 8 區的 8 點,但當前會話可是東 9 區的,東 8 區的 8 點等于東 9 區的 9 點,所以正確顯示應該為 9 點才對,時間差了 1 小時!

4、然后,我又調用 /queryAll 接口查詢了一把,想看看 mybatis 查詢出來的時間數據對不對,如下:

mysql 的 timestamp 存在時區問題怎么解決

可以看到 timeStamp 是 1582416000000,秒級是 1582416000,這個時間就是東 8 區的 8 點,東 9 區的 9 點啊!查詢出來的時間竟然是正確的,為什么???

serverTimezone 的本質

為了找出問題所在,我調試了一下 mysql 的 jdbc 驅動代碼,終于弄明白了原因,主要可以看看如下這幾點:

1.mysql 驅動創建連接后,會調用 com.mysql.jdbc.ConnectionImpl#configureTimezone() 來配置此連接的時區,如果配置了 serverTimezone,則會使用 serverTimezone 配置的時區,沒配置時會去取數據庫中的 time_zone 變量,這就是為什么我們沒有配置 serverTimezone 變量時,結果也是正確的。

// 若使用普通驅動,使用此方法配置 mysql 連接的時區
com.mysql.jdbc.ConnectionImpl#configureTimezone()
// 若使用 cj 驅動,使用此方法配置 mysql 連接的時區
com.mysql.cj.protocol.a.NativeProtocol#configureTimezone()

2. 調用 jdbc 的 setTimestamp() 方法時,實際調用的是 com.mysql.cj.jdbc.ClientPreparedStatement#setTimestamp(),這里面會根據 serverTimezone 指定的時區,將對應的 Timestamp 對象轉換為 serverTimezone 指定時區的本地時間字符串。

3. 執行 sql 語句時,會執行 com.mysql.cj.jdbc.ClientPreparedStatement#execute(),這里面 sendPacket 變量保存著真實會發送到 mysql 的 sql 語句。

注:看的是 8.0.11 版本 mysql-connector-java 驅動源碼,不同版本代碼會稍有差異,比如 5.2.16 版本驅動,jdbc url 上需要同時配置這兩個配置:useTimezone=true serverTimezone=GMT%2B8,且 setTimestamp() 對應的是 com.mysql.jdbc.PreparedStatement#setTimestampInternal 方法。

原理總結如下:

mysql 驅動在發送 sql 前,會將 jdbc 中的 Date 對象參數,根據 serverTimeZone 配置的時區轉化為日期字符串后,再發送 sql 請求給 mysql server,同樣在 mysql server 返回查詢結果后,結果中的日期值也是日期字符串,mysql 驅動會根據 serverTimeZone 配置的時區,將日期字符串轉化為 Date 對象。

因此,當 serverTimeZone 與數據庫實際時區不一致時,會發生時區轉換錯誤,導致時間偏差,如下:

a、比如 sql 參數是一個 Date 對象,時間值是東 8 區的 2020-02-23 08:00:00,注意它里面存儲的可不是 2020-02-23 08:00:00 這個字符串,它是 Date 對象 (絕對時間),只是我用文字表達出來是東 8 區的 2020-02-23 08:00:00。

b、然后,由于 serverTimeZone 配置的是東 8 區,mysql 驅動會將這個 Date 對象轉為 2020-02-23 08:00:00,注意這時已經是字符串了,然后再將 sql 發送給 mysql,注意這里的 sql 里面已經將 Date 參數替換為 2020-02-23 08:00:00 了,因為 Date 對象本身是無法走網絡的。

c、然后 mysql 數據庫接收到這個時間字符串 2020-02-23 08:00:00 后,由于數據庫時區配置是東 9 區,它會認為這個時間是東 9 區的,它會以東 9 區解析這個時間字符串,這時數據庫保存的時間是東 9 區的 2020-02-23 08:00:00,也就是東 8 區的 2020-02-23 07:00:00,保存的時間就偏差了 1 個小時。

d、查詢結果里時間為什么又對了呢,因為查詢結果返回了東 9 區的時間字符串,而 java 應用又將其理解為是東 8 區的時間,負負得正了!

將 serverTimezone 與 mysql 時區保持一致

so,那么如果我們將 serverTimezone 配置改正確,即與數據庫保持一致時,應該查詢到的時間就會是錯的,會少 1 個小時。

1、jdbc url 中使用與數據庫一樣的東 9 區 GMT+9,如下:

url: jdbc:mysql://localhost:3306/testdb?serverTimezone=GMT%2B9 useUnicode=true characterEncoding=utf8

其中的 GMT%2B9,即是 GMT+9。

2、然后重啟 Java 應用,再查詢一把看看,如下:

mysql 的 timestamp 存在時區問題怎么解決

返回的是毫秒級時間綴 1582412400000,秒級就是 1582412400,使用 linux 的 date 命令轉換為時間字符串形式:

$  date  --date= @1582412400  + %F %T %z 
2020-02-23 07:00:00 +0800

看到沒,它是東 8 區的 7 點,剛好差了 1 個小時。

3、所以,使用 mysql 的 timestamp 類型時,對于 java 應用來說,一定要保證 jdbc url 中的 serverTimezone 與數據庫中的時區配置是一致的。

另外一點是,當沒有配置 serverTimezone 時,mysql 驅動會自動讀取 mysql server 中配置的時區,這里面也有坑!如下:

mysql 驅動自動讀取數據庫時區的坑

3.1 mysql 安裝好后,默認時區是 SYSTEM,而 SYSTEM 指的是 system_time_zone 變量的時區,如下:

mysql 的 timestamp 存在時區問題怎么解決

3.2 當 mysql 驅動讀到 time_zone 變量是 SYSTEM 時,會再去讀取 system_time_zone 變量,而 system_time_zone 對于國內來說,默認是 CST,這是一個混亂的時區,是 4 個不同時區的縮寫,如下:

mysql 的 timestamp 存在時區問題怎么解決

對于 Linux 或 MySQL,會認為 CST 是中國標準時間 (+8:00),但 Java 卻認為 CST 是美國標準時間 (-6:00)(注:可能和 Java 運行在 Windows 中有關):

如下,linux 中 CST 等于 +0800,即中國時區:

$  date  + %F %T %Z %z 
2021-09-12 18:35:49 CST +0800

如下,java 中 CST 等于 -06:00,美國時區:

mysql 的 timestamp 存在時區問題怎么解決

3.3 因此 mysql 驅動取到 CST 這個時區值時,它會以為這是 -6:00 時區,但 MySQL 卻理解為 +8:00 時區,因此 MySQL 時區一定不要配置為 CST,而要配置為具體的時區,如 +8:00,但如果 MySQL 時區為 CST 且不可修改的情況下,一定要配置 jdbc 的 serverTimezone 為清晰的時區 (如:GMT+8)。

Entity 中日期屬性是 String 呢?

1、我們將 Entity 對象中的時間屬性改為 String(不推薦),如下:

mysql 的 timestamp 存在時區問題怎么解決

2、然后也寫兩個接口,/insert2 與 /queryAll2,如下:

mysql 的 timestamp 存在時區問題怎么解決

3、然后插入數據,注意這時我是直接將無時區的 8 點,作為參數給到 sql 的,如下:

mysql 的 timestamp 存在時區問題怎么解決

4、然后再查詢一把,如下:

mysql 的 timestamp 存在時區問題怎么解決

如上所示,time_stamp 字段值是 8 點,但此時數據庫時區是東 9 區,所以這是東 9 區的 8 點。

5、然后我將數據庫與 jdbc 中 serverTimezone 都改為東 8 區呢,改完后重啟 Java 應用,如下:

mysql 的 timestamp 存在時區問題怎么解決

url: jdbc:mysql://localhost:3306/testdb?serverTimezone=GMT%2B8 useUnicode=true characterEncoding=utf8

6、再次插入數據,參數還是無時區的 8 點,如下:

mysql 的 timestamp 存在時區問題怎么解決

7、再查詢一把,如下:

mysql 的 timestamp 存在時區問題怎么解決

如上所示,time_stamp 字段值是 8 點,但現在數據庫時間是東 8 區,所以這是東 8 區的 8 點。

8、然后我再將 jdbc url 上的 serverTimezone 調整為東 9 區,然后重啟 Java 應用,如下:

url: jdbc:mysql://localhost:3306/testdb?serverTimezone=GMT%2B9 useUnicode=true characterEncoding=utf8

現在 serverTimezone 與數據庫中不一致,數據庫是東 8 區,serverTimezone 是東 9 區。

9、我們再次插入無時區的 8 點,如下:

mysql 的 timestamp 存在時區問題怎么解決

10、然后再查詢一把,如下:

mysql 的 timestamp 存在時區問題怎么解決

time_stamp 字段值還是 8 點,數據庫是東 8 區,所以這是東 8 區的 8 點,但我們 serverTimezone 與數據庫的時區不一致啊,沒看到時間有偏差,為什么?

解釋一下

前面說過了,對于 jdbc 中的 Date 對象,在發送給 mysql 前,會先根據 serverTimezone 轉換為相應時區的時間字符串,但現在 Entity 中時間屬性是 String 類型,mysql 驅動不會進行轉換,所以不管 serverTimezone 怎么配置,對 String 類型的時間串都沒影響。

這樣的話,似乎 java 中日期類型用時間字符串來存還好些,不容易出錯,但請再認真考慮一下,調用方傳了一個無時區的 8 點,數據庫自作主張,就將其認為是東 9 區的 8 點,但如果這個時間字符串實際是東 8 區的 8 點呢?這時如果保存到數據庫中為東 9 區的 8 點,那數據就存錯了!

那如果目前 api 接口就傳的無時區的時間串,Entity 中就定義的 String,怎么解決呢?

1、詢問接口定義人員,這個接口的時間串指的是哪個時區的,比如是東 8 區的 2020-02-23 08:00:00。

2、然后接口接收到時間后,要以東 8 區將時間字符串轉換為 Date 對象,如下:

SimpleDateFormat sdf = new SimpleDateFormat( yyyy-MM-dd HH:mm:ss 
sdf.setTimeZone(TimeZone.getTimeZone( GMT+8));
Date date = sdf.parse(2020-02-23 08:00:00

3、然后如果 Entity 中時間屬性定義的是 String,那么我們要再將 Date 對象以數據庫的時區格式化為對應的時間字符串,比如數據庫時區是東 9 區,那么格式化后就是 2020-02-23 09:00:00,如下:

SimpleDateFormat sdf = new SimpleDateFormat( yyyy-MM-dd HH:mm:ss 
sdf.setTimeZone(TimeZone.getTimeZone( GMT+9));
String dateStr = sdf.format(date);
entity.setTimeStamp(dateStr);

4、然后將 Entity 保存到 mysql 中的,就也會是東 9 區的 2020-02-23 09:00:00,結果正確。

所以,使用 String 類型來存儲時間數據,要想將時間值保存正確,超級麻煩,不建議在實際開發中這種使用。

最佳實踐

1、大多數團隊會規定 api 中傳遞時間要用 unix 時間綴,因為如果你傳一個 2020-02-23 08:00:00 時間值,它到底是哪個時區的 8 點呢?對于 unix 時間綴,就不會有此問題,因為它是絕對時間。而如果某些特殊原因,一定要使用時間字符串,最好使用 ISO8601 規范那種帶時區的時間串,比如:2020-02-23T08:00:00+08:00。

2、Mybatis 中 Entity 定義要與數據庫定義一致,數據庫中是 timestamp,那么 Entity 中要定義為 Date 對象,因為 mysql 驅動在執行 sql 時,會自動根據 serverTimezone 配置幫你轉換為數據庫時區的時間串,如果你自己來轉換,你極有可能因為忘記調用 setTimeZone() 方法,而使用當前 java 應用所在機器的默認時區,一旦 java 應用所在機器的時區與數據庫的時區不一致,就會出現時區問題。

3、jdbc 的 serverTimezone 參數,要配置正確,當不配置時,mysql 驅動會自動讀取 mysql server 的時區,此時一定要將 mysql server 的時區指定為清晰的時區 (如:+08:00),切勿使用 CST。

4、如果數據庫時區修改后,jdbc 的 serverTimezone 也要跟著修改,并重啟 Java 應用,就算沒有配置 serverTimezone,也需要重啟,因為 mysql 驅動初始化連接時,會將當前數據庫時區緩存到一個 java 變量中,不重啟 Java 應用它不會變。

數據庫中用 timestamp 還是 int 來存儲時間?

如果用 int 型時間綴存儲,不管數據庫時區是啥,都不影響,因為存儲的是絕對時間,看起來完美解決了時區問題。

但從某些角度看,這種方案只是把時區問題從數據庫端推到應用端去了,時區問題將出現在將時間字符串轉換為時間綴的過程中,比如某程序員從 api 接口中拿到時間字符串后,沒考慮時區,直接轉為 unix 時間綴,就可能出現時區問題。

因此,對于不帶時區的時間串解析,一定要問清楚這是哪個時區的時間,并在代碼中顯式指定!

另外,用 int 存儲時間還有如下 3 個不好的點:

開發人員看到這個字段后,無法一目了然的了解到這個時間綴大概是個什么時間,需要去轉換一下,會很繁瑣。

像 update_time 這樣的字段,數據庫提供了 DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 的機制,這樣在更新任何字段時,update_time 會自動更新,而如果使用 int 存儲,就需要程序員每次更新表時,重新 set 這個字段,容易遺忘。

由于 int 只有 4 個字節,用它來存儲時間,會在 2038 年后溢出,而對于 timestamp 來說,MySQL 將其底層存儲統一修改為 8 個字節,相對來說還是比較容易的。

當然,也并不是建議不用 int,這是見仁見智的,不管用 timestamp 還是 int,都沒有致命性問題的。

“mysql 的 timestamp 存在時區問題怎么解決”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注丸趣 TV 網站,丸趣 TV 小編將為大家輸出更多高質量的實用文章!

正文完
 
丸趣
版權聲明:本站原創文章,由 丸趣 2023-07-15發表,共計8674字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 呈贡县| 贵定县| 南陵县| 资溪县| 昭觉县| 青海省| 炉霍县| 乐业县| 平潭县| 广德县| 西峡县| 瓮安县| 容城县| 凌源市| 天峻县| 孟津县| 宜宾县| 布尔津县| 乾安县| 乌兰浩特市| 香港| 沁阳市| 瑞昌市| 莱西市| 玉门市| 黄冈市| 南雄市| 博乐市| 正镶白旗| 广宗县| 边坝县| 宜兴市| 新巴尔虎右旗| 井研县| 时尚| 阳原县| 克什克腾旗| 卢龙县| 淮南市| 贡嘎县| 金坛市|