一文徹底搞懂MySQL?TimeStamp時(shí)區(qū)問題
前言
mysql中有兩個(gè)時(shí)間類型,timestamp與datetime,其中timestamp在存儲(chǔ)上是包含時(shí)區(qū)的,而datetime是不包含時(shí)區(qū)的字符串形式。而通常應(yīng)用下所說的時(shí)區(qū)問題,也指的是Java應(yīng)用使用了jdbc驅(qū)動(dòng)時(shí),存儲(chǔ)和讀取的時(shí)區(qū)不一致的問題,兩者可能會(huì)相差8小時(shí)或者13小時(shí),今天,就來徹底搞懂為什么會(huì)發(fā)生這種所謂的時(shí)區(qū)問題。
首先需要明白,JDK以版本8為界,有兩套處理日期/時(shí)間的API:Date和JSR 310,其中,Date對象是絕對時(shí)間,Date對象里存的是自格林威治時(shí)間(GMT)1970年1月1日0點(diǎn)至Date所表示時(shí)刻所經(jīng)過的毫秒數(shù)。
Date類通過SimpleDateFormat格式化出來的yyyy-MM-dd HH:mm:ss
形式的時(shí)間字符串,是帶了時(shí)區(qū)的本地時(shí)間,如果SimpleDateFormat沒有調(diào)用setTimeZone()顯示指定時(shí)區(qū),那么默認(rèn)用的是jvm運(yùn)行在的操作系統(tǒng)上的時(shí)區(qū),我們開發(fā)機(jī)上的時(shí)區(qū)基本都是GMT+8。
我們首先需要搞明白上面的眾多名詞:GMT,UTC,時(shí)間標(biāo)準(zhǔn),時(shí)區(qū),以及與時(shí)區(qū)相關(guān)的夏令時(shí)的概念,才會(huì)對本次要講的內(nèi)容做很好的理解。
時(shí)間標(biāo)準(zhǔn)
GMT:Greenwich Mean Time
格林威治標(biāo)準(zhǔn)時(shí)間 ; 格林威治皇家天文臺(tái)為了海上霸權(quán)的擴(kuò)張計(jì)劃,在十七世紀(jì)就開始進(jìn)行天體觀測。為了天文觀測,選擇了穿過英國倫敦格林威治天文臺(tái)子午儀中心的一條經(jīng)線作為零度參考線,這條線,簡稱格林威治子午線。
英國倫敦格林威治定為0°經(jīng)線開始的地方,地球每15°經(jīng)度 被分為一個(gè)時(shí)區(qū),共分為24個(gè)時(shí)區(qū),GMT、UTC、DST、CST相鄰時(shí)區(qū)相差一小時(shí);例例:中國北京位于東八區(qū),GMT時(shí)間比北京時(shí)間慢8小時(shí)。
1972年之前,格林威治時(shí)間(GMT)一直是世界時(shí)間的標(biāo)準(zhǔn)。1972年之后,GMT 不再是一個(gè)時(shí)間標(biāo)準(zhǔn)了。
UTC:世界協(xié)調(diào)時(shí)間
UTC 是現(xiàn)在全球通用的時(shí)間標(biāo)準(zhǔn),全球各地都同意將各自的時(shí)間進(jìn)行同步協(xié)調(diào)。UTC 時(shí)間是經(jīng)過平均太陽時(shí)(以格林威治時(shí)間GMT為準(zhǔn))、地軸運(yùn)動(dòng)修正后的新時(shí)標(biāo)以及以秒為單位的國際原子時(shí)所綜合精算而成。
其以原子時(shí)秒長為基礎(chǔ),精確到秒,誤差在0.9s以內(nèi), 是比GMT更為精確的世界時(shí)間。需要注意的是:協(xié)調(diào)世界時(shí)不與任何地區(qū)位置相關(guān),也不代表此刻某地的時(shí)間,所以在說明某地時(shí)間時(shí)要加上時(shí)區(qū),也就是說GMT并不等于UTC,而是等于UTC+0,只是格林尼治剛好在0時(shí)區(qū)上。GMT實(shí)際相當(dāng)于UTC+0,格林威治在0時(shí)區(qū)上。例如:中國標(biāo)準(zhǔn)時(shí)間比協(xié)調(diào)世界時(shí)早 8 小時(shí),記為:UTC+8
時(shí)區(qū)
從格林威治本初子午線起,經(jīng)度每向東或者向西間隔15°,就劃分一個(gè)時(shí)區(qū),在這個(gè)區(qū)域內(nèi),大家使用同樣的標(biāo)準(zhǔn)時(shí)間。
但實(shí)際上,為了照顧到行政上的方便,常將1個(gè)國家或1個(gè)省份劃在一起。所以時(shí)區(qū)并不嚴(yán)格按南北直線來劃分,而是按自然條件來劃分。另外:由于目前,國際上并沒有一個(gè)批準(zhǔn)各國更改時(shí)區(qū)的機(jī)構(gòu)。一些國家會(huì)由于特定原因改變自己的時(shí)區(qū)。
各個(gè)時(shí)區(qū)的標(biāo)準(zhǔn)時(shí)間都有自己的簡寫,例如美國東部標(biāo)準(zhǔn)時(shí)間叫:EST,Estern Standard Time,但是有的簡寫會(huì)重復(fù),這就是MySQL時(shí)區(qū)為什么會(huì)“錯(cuò)亂”的原因之一,后續(xù)會(huì)講
但是CST同時(shí)是四個(gè)不同時(shí)區(qū)的縮寫,存在歧義
Central Standard Time (USA) UT-6:00 美國標(biāo)準(zhǔn)時(shí)間
Central Standard Time (Australia) UT+9:30 澳大利亞標(biāo)準(zhǔn)時(shí)間
China Standard Time UT+8:00 中國標(biāo)準(zhǔn)時(shí)間
Cuba Standard Time UT-4:00 古巴標(biāo)準(zhǔn)時(shí)間
需要注意的是,Java語言中的時(shí)間戳表示從1970年1月1日 00:00:00
到現(xiàn)在所經(jīng)歷的秒數(shù),與時(shí)區(qū)無關(guān)
夏令時(shí)
時(shí)區(qū)之外,還需要了解夏令時(shí)的概念,如今中國已不再使用夏令時(shí),但是仍然有一些國家在使用夏令時(shí),夏令時(shí)(DST: Daylight Saving Time),夏季節(jié)約時(shí)間,即夏令時(shí);是為了利用夏天充足的光照而將時(shí)間調(diào)早一個(gè)小時(shí),北美、歐洲的許多國家實(shí)行夏令時(shí);
Java的Date類
可以看到,在時(shí)區(qū)設(shè)置不為空的情況下,toString方法可以以ISO時(shí)間標(biāo)準(zhǔn)表示方法輸出JDK所在地區(qū)的GMT標(biāo)準(zhǔn)時(shí)間
public String toString() { // "EEE MMM dd HH:mm:ss zzz yyyy"; BaseCalendar.Date date = normalize(); StringBuilder sb = new StringBuilder(28); int index = date.getDayOfWeek(); if (index == BaseCalendar.SUNDAY) { index = 8; } convertToAbbr(sb, wtb[index]).append(' '); // EEE convertToAbbr(sb, wtb[date.getMonth() - 1 + 2 + 7]).append(' '); // MMM CalendarUtils.sprintf0d(sb, date.getDayOfMonth(), 2).append(' '); // dd CalendarUtils.sprintf0d(sb, date.getHours(), 2).append(':'); // HH CalendarUtils.sprintf0d(sb, date.getMinutes(), 2).append(':'); // mm CalendarUtils.sprintf0d(sb, date.getSeconds(), 2).append(' '); // ss TimeZone zi = date.getZone(); if (zi != null) { sb.append(zi.getDisplayName(date.isDaylightTime(), TimeZone.SHORT, Locale.US)); // zzz } else { sb.append("GMT"); } sb.append(' ').append(date.getYear()); // yyyy return sb.toString(); }
getTime()方法可以獲取毫秒數(shù),返回值類型是long
new Date().getTime();
示例
Date date=new Date(); System.out.println(date.toString()); System.out.println(date.toLocaleString());//棄用 System.out.println(date.toGMTString());//棄用 //Thu Jul 14 23:55:10 CST 2022 //2022-7-14 23:55:10 //14 Jul 2022 15:55:10 GMT
JDK8之前,Java對時(shí)區(qū)和偏移量都使用TimeZone類來表示的。Java不可以使用UTC+8。
getDefault()方法
sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=31,lastRule=null]
時(shí)區(qū)轉(zhuǎn)換
可以使用SimpleDateFormat對時(shí)間在不同時(shí)區(qū)間進(jìn)行轉(zhuǎn)換
//Java 中的 UTC和 GMT jshell> Date now = new Date() now ==> Thu Oct 30 16:59:27 CST 2021 jshell> import java.text.* jshell> DateFormat df = DateFormat.getInstance() df ==> java.text.SimpleDateFormat@ca92313f jshell> df.setTimeZone(TimeZone.getTimeZone("GMT+8")) jshell> df.format(now) $5 ==> "2021/10/30 下午4:59 jshell> df.setTimeZone(TimeZone.getTimeZone("UTC+8")) jshell> df.format(now) $7 ==> "2021/10/30 上午8:59" jshell> df.setTimeZone(TimeZone.getTimeZone("UTC")) jshell> df.format(now) $9 ==> "2021/10/30 上午8:59" jshell> df.setTimeZone(TimeZone.getTimeZone("GMT")) jshell> df.format(now) $11 ==> "2021/10/30 上午8:59" jshell> df.setTimeZone(TimeZone.getTimeZone("abc")) jshell> df.format(now) $13 ==> "2021/10/30 上午8:59" //從上面的輸出可以看出,在當(dāng)前使用的 Java 版本中,設(shè)定時(shí)區(qū)偏移量時(shí),是不能使用 UTC的,只能使用 GMT!另外,使用非法的時(shí)區(qū) ID 時(shí),會(huì)將時(shí)區(qū)設(shè)定為零時(shí)區(qū)。 //GMT+8和 Asia/Shanghai //通常我們會(huì)將日期表示成格式化的字符串,如 2021-10-31 10:34:00,然后將其解析為日期類型并使用,需要打印時(shí)再將日期類型轉(zhuǎn)換為字符串,例如: jshell> import java.text.* jshell> SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") sdf ==> java.text.SimpleDateFormat@4f76f1a0 jshell> DateFormat df = DateFormat.getInstance() df ==> java.text.SimpleDateFormat@ca92313f jshell> df.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")) jshell> df.format(sdf.parse("2021-10-31 10:34:00")) $6 ==> "2021/10/31 上午10:34" jshell> df.setTimeZone(TimeZone.getTimeZone("GMT+8")) jshell> df.format(sdf.parse("2021-10-31 10:34:00")) $8 ==> "2021/10/31 上午10:34"
Java中的夏令時(shí)問題
前面談到了夏令時(shí)問題,所以會(huì)有這樣的疑問:GMT+8東八區(qū)時(shí)間一定是地區(qū)時(shí)間Asia/Shanghai嗎?下面是夏令時(shí)問題。
jshell> df.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")) jshell> df.format(sdf.parse("1986-05-04 02:00:00")) $10 ==> "1986/5/4 上午3:00" jshell> df.setTimeZone(TimeZone.getTimeZone("GMT+8")) jshell> df.format(sdf.parse("1986-05-04 02:00:00")) $12 ==> "1986/5/4 上午2:00"
當(dāng)我們使用1986-05-04 02:00:00
這個(gè)時(shí)間戳?xí)r:
時(shí)區(qū)設(shè)置為 GMT+8,字符串轉(zhuǎn)日期,再將日期轉(zhuǎn)成字符串,得到的日期和時(shí)間跟輸入的時(shí)間戳是一樣的,時(shí)區(qū)設(shè)置為 Asia/Shanghai,經(jīng)過轉(zhuǎn)換之后,輸出的時(shí)間變成了上午三點(diǎn),比輸入的上午兩點(diǎn)多了一小時(shí),這是因?yàn)橄牧顣r(shí)造成的。
由此可以看到,僅僅使用偏移量無法準(zhǔn)確的描述一個(gè)地區(qū)的時(shí)間,因此Java推出了JSR 310使用時(shí)區(qū)來描述當(dāng)?shù)氐臅r(shí)間。
TIPS: GMT-8和Asia/Shanghai
- GMT-8是東八區(qū),北京時(shí)間和東八區(qū)一致。
- Asia/Shanghai是已地區(qū)命名的地區(qū)標(biāo)準(zhǔn)時(shí),這個(gè)地區(qū)標(biāo)準(zhǔn)時(shí)會(huì)兼容歷史各個(gè)時(shí)間節(jié)點(diǎn)。中國1986-1991年實(shí)行夏令時(shí),夏天和冬天差1個(gè)小時(shí),Asia/Shanghai會(huì)兼容這個(gè)時(shí)間段。1992年以后,在中國,GMT-8和Asia/Shanghai是一樣的時(shí)間,1986-1991之間,夏天會(huì)有一小時(shí)時(shí)差。
JSR310
時(shí)區(qū)和偏移量在概念和實(shí)際作用上是有較大區(qū)別的,主要體現(xiàn)在:UTC偏移量僅僅記錄了偏移的小時(shí)分鐘而已,除此之外無地理/時(shí)區(qū)含義。比如:+08:00的意思是比UTC時(shí)間早8小時(shí),沒有地理/時(shí)區(qū)含義,相應(yīng)的-03:30代表的意思僅僅是比UTC時(shí)間晚3個(gè)半小時(shí)。
時(shí)區(qū)是特定于地區(qū)而言的,它和地理上的地區(qū)(包括規(guī)則)強(qiáng)綁定在一起。比如整個(gè)中國都叫東八區(qū),紐約在西五區(qū)等等。
中國沒有夏令時(shí),所有東八區(qū)對應(yīng)的偏移量永遠(yuǎn)是+8;紐約有夏令時(shí),因此它的偏移量可能是-4也可能是-5,因此規(guī)定了時(shí)區(qū)后,我們可以根據(jù)夏令時(shí)準(zhǔn)確的計(jì)算當(dāng)?shù)貢r(shí)間
ZoneId
在JDK 8之前,Java使用java.util.TimeZone來表示時(shí)區(qū)。而在JDK 8里分別使用了ZoneId表示時(shí)區(qū),ZoneOffset表示UTC的偏移量。
ZoneId在系統(tǒng)內(nèi)是唯一的,它共包含三種類型的ID:
最簡單的ID類型:ZoneOffset,它由’Z’和以’+‘或’-'開頭的id組成。如:Z、+18:00、-18:00
另一種類型的ID是帶有某種前綴形式的偏移樣式ID,例如’GMT+2’或’UTC+01:00’??勺R(shí)別的(合法的)前綴是’UTC’, ‘GMT’和’UT’
第三種類型是基于區(qū)域的ID(推薦使用)?;趨^(qū)域的ID必須包含兩個(gè)或多個(gè)字符,且不能以’UTC’、‘GMT’、‘UT’ '+‘或’-'開頭?;趨^(qū)域的id由配置定義好的,如Europe/Paris
System.out.println(ZoneId.systemDefault());//Asia/Shanghai
ZoneId底層使用的也是TimeZone
/** * Gets the system default time-zone. * <p> * This queries {@link TimeZone#getDefault()} to find the default time-zone * and converts it to a {@code ZoneId}. If the system default time-zone is changed, * then the result of this method will also change. * * @return the zone ID, not null * @throws DateTimeException if the converted zone ID has an invalid format * @throws ZoneRulesException if the converted zone region ID cannot be found */ public static ZoneId systemDefault() { return TimeZone.getDefault().toZoneId(); }
ZoneOffset
時(shí)區(qū)偏移量是時(shí)區(qū)與格林威治/UTC之間的時(shí)間差。這通常是固定的小時(shí)數(shù)和分鐘數(shù)。世界不同的地區(qū)有不同的時(shí)區(qū)偏移量。在ZoneId類中捕獲關(guān)于偏移量如何隨一年的地點(diǎn)和時(shí)間而變化的規(guī)則(主要是夏令時(shí)規(guī)則),所以繼承自ZoneId。
JSR 310對日期、時(shí)間進(jìn)行了分開表示(LocalDate、LocalTime、LocalDateTime),對本地時(shí)間和帶時(shí)區(qū)的時(shí)間進(jìn)行了分開管理。LocalXXX表示本地時(shí)間,也就是說是當(dāng)前JVM所在時(shí)區(qū)的時(shí)間;ZonedXXX表示是一個(gè)帶有時(shí)區(qū)的日期時(shí)間,它們能非常方便的互相完成轉(zhuǎn)換。
// 本地日期/時(shí)間 System.out.println("================本地時(shí)間================"); System.out.println(LocalDate.now()); System.out.println(LocalTime.now()); System.out.println(LocalDateTimenow()); // 時(shí)區(qū)時(shí)間 System.out.println("================帶時(shí)區(qū)的時(shí)間ZonedDateTime================"); System.out.println(ZonedDateTime.now()); // 使用系統(tǒng)時(shí)區(qū) System.out.println(ZonedDateTime.now(ZoneId.of("America/New_York"))); // 自己指定時(shí)區(qū) System.out.println(ZonedDateTime.now(Clock.systemUTC())); // 自己指定時(shí)區(qū) System.out.println("================帶時(shí)區(qū)的時(shí)間OffsetDateTime================"); System.out.println(OffsetDateTime.now()); // 使用系統(tǒng)時(shí)區(qū) System.out.println(OffsetDateTime.now(ZoneId.of("America/New_York"))); // 自己指定時(shí)區(qū) System.out.println(OffsetDateTime.now(Clock.systemUTC())); // 自己指定時(shí)區(qū)
MySQL中的時(shí)區(qū)
Java的時(shí)間實(shí)際上是經(jīng)過jdbc轉(zhuǎn)換為字符串的形式寫入數(shù)據(jù)庫,查詢時(shí)同理以字符串的形式轉(zhuǎn)換為Java Date類型,所以需要分析jdbc驅(qū)動(dòng),發(fā)現(xiàn)實(shí)際上在調(diào)用jdbc的setTimestamp()方法時(shí),com.mysql.cj.jdbc.ClientPreparedStatement#setTimestamp(),這里面會(huì)根據(jù)serverTimezone指定的時(shí)區(qū),將對應(yīng)的Timestamp對象轉(zhuǎn)換為serverTimezone指定時(shí)區(qū)的本地時(shí)間字符串。如果jdbc連接串的serverTimezone配置的時(shí)區(qū)和數(shù)據(jù)庫配置的時(shí)區(qū)不一致時(shí),查詢的時(shí)間的時(shí)區(qū)以serverTimezone為準(zhǔn)。
查看MySQL數(shù)據(jù)庫當(dāng)前時(shí)區(qū):
SHOW variables LIKE '%time_zone%'; Variable_name |Value | ----------------+------+ system_time_zone|CST | time_zone |SYSTEM|
發(fā)現(xiàn)MySQL默認(rèn)配置的是CST,然而對于CST,MySQL和Java卻對其有不同的解讀。
- MySQL中,CST表示的是:中國標(biāo)準(zhǔn)時(shí)間(UTC+08:00)
- Java中,CST表示的是:美國標(biāo)準(zhǔn)時(shí)間(UTC-05:00或UTC-06:00)
所以出現(xiàn)時(shí)區(qū)問題的根本原因是由于Java和MySQL服務(wù)端對CST時(shí)區(qū)的不同解讀,最終導(dǎo)致了Java與MySQL時(shí)間不一致的問題。
比如一個(gè)時(shí)間值是東8區(qū)的2020-02-23 08:00:00
,在Java代碼中存儲(chǔ)的是Date類型的絕對時(shí)間,由于serverTimeZone未配置,讀取MySQL默認(rèn)的time_zone為CST(中國標(biāo)準(zhǔn)時(shí)間),由于Java認(rèn)為CST是美國標(biāo)準(zhǔn)時(shí)間的縮寫,因此MySQL驅(qū)動(dòng)會(huì)將這個(gè)Date對象轉(zhuǎn)為字符串類型的2020-02-22 19:00:00
,將sql發(fā)送給MySQL,然后MySQL數(shù)據(jù)庫接收到這個(gè)時(shí)間字符串2020-02-22 19:00:00
后,由于數(shù)據(jù)庫時(shí)區(qū)配置是CST中國標(biāo)準(zhǔn)時(shí)間東8區(qū),數(shù)據(jù)庫會(huì)認(rèn)為是東8區(qū)的2020-02-22 19:00:00
。查詢時(shí)MySQL返回了東8區(qū)的時(shí)間字符串2020-02-22 19:00:00
,Java拿到后認(rèn)為是CST美國標(biāo)準(zhǔn)時(shí)間的2020-02-22 19:00:00
,最后以UTC+8標(biāo)準(zhǔn)時(shí)間輸出到控制臺(tái),就顯示為2020-02-23 08:00:00
,因此,當(dāng)連接串不指定時(shí)區(qū)時(shí),也不會(huì)出現(xiàn)問題。
但是,如果在這種情況下,我們把數(shù)據(jù)庫連接串的時(shí)區(qū)配置為真正的中國時(shí)區(qū)Aisa\Shanghai,那么,問題就出現(xiàn)了,根據(jù)上面的解釋,Java應(yīng)用最后應(yīng)該會(huì)輸出的時(shí)間會(huì)少13個(gè)小時(shí),這就是出現(xiàn)時(shí)區(qū)問題的根本原因。
以下是jdbc獲取時(shí)區(qū)配置的代碼:
/** * Configures the client's timezone if required. * * @throws CJException * if the timezone the server is configured to use can't be * mapped to a Java timezone. */ public void configureTimezone() { String configuredTimeZoneOnServer = this.serverSession.getServerVariable("time_zone"); if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) { configuredTimeZoneOnServer = this.serverSession.getServerVariable("system_time_zone"); } String canonicalTimezone = getPropertySet().getStringProperty(PropertyKey.serverTimezone).getValue(); if (configuredTimeZoneOnServer != null) { // user can override this with driver properties, so don't detect if that's the case if (canonicalTimezone == null || StringUtils.isEmptyOrWhitespaceOnly(canonicalTimezone)) { try { canonicalTimezone = TimeUtil.getCanonicalTimezone(configuredTimeZoneOnServer, getExceptionInterceptor()); } catch (IllegalArgumentException iae) { throw ExceptionFactory.createException(WrongArgumentException.class, iae.getMessage(), getExceptionInterceptor()); } } } if (canonicalTimezone != null && canonicalTimezone.length() > 0) { this.serverSession.setServerTimeZone(TimeZone.getTimeZone(canonicalTimezone)); // // The Calendar class has the behavior of mapping unknown timezones to 'GMT' instead of throwing an exception, so we must check for this... // if (!canonicalTimezone.equalsIgnoreCase("GMT") && this.serverSession.getServerTimeZone().getID().equals("GMT")) { throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("Connection.9", new Object[] { canonicalTimezone }), getExceptionInterceptor()); } } this.serverSession.setDefaultTimeZone(this.serverSession.getServerTimeZone()); }
如果MySQL時(shí)區(qū)和serverTimezone配置不一樣呢,這種情況會(huì)發(fā)生時(shí)區(qū)問題嗎?比如把數(shù)據(jù)庫時(shí)區(qū)配置為+9:00時(shí)區(qū),然后把jdbc的url上的serverTimezone配置為與數(shù)據(jù)庫不一致的GMT+8時(shí)區(qū)時(shí),會(huì)發(fā)生時(shí)區(qū)問題嗎?
比如一個(gè)時(shí)間值是東8區(qū)的2020-02-23 08:00:00
,在Java代碼中存儲(chǔ)的是Date類型的絕對時(shí)間,由于serverTimeZone配置的是東8區(qū),MySQL驅(qū)動(dòng)會(huì)將這個(gè)Date對象轉(zhuǎn)為字符串類型的2020-02-23 08:00:00
,將sql發(fā)送給MySQL,然后MySQL數(shù)據(jù)庫接收到這個(gè)時(shí)間字符串2020-02-23 08:00:00
后,由于數(shù)據(jù)庫時(shí)區(qū)配置是東9區(qū),它會(huì)以東9區(qū)解析這個(gè)時(shí)間字符串,這時(shí)數(shù)據(jù)庫保存的時(shí)間是東9區(qū)的2020-02-23 08:00:00
,也就是東8區(qū)的2020-02-23 07:00:00
,**這里需要注意的是,時(shí)間實(shí)際上已經(jīng)出現(xiàn)了問題,保存的時(shí)間偏差了1個(gè)小時(shí)。**但是查詢時(shí)MySQL返回了東9區(qū)的時(shí)間字符串2020-02-23 08:00:00
,而Java又認(rèn)為這是東8區(qū)的2020-02-23 08:00:00
,和存儲(chǔ)時(shí)一致。也是沒有時(shí)區(qū)的問題。
但是,如果在這種情況下,我們把數(shù)據(jù)庫連接串的時(shí)區(qū)也配置為+9:00時(shí)區(qū),那么,問題就出現(xiàn)了,根據(jù)上面的解釋,Java應(yīng)用最后應(yīng)該會(huì)輸出的時(shí)間會(huì)多1個(gè)小時(shí),這就是出現(xiàn)時(shí)區(qū)問題的根本原因。
總結(jié)
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)呢?對于unix時(shí)間綴,就不會(huì)有此問題,因?yàn)樗墙^對時(shí)間。而如果某些特殊原因,一定要使用時(shí)間字符串,最好使用ISO8601
規(guī)范那種帶時(shí)區(qū)的時(shí)間串,比如:2020-02-23T08:00:00+08:00
。
2、Mybatis中Entity定義要與數(shù)據(jù)庫定義一致,數(shù)據(jù)庫中是timestamp,那么Entity中要定義為Date對象,因?yàn)镸ySQL驅(qū)動(dòng)在執(zhí)行sql時(shí),會(huì)自動(dòng)根據(jù)serverTimezone配置幫你轉(zhuǎn)換為數(shù)據(jù)庫時(shí)區(qū)的時(shí)間串,如果你自己來轉(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ù)庫的時(shí)區(qū)不一致,就會(huì)出現(xiàn)時(shí)區(qū)問題。
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ù)庫時(shí)區(qū)修改后,jdbc的serverTimezone也要跟著修改,并重啟Java應(yīng)用,就算沒有配置serverTimezone,也需要重啟,因?yàn)镸ySQL驅(qū)動(dòng)初始化連接時(shí),會(huì)將當(dāng)前數(shù)據(jù)庫時(shí)區(qū)緩存到一個(gè)java變量中,不重啟Java應(yīng)用它不會(huì)變。
到此這篇關(guān)于一文徹底搞懂MySQL TimeStamp時(shí)區(qū)問題的文章就介紹到這了,更多相關(guān)MySQL TimeStamp時(shí)區(qū)問題內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- IDEA連接mysql時(shí)區(qū)問題解決
- Mysql查看數(shù)據(jù)庫時(shí)區(qū)并設(shè)置時(shí)區(qū)的方法
- MySQL時(shí)區(qū)差8小時(shí)的多種問題解決方法
- MySQL數(shù)據(jù)時(shí)區(qū)問題以及datetime和timestamp類型存儲(chǔ)的差異
- 關(guān)于mysql的時(shí)區(qū)問題
- MySQL中的時(shí)區(qū)設(shè)置方式
- MySQL修改時(shí)區(qū)的方法圖文詳解
- MySQL timestamp與時(shí)區(qū)問題的解決
- 解決MySQL時(shí)區(qū)日期時(shí)差8個(gè)小時(shí)的問題
- Mysql時(shí)區(qū)的幾種問題及解決方法
相關(guān)文章
MySQL數(shù)據(jù)庫主從復(fù)制延時(shí)超長的解決方法
這篇文章主要給大家介紹了關(guān)于MySQL數(shù)據(jù)庫主從復(fù)制延時(shí)超長的解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用MySQL具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06利用Mysql定時(shí)+存儲(chǔ)過程創(chuàng)建臨時(shí)表統(tǒng)計(jì)數(shù)據(jù)的過程
這篇文章主要介紹了利用Mysql定時(shí)+存儲(chǔ)過程創(chuàng)建臨時(shí)表統(tǒng)計(jì)數(shù)據(jù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03MySQL如何處理InnoDB并發(fā)事務(wù)中的間隙鎖死鎖
這篇文章主要為大家介紹了MySQL如何處理InnoDB并發(fā)事務(wù)中的間隙鎖死鎖,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10mysql關(guān)聯(lián)查詢速度慢的問題及解決
這篇文章主要介紹了mysql關(guān)聯(lián)查詢速度慢的問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03MySQL 5.6主從報(bào)錯(cuò)的實(shí)戰(zhàn)記錄
這篇文章主要給大家介紹了關(guān)于MySQL 5.6主從報(bào)錯(cuò)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03淺談MySQL存儲(chǔ)引擎選擇 InnoDB與MyISAM的優(yōu)缺點(diǎn)分析
MyISAM 是MySQL中默認(rèn)的存儲(chǔ)引擎,一般來說不是有太多人關(guān)心這個(gè)東西。決定使用什么樣的存儲(chǔ)引擎是一個(gè)很tricky的事情,但是還是值我們?nèi)パ芯恳幌拢@里的文章只考慮 MyISAM 和InnoDB這兩個(gè),因?yàn)檫@兩個(gè)是最常見的2013-06-06mysqlreport顯示Com_中change_db占用比例高的問題的解決方法
最近公司的mysql服務(wù)器經(jīng)常出現(xiàn)阻塞狀態(tài)。動(dòng)不動(dòng)就重啟,給用戶訪問帶來了相當(dāng)?shù)牟槐恪?/div> 2009-05-05Mysql保持現(xiàn)有內(nèi)容在后面增加內(nèi)容的sql語句
這篇文章主要介紹了Mysql保持現(xiàn)有內(nèi)容在后面增加內(nèi)容的sql語句,需要的朋友可以參考下2017-05-05MySQL數(shù)據(jù)庫如何導(dǎo)入導(dǎo)出(備份還原)
這篇文章主要介紹了MySQL數(shù)據(jù)庫如何導(dǎo)入導(dǎo)出(備份還原),需要的朋友可以參考下2015-10-10最新評論