站長(zhǎng)資訊網(wǎng)
最全最豐富的資訊網(wǎng)站

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

本篇文章給大家?guī)?lái)了關(guān)于mysql的相關(guān)知識(shí),主要介紹了淺談mysql的timestamp存在的時(shí)區(qū)問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,下面一起來(lái)看一下,希望對(duì)大家有幫助。

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

推薦學(xué)習(xí):mysql視頻教程

簡(jiǎn)介

眾所周知,mysql中有兩個(gè)時(shí)間類(lèi)型,timestamp與datetime,但當(dāng)在網(wǎng)上搜索timestamp與datetime區(qū)別時(shí),會(huì)發(fā)現(xiàn)網(wǎng)上有不少與時(shí)區(qū)有關(guān)的完全相反的結(jié)論,主要兩種:

timestamp沒(méi)有時(shí)區(qū)問(wèn)題,而datetime有時(shí)區(qū)問(wèn)題,原因是timestamp是以UTC格式存儲(chǔ)的,而datetime存儲(chǔ)類(lèi)似于時(shí)間字符串的形式,示例博文:MySQL 中 datetime 和 timestamp 的區(qū)別與選擇timestamp也有時(shí)區(qū)問(wèn)題,示例博文:mysql中timestamp時(shí)區(qū)的問(wèn)題

兩種觀點(diǎn)讓人迷惑,那timestamp到底會(huì)不會(huì)有時(shí)區(qū)問(wèn)題呢?

答:因?yàn)閙ysql數(shù)據(jù)庫(kù)未指定所在時(shí)區(qū)默認(rèn)為美國(guó)中部時(shí)間
(UTC-06:00),美國(guó)從“3月11日”至“11月7日”實(shí)行夏令時(shí),美國(guó)中部時(shí)間改為 UTC-05:00,與 UTC+08:00 相差 13 小時(shí),冬令時(shí)則相差14個(gè)小時(shí)。所以存儲(chǔ)的時(shí)候時(shí)間就已經(jīng)有“誤差了”。
各位小伙伴使用timestamp類(lèi)型的時(shí)候一定要注意指定時(shí)區(qū),不管是在數(shù)據(jù)庫(kù)配置指定還是數(shù)據(jù)庫(kù)連接的參數(shù)設(shè)置,一定要指定時(shí)區(qū)。

serverTimezone=Asia/Shanghai  show variables like ‘%time_zone%'; set time_zone='+08:00';  select now();

基本概念

時(shí)區(qū):
由于地域的限制,人們發(fā)明了時(shí)區(qū)的概念,用來(lái)適應(yīng)人們?cè)跁r(shí)間感受上的差異,比如中國(guó)的時(shí)區(qū)是東8區(qū),表示為+8:00,或GMT+8,而日本的時(shí)區(qū)是東9區(qū),表示為+9:00,或GMT+9,當(dāng)中國(guó)是早上8點(diǎn)時(shí),日本是早上9點(diǎn),即東8區(qū)的8點(diǎn)與東9區(qū)的9點(diǎn),這兩個(gè)時(shí)間是相等的。
另外時(shí)間還有如下兩個(gè)概念:

絕對(duì)時(shí)間:

如unix時(shí)間綴,是1970-01-01 00:00:00開(kāi)始到現(xiàn)在的秒數(shù),如:1582416000,這種表示是絕對(duì)時(shí)間,不受時(shí)區(qū)影響,也叫紀(jì)元時(shí)epoch。

本地時(shí)間:

相對(duì)于某一時(shí)區(qū)的時(shí)間,是本地時(shí)間,比如東8區(qū)的2020-02-23 08:00:00,是中國(guó)人的本地時(shí)間,而在此時(shí),日本人的本地時(shí)間是2020-02-23 09:00:00,所以本地時(shí)間都是與某一時(shí)區(qū)相關(guān)的,脫離時(shí)區(qū)看本地時(shí)間,是沒(méi)有意義的,因?yàn)槟悴⒉恢肋@具體是指的什么時(shí)間點(diǎn)。

比如在Java中,Date對(duì)象是絕對(duì)時(shí)間,通過(guò)SimpleDateFormat格式化出來(lái)的yyyy-MM-dd HH:mm:ss形式的時(shí)間字符串,是本地時(shí)間,如果SimpleDateFormat沒(méi)有調(diào)用setTimeZone()顯示指定時(shí)區(qū),那么默認(rèn)用的是jvm運(yùn)行在的操作系統(tǒng)上的時(shí)區(qū),我們開(kāi)發(fā)機(jī)上的時(shí)區(qū)基本都是GMT+8

timestamp與datetime區(qū)別

如下,我創(chuàng)建了一張表,里面time_stamp是timestamp類(lèi)型,date_time是datetime類(lèi)型,create_timestamp、create_datetime是timestamp與datetime類(lèi)型,但是它們可以由數(shù)據(jù)庫(kù)自動(dòng)生成。

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

1、首先將數(shù)據(jù)庫(kù)時(shí)區(qū)設(shè)置為+8:00,即中國(guó)的東8區(qū)

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

2、然后如下手動(dòng)插入一個(gè)固定時(shí)間的數(shù)據(jù),以及用now()函數(shù)插入當(dāng)前時(shí)間

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

3、當(dāng)插入完數(shù)據(jù)后,然后我們修改當(dāng)前會(huì)話的時(shí)區(qū)為+9:00,即日本的東9區(qū),然后再次查看數(shù)據(jù)

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

4、如上,定義為timestamp類(lèi)型的列time_stampcreate_timestamp不管是手動(dòng)插入的,還是now()函數(shù)插入的,東9區(qū)都比東8區(qū)的時(shí)間大1個(gè)小時(shí),這是正確的,說(shuō)明timestamp類(lèi)型是時(shí)區(qū)相關(guān)的,然而定義為datetime類(lèi)型的date_timecreate_datetime字段,時(shí)間都沒(méi)有變化,這說(shuō)明datetime類(lèi)型是時(shí)區(qū)無(wú)關(guān)的。

結(jié)論:
timestamp在存儲(chǔ)上是包含時(shí)區(qū)的,而datetime是不包含時(shí)區(qū),說(shuō)明網(wǎng)上的第一種說(shuō)法是對(duì)的。

再看個(gè)例子
我們將東8區(qū)的的2020-02-23 08:00:00轉(zhuǎn)換為unix時(shí)間綴(絕對(duì)時(shí)間),再插入數(shù)據(jù)庫(kù)試試?
如下,使用linux的date命令轉(zhuǎn)換時(shí)間串為unix時(shí)間綴:

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

然后用mysql的()函數(shù),將unix時(shí)間綴轉(zhuǎn)換為mysql時(shí)間類(lèi)型來(lái)插入數(shù)據(jù)。

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

如上,查詢出來(lái)的時(shí)間,也是東9區(qū)的9點(diǎn),時(shí)間也是正確的。

為什么網(wǎng)上又說(shuō)timestamp類(lèi)型存在時(shí)區(qū)問(wèn)題?

我發(fā)現(xiàn)網(wǎng)上說(shuō)timestamp有時(shí)區(qū)問(wèn)題,都是應(yīng)用端插入數(shù)據(jù),然后到數(shù)據(jù)庫(kù)中去看,結(jié)果發(fā)現(xiàn)時(shí)間不一樣,因此我打算在Java中寫(xiě)個(gè)Demo試一下,看能不能重現(xiàn)這個(gè)問(wèn)題。

1、首先,下面是Java中Entity的定義,與上面的time_test表對(duì)應(yīng),注意,這里面時(shí)間屬性都是用Date類(lèi)型定義的,如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

2、然后,我寫(xiě)了兩個(gè)接口/insert/queryAll來(lái)插入與查詢數(shù)據(jù),如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

3、然后我把數(shù)據(jù)庫(kù)的時(shí)區(qū)設(shè)置為+09:00時(shí)區(qū),即日本的東9區(qū),如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

4、然后調(diào)用/insert接口插入數(shù)據(jù),注意我接口傳入的時(shí)間是東8區(qū)的8點(diǎn),如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

5、插入完后,去數(shù)據(jù)庫(kù)中查詢一把,如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

可以看到,time_stamp字段時(shí)間是9點(diǎn),且我已將數(shù)據(jù)庫(kù)時(shí)區(qū)設(shè)置為東9區(qū),東9區(qū)的9點(diǎn)與東8區(qū)的8點(diǎn),這兩個(gè)時(shí)間實(shí)際是相等的,因此時(shí)間數(shù)據(jù)沒(méi)錯(cuò)。

6、然后我使用/queryAll接口將數(shù)據(jù)查詢出來(lái),如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

timeStamp屬性是1582416000000,這是毫秒級(jí)的時(shí)間綴,秒級(jí)則是1582416000,對(duì)應(yīng)是東8區(qū)的2020-02-23 08:00:00,時(shí)間數(shù)據(jù)也沒(méi)錯(cuò)!

7、然后我又將mysql時(shí)區(qū)修改回+8:00,并重啟我們的java應(yīng)用,如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

8、再查詢一下數(shù)據(jù),如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

timeStamp屬性還是1582416000000,時(shí)間沒(méi)有變化,這也是正確的。

那為什么網(wǎng)上會(huì)說(shuō)timestamp存在時(shí)區(qū)問(wèn)題?

經(jīng)過(guò)一翻查看,我發(fā)現(xiàn)他們都提到了jdbc的serverTimezone,會(huì)不會(huì)是這個(gè)配置錯(cuò)誤導(dǎo)致的呢?就先試試吧!
1、如圖,我把數(shù)據(jù)庫(kù)時(shí)區(qū)修改回+9:00時(shí)區(qū),然后故意把jdbc的url上的serverTimezone配置為與數(shù)據(jù)庫(kù)不一致的GMT+8時(shí)區(qū),然后重啟java應(yīng)用,如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

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

其中GMT%2B8就是GMT+8,因?yàn)樵趗rl上需要urlencode,所以就變成了GMT%2B8

2、重新插入數(shù)據(jù),注意插入的時(shí)間還是東8區(qū)的8點(diǎn),如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

3、然后,我再到數(shù)據(jù)庫(kù)中查詢一把,如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

time_stamp中時(shí)間竟然是8點(diǎn)!要知道我們雖然插入的是東8區(qū)的8點(diǎn),但當(dāng)前會(huì)話可是東9區(qū)的,東8區(qū)的8點(diǎn)等于東9區(qū)的9點(diǎn),所以正確顯示應(yīng)該為9點(diǎn)才對(duì),時(shí)間差了1小時(shí)!

4、然后,我又調(diào)用/queryAll接口查詢了一把,想看看mybatis查詢出來(lái)的時(shí)間數(shù)據(jù)對(duì)不對(duì),如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

可以看到timeStamp1582416000000,秒級(jí)是1582416000,這個(gè)時(shí)間就是東8區(qū)的8點(diǎn),東9區(qū)的9點(diǎn)啊!查詢出來(lái)的時(shí)間竟然是正確的,為什么???

serverTimezone的本質(zhì)

為了找出問(wèn)題所在,我調(diào)試了一下mysql的jdbc驅(qū)動(dòng)代碼,終于弄明白了原因,主要可以看看如下這幾點(diǎn):

1.mysql驅(qū)動(dòng)創(chuàng)建連接后,會(huì)調(diào)用com.mysql.jdbc.ConnectionImpl#configureTimezone()來(lái)配置此連接的時(shí)區(qū),如果配置了serverTimezone,則會(huì)使用serverTimezone配置的時(shí)區(qū),沒(méi)配置時(shí)會(huì)去取數(shù)據(jù)庫(kù)中的time_zone變量,這就是為什么我們沒(méi)有配置serverTimezone變量時(shí),結(jié)果也是正確的。

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

2.調(diào)用jdbc的setTimestamp()方法時(shí),實(shí)際調(diào)用的是com.mysql.cj.jdbc.ClientPreparedStatement#setTimestamp(),這里面會(huì)根據(jù)serverTimezone指定的時(shí)區(qū),將對(duì)應(yīng)的Timestamp對(duì)象轉(zhuǎn)換為serverTimezone指定時(shí)區(qū)的本地時(shí)間字符串。

3.執(zhí)行sql語(yǔ)句時(shí),會(huì)執(zhí)行com.mysql.cj.jdbc.ClientPreparedStatement#execute(),這里面sendPacket變量保存著真實(shí)會(huì)發(fā)送到mysql的sql語(yǔ)句。

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

原理總結(jié)如下:
mysql驅(qū)動(dòng)在發(fā)送sql前,會(huì)將jdbc中的Date對(duì)象參數(shù),根據(jù)serverTimeZone配置的時(shí)區(qū)轉(zhuǎn)化為日期字符串后,再發(fā)送sql請(qǐng)求給mysql server,同樣在mysql server返回查詢結(jié)果后,結(jié)果中的日期值也是日期字符串,mysql驅(qū)動(dòng)會(huì)根據(jù)serverTimeZone配置的時(shí)區(qū),將日期字符串轉(zhuǎn)化為Date對(duì)象。

因此,當(dāng)serverTimeZone與數(shù)據(jù)庫(kù)實(shí)際時(shí)區(qū)不一致時(shí),會(huì)發(fā)生時(shí)區(qū)轉(zhuǎn)換錯(cuò)誤,導(dǎo)致時(shí)間偏差,如下:
a、比如sql參數(shù)是一個(gè)Date對(duì)象,時(shí)間值是東8區(qū)的2020-02-23 08:00:00,注意它里面存儲(chǔ)的可不是2020-02-23 08:00:00這個(gè)字符串,它是Date對(duì)象(絕對(duì)時(shí)間),只是我用文字表達(dá)出來(lái)是東8區(qū)的2020-02-23 08:00:00
b、然后,由于serverTimeZone配置的是東8區(qū),mysql驅(qū)動(dòng)會(huì)將這個(gè)Date對(duì)象轉(zhuǎn)為2020-02-23 08:00:00,注意這時(shí)已經(jīng)是字符串了,然后再將sql發(fā)送給mysql,注意這里的sql里面已經(jīng)將Date參數(shù)替換為2020-02-23 08:00:00了,因?yàn)镈ate對(duì)象本身是無(wú)法走網(wǎng)絡(luò)的。
c、然后mysql數(shù)據(jù)庫(kù)接收到這個(gè)時(shí)間字符串2020-02-23 08:00:00后,由于數(shù)據(jù)庫(kù)時(shí)區(qū)配置是東9區(qū),它會(huì)認(rèn)為這個(gè)時(shí)間是東9區(qū)的,它會(huì)以東9區(qū)解析這個(gè)時(shí)間字符串,這時(shí)數(shù)據(jù)庫(kù)保存的時(shí)間是東9區(qū)的2020-02-23 08:00:00,也就是東8區(qū)的2020-02-23 07:00:00,保存的時(shí)間就偏差了1個(gè)小時(shí)。
d、查詢結(jié)果里時(shí)間為什么又對(duì)了呢,因?yàn)椴樵兘Y(jié)果返回了東9區(qū)的時(shí)間字符串,而java應(yīng)用又將其理解為是東8區(qū)的時(shí)間,負(fù)負(fù)得正了!

將serverTimezone與mysql時(shí)區(qū)保持一致

so,那么如果我們將serverTimezone配置改正確,即與數(shù)據(jù)庫(kù)保持一致時(shí),應(yīng)該查詢到的時(shí)間就會(huì)是錯(cuò)的,會(huì)少1個(gè)小時(shí)。

1、jdbc url中使用與數(shù)據(jù)庫(kù)一樣的東9區(qū)GMT+9,如下:

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

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

2、然后重啟Java應(yīng)用,再查詢一把看看,如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

返回的是毫秒級(jí)時(shí)間綴1582412400000,秒級(jí)就是1582412400,使用linux的date命令轉(zhuǎn)換為時(shí)間字符串形式:

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

看到?jīng)],它是東8區(qū)的7點(diǎn),剛好差了1個(gè)小時(shí)。

3、所以,使用mysql的timestamp類(lèi)型時(shí),對(duì)于java應(yīng)用來(lái)說(shuō),一定要保證jdbc url中的serverTimezone與數(shù)據(jù)庫(kù)中的時(shí)區(qū)配置是一致的。
另外一點(diǎn)是,當(dāng)沒(méi)有配置serverTimezone時(shí),mysql驅(qū)動(dòng)會(huì)自動(dòng)讀取mysql server中配置的時(shí)區(qū),這里面也有坑!如下:

mysql驅(qū)動(dòng)自動(dòng)讀取數(shù)據(jù)庫(kù)時(shí)區(qū)的坑

3.1 mysql安裝好后,默認(rèn)時(shí)區(qū)是SYSTEM,而SYSTEM指的是system_time_zone變量的時(shí)區(qū),如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

3.2 當(dāng)mysql驅(qū)動(dòng)讀到time_zone變量是SYSTEM時(shí),會(huì)再去讀取system_time_zone變量,而system_time_zone對(duì)于國(guó)內(nèi)來(lái)說(shuō),默認(rèn)是CST,這是一個(gè)混亂的時(shí)區(qū),是4個(gè)不同時(shí)區(qū)的縮寫(xiě),如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

對(duì)于Linux或MySQL,會(huì)認(rèn)為CST是中國(guó)標(biāo)準(zhǔn)時(shí)間(+8:00),但Java卻認(rèn)為CST是美國(guó)標(biāo)準(zhǔn)時(shí)間(-6:00)(注:可能和Java運(yùn)行在Windows中有關(guān)):
如下,linux中CST等于+0800,即中國(guó)時(shí)區(qū):

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

如下,java中CST等于-06:00,美國(guó)時(shí)區(qū):

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

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

Entity中日期屬性是String呢?

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

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

2、然后也寫(xiě)兩個(gè)接口,/insert2/queryAll2,如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

3、然后插入數(shù)據(jù),注意這時(shí)我是直接將無(wú)時(shí)區(qū)的8點(diǎn),作為參數(shù)給到sql的,如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

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

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

如上所示,time_stamp字段值是8點(diǎn),但此時(shí)數(shù)據(jù)庫(kù)時(shí)區(qū)是東9區(qū),所以這是東9區(qū)的8點(diǎn)。
5、然后我將數(shù)據(jù)庫(kù)與jdbc中serverTimezone都改為東8區(qū)呢,改完后重啟Java應(yīng)用,如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

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

6、再次插入數(shù)據(jù),參數(shù)還是無(wú)時(shí)區(qū)的8點(diǎn),如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

7、再查詢一把,如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

如上所示,time_stamp字段值是8點(diǎn),但現(xiàn)在數(shù)據(jù)庫(kù)時(shí)間是東8區(qū),所以這是東8區(qū)的8點(diǎn)。
8、然后我再將jdbc url上的serverTimezone調(diào)整為東9區(qū),然后重啟Java應(yīng)用,如下:

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

現(xiàn)在serverTimezone與數(shù)據(jù)庫(kù)中不一致,數(shù)據(jù)庫(kù)是東8區(qū),serverTimezone是東9區(qū)。
9、我們?cè)俅尾迦霟o(wú)時(shí)區(qū)的8點(diǎn),如下:

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

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

深入淺析mysql的timestamp存在的時(shí)區(qū)問(wèn)題

time_stamp字段值還是8點(diǎn),數(shù)據(jù)庫(kù)是東8區(qū),所以這是東8區(qū)的8點(diǎn),但我們serverTimezone與數(shù)據(jù)庫(kù)的時(shí)區(qū)不一致啊,沒(méi)看到時(shí)間有偏差,為什么?

解釋一下
前面說(shuō)過(guò)了,對(duì)于jdbc中的Date對(duì)象,在發(fā)送給mysql前,會(huì)先根據(jù)serverTimezone轉(zhuǎn)換為相應(yīng)時(shí)區(qū)的時(shí)間字符串,但現(xiàn)在Entity中時(shí)間屬性是String類(lèi)型,mysql驅(qū)動(dòng)不會(huì)進(jìn)行轉(zhuǎn)換,所以不管serverTimezone怎么配置,對(duì)String類(lèi)型的時(shí)間串都沒(méi)影響。

這樣的話,似乎java中日期類(lèi)型用時(shí)間字符串來(lái)存還好些,不容易出錯(cuò),但請(qǐng)?jiān)僬J(rèn)真考慮一下,調(diào)用方傳了一個(gè)無(wú)時(shí)區(qū)的8點(diǎn),數(shù)據(jù)庫(kù)自作主張,就將其認(rèn)為是東9區(qū)的8點(diǎn),但如果這個(gè)時(shí)間字符串實(shí)際是東8區(qū)的8點(diǎn)呢?這時(shí)如果保存到數(shù)據(jù)庫(kù)中為東9區(qū)的8點(diǎn),那數(shù)據(jù)就存錯(cuò)了!

那如果目前api接口就傳的無(wú)時(shí)區(qū)的時(shí)間串,Entity中就定義的String,怎么解決呢?
1、詢問(wèn)接口定義人員,這個(gè)接口的時(shí)間串指的是哪個(gè)時(shí)區(qū)的,比如是東8區(qū)的2020-02-23 08:00:00。
2、然后接口接收到時(shí)間后,要以東8區(qū)將時(shí)間字符串轉(zhuǎn)換為Date對(duì)象,如下:

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中時(shí)間屬性定義的是String,那么我們要再將Date對(duì)象以數(shù)據(jù)庫(kù)的時(shí)區(qū)格式化為對(duì)應(yīng)的時(shí)間字符串,比如數(shù)據(jù)庫(kù)時(shí)區(qū)是東9區(qū),那么格式化后就是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中的,就也會(huì)是東9區(qū)的2020-02-23 09:00:00,結(jié)果正確。

所以,使用String類(lèi)型來(lái)存儲(chǔ)時(shí)間數(shù)據(jù),要想將時(shí)間值保存正確,超級(jí)麻煩,不建議在實(shí)際開(kāi)發(fā)中這種使用。

最佳實(shí)踐

1、大多數(shù)團(tuán)隊(duì)會(huì)規(guī)定api中傳遞時(shí)間要用unix時(shí)間綴,因?yàn)槿绻銈饕粋€(gè)2020-02-23 08:00:00時(shí)間值,它到底是哪個(gè)時(shí)區(qū)的8點(diǎn)呢?對(duì)于unix時(shí)間綴,就不會(huì)有此問(wèn)題,因?yàn)樗墙^對(duì)時(shí)間。而如果某些特殊原因,一定要使用時(shí)間字符串,最好使用ISO8601規(guī)范那種帶時(shí)區(qū)的時(shí)間串,比如:2020-02-23T08:00:00+08:00

2、Mybatis中Entity定義要與數(shù)據(jù)庫(kù)定義一致,數(shù)據(jù)庫(kù)中是timestamp,那么Entity中要定義為Date對(duì)象,因?yàn)閙ysql驅(qū)動(dòng)在執(zhí)行sql時(shí),會(huì)自動(dòng)根據(jù)serverTimezone配置幫你轉(zhuǎn)換為數(shù)據(jù)庫(kù)時(shí)區(qū)的時(shí)間串,如果你自己來(lái)轉(zhuǎn)換,你極有可能因?yàn)橥浾{(diào)用setTimeZone()方法,而使用當(dāng)前java應(yīng)用所在機(jī)器的默認(rèn)時(shí)區(qū),一旦java應(yīng)用所在機(jī)器的時(shí)區(qū)與數(shù)據(jù)庫(kù)的時(shí)區(qū)不一致,就會(huì)出現(xiàn)時(shí)區(qū)問(wèn)題。

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

4、如果數(shù)據(jù)庫(kù)時(shí)區(qū)修改后,jdbc的serverTimezone也要跟著修改,并重啟Java應(yīng)用,就算沒(méi)有配置serverTimezone,也需要重啟,因?yàn)閙ysql驅(qū)動(dòng)初始化連接時(shí),會(huì)將當(dāng)前數(shù)據(jù)庫(kù)時(shí)區(qū)緩存到一個(gè)java變量中,不重啟Java應(yīng)用它不會(huì)變。

數(shù)據(jù)庫(kù)中用timestamp還是int來(lái)存儲(chǔ)時(shí)間?

如果用int型時(shí)間綴存儲(chǔ),不管數(shù)據(jù)庫(kù)時(shí)區(qū)是啥,都不影響,因?yàn)榇鎯?chǔ)的是絕對(duì)時(shí)間,看起來(lái)完美解決了時(shí)區(qū)問(wèn)題。
但從某些角度看,這種方案只是把時(shí)區(qū)問(wèn)題從數(shù)據(jù)庫(kù)端推到應(yīng)用端去了,時(shí)區(qū)問(wèn)題將出現(xiàn)在將時(shí)間字符串轉(zhuǎn)換為時(shí)間綴的過(guò)程中,比如某程序員從api接口中拿到時(shí)間字符串后,沒(méi)考慮時(shí)區(qū),直接轉(zhuǎn)為unix時(shí)間綴,就可能出現(xiàn)時(shí)區(qū)問(wèn)題。
因此,對(duì)于不帶時(shí)區(qū)的時(shí)間串解析,一定要問(wèn)清楚這是哪個(gè)時(shí)區(qū)的時(shí)間,并在代碼中顯式指定!

另外,用int存儲(chǔ)時(shí)間還有如下3個(gè)不好的點(diǎn):

開(kāi)發(fā)人員看到這個(gè)字段后,無(wú)法一目了然的了解到這個(gè)時(shí)間綴大概是個(gè)什么時(shí)間,需要去轉(zhuǎn)換一下,會(huì)很繁瑣。像update_time這樣的字段,數(shù)據(jù)庫(kù)提供了DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP的機(jī)制,這樣在更新任何字段時(shí),update_time會(huì)自動(dòng)更新,而如果使用int存儲(chǔ),就需要程序員每次更新表時(shí),重新set這個(gè)字段,容易遺忘。由于int只有4個(gè)字節(jié),用它來(lái)存儲(chǔ)時(shí)間,會(huì)在2038年后溢出,而對(duì)于timestamp來(lái)說(shuō),MySQL將其底層存儲(chǔ)統(tǒng)一修改為8個(gè)字節(jié),相對(duì)來(lái)說(shuō)還是比較容易的。

當(dāng)然,也并不是建議不用int,這是見(jiàn)仁見(jiàn)智的,不管用timestamp還是int,都沒(méi)有致命性問(wèn)題的。

總結(jié)

timestamp本身是沒(méi)有時(shí)區(qū)問(wèn)題的,時(shí)區(qū)問(wèn)題是由于serverTimezone配置錯(cuò)誤、mysql使用CST這種混亂時(shí)區(qū)或Entity中將日期定義String類(lèi)型導(dǎo)致的。

推薦學(xué)習(xí):mysql視頻教程

贊(0)
分享到: 更多 (0)
網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)
久久se这里只有精品| 中文字幕日韩在线观看| 亚洲国产精品无码久久久| 国产精品一区二区久久精品无码 | 中文字幕精品久久| 日韩AV无码一区二区三区不卡| 国产av影片麻豆精品传媒| 四虎永久在线观看视频精品| 国产精品2018| 日产精品99久久久久久| 中文字幕一区精品| 国产精品无码永久免费888| 日韩欧美亚洲国产精品字幕久久久| 99国产精品欧美一区二区三区| 久久精品国产亚洲αv忘忧草| 国产精品禁18久久久夂久| 久久午夜无码鲁丝片午夜精品| 久久久久久久久无码精品亚洲日韩| 亚洲精品视频免费在线观看| 国产亚州精品女人久久久久久| 日韩不卡免费视频| 国产精品一区不卡| 精品亚洲国产成人av| 国产成人精品高清在线观看99| 少妇人妻精品一区二区三区| 自拍偷自拍亚洲精品情侣| 国产精品国产三级国产a| 高清免费久久午夜精品| 日韩免费视频一区二区| 国产精品99亚发布| MM1313亚洲精品无码久久| 无码精品A∨在线观看免费| 国产香蕉免费精品视频| 法国性xxxx精品hd| 久久99精品国产麻豆宅宅| 国产成人综合日韩精品无码不卡| 国产精品福利片免费看| 久久伊人精品青青草原日本| 国产乱码精品一区二区三| 久久久久国产精品三级网| 国产精品熟女福利久久AV|