mysql用來存儲日期時間的數據類型有兩種:DATETIME和TIMESTAMP。
DATETIME類型,可認為存儲的是日期時間文本,如 2022-02-19 21:26:35。它不是一個完整的時間,因為它未指明時區,從而也無法定位到現實中確定的時間點。
TIMESTAMP類型,其存儲的是時間戳,即保存了從1970年1月1日0點整(格林尼治標准時間)以來的秒數。它是一個完整的時間,可以代表現實中的某個時間點。
由此可知,DATETIME類型其存儲結構決定了,其無法代表現實中具體的時間點。那怎么辦呢?我們只需對以DATETIME類型存儲的 日期時間文本 指定一個時區,即可明確表示現實中的某個時間點。如,日期時間文本 ‘2022-02-19 21:26:35’,我們指定其為東八區,則這個時間點就是完整的了。在mysql中,我們通過指定數據庫的時區,來實現上述操作。
mysql的時區,可以在數據庫服務中設置默認值確定,也可以在連接會話中指定,且每個會話對時區的指定只在當前會話有效。因此,在一個mysql的連接會話中,可認為其時區由連接方決定。
由上所述,我們得到3個關鍵點:
(1)DATETIME類型存儲的是日期時間文本
(2)TIMESTAMP類型存儲的是時間戳
(3)mysql的時區可由數據庫連接方指定
* 問題場景
在涉及到這兩種數據類型的java程序中,我們經常遇到兩個問題場景:
(1)jvm從mysql取出的日期時間數據,在到瀏覽器前端后,瀏覽器處看到的顯示文本,與mysql中有小時偏差。
(2)前端輸入的日期時間文本,在到mysql后,看到的顯示文本,與前端輸入有小時偏差。
* 問題分析
一個普通的java應用,根據數據的流經路徑,會有3個區域:mysql數據庫、jvm、brower瀏覽器。這3個區域的時區設置,未必一致,這就導致了一個同樣的時間點,在3處環境的顯示不一致。此外,時間類型的數據,在3處流轉時,若不以完整時間點的形式來流轉,這也導致了3處的時間不一致。
- TIMESTAMP
如前所述,timestamp類型存儲的是時間戳,它能代表一個完整的時間點。mysql--jvm--brower之間若以標准的時間戳的形式進行交互,不會有歧義,但是顯示的文本隨時區會有差異,但這屬於正常現象。
但以日期時間文本的形式,且不帶時區的形式,會造成混淆。如brower與jvm之間以時間文本的形式交互,則再與mysql交互時,會把該時間文本,默認指定為jvm的時區,造成混淆,違背了用戶的本意。
- DATETIME
DATETIME類型不是完整的時間點,但jdbc默認會把它映射為一個完整的時間類型,即java.sql.Timestamp。在映射過程中,jdbc給DATETIME類型的數據指定了一個時區,從而使其成為一個完整的時間點。時區的指定,是在jdbc與mysql的連接會話中指定的,指定方式詳見附文。
所指定時區,可能與jvm和brower所在時區都不一致,導致java.sql.Timestamp類型在jvm中轉化為本地時間后,即LocalDatetime,其文本值與原始數據庫中的文本值不一致。而瀏覽器端得到的時間文本,又直接是jvm中的本地時間文本,從而在整個操作中,潛意識地將瀏覽器的時區設置成了和jvm一致,導致數據的處理的混淆和錯誤。
*最佳實踐
回歸到事務的本質,即 DATETIME類型存儲的是日期時間文本,TIMESTAMP存儲的是時間戳。因此建議:
1 TIMESTAMP類型:mysql--jvm–brower之間都以時間戳的形式交互,前端根據本身所在時區進行文本顯示的轉換。
2 DATETIME類型:類型本身不是一個完整的時間,但jdbc與mysql交互時,會在連接會話中給mysql指定時區,再根據jvm時區和所指定的mysql時區,作文本顯示值的轉換,並將轉換后的值傳給mysql。因此,為了避免引起混淆,前后端交互時,也應該以完整時間點的形式進行交互,即 “時間文本+時區” 的格式,如,“2021-08-12T10:52:32+08:00 ”。且時區最好為連接會話中所指定的時區。這樣就保證了前端看到的時間文本與mysql中一致,且時區轉換也不會引起混淆。
* 名稱解釋
(1)完整時間點
可表示現實中的某個時間點,本文中有兩種形式:a 日期時間文本+時區,b 時間戳
(2)mysql時區
在一個連接會話中,mysql的時區決定於3個配置:a. 會話中的時區配置(jdbc連接字符串中的serverTimezone),b. mysql默認全局時區,c. 操作系統時區。且優先級為 a>b>c。