mysql里CST時區(qū)的坑及解決
一、問題簡述
mysql 里 CST 時區(qū)是個非??拥母拍睿?yàn)樵?mysql 里CST既表示中國也表示美國的時區(qū)。
但是在Jdk代碼里,CST
這個字符串被理解為Central Standard Time (USA)
(GMT-6),這就是造成坑的原因。
解決辦法:
mysql 的 time_zone配置 不要用SYSTEM,因?yàn)橛昧司褪歉S system_time_zone 的值,而 system_time_zone 讀取自數(shù)據(jù)庫所在宿主機(jī)的操作系統(tǒng),常常是 CST 的值。
解決辦法是將 time_zone 改成例如 +08:00
以免造成誤解。(肯定改mysql啦,你改得了jdk源碼嗎?)。
修改方法見后文
二、查看、修改時區(qū)
1、怎么查看mysql是什么時區(qū)?
選一個即可
方法1:
命令: 連上mysql后命令行執(zhí)行 show variables like '%time_zone%'
(Navicat連上后執(zhí)行也行)
結(jié)果:
system_time_zone CST time_zone SYSTEM
看time_zone就行了,SYSTEM表示其值跟隨system_time_zone。
而system_time_zone的CST值,是表示中國呢,還是美國,是不清楚的,你直接再執(zhí)行 select now()
跟你手機(jī)的時間對比一下就清楚了。
為什么會system_time_zone和time_zone兩個? 要看哪個? 不急后面會解釋
方法2:
命令:連上mysql后命令行執(zhí)行 (Navicat連上后執(zhí)行也行)
// 分兩次執(zhí)行下面2條命令 select @@system_time_zone; select @@time_zone;
結(jié)果:
select @@system_time_zone; +--------------------+ | @@system_time_zone | +--------------------+ | CST | +--------------------+ 1 row in set (4.81 sec) mysql> select @@time_zone; +-------------+ | @@time_zone | +-------------+ | SYSTEM | +-------------+ 1 row in set (0.04 sec)
方法3:
// 查出全局時區(qū)和會話時區(qū) select @@GLOBAL.time_zone,@@SESSION.time_zone;
為什么mysql有system_time_zone和time_zone兩個
見 https://dev.mysql.com/doc/refman/8.0/en/time-zone-support.html
簡單說就是system_time_zone就是mysql服務(wù)(即mysqld)啟動的時候讀取數(shù)據(jù)庫所在宿主機(jī)的時區(qū),固定下來的值,這個值之后不再改變(除非重啟mysql服務(wù))。time_zone的值未SYSTEM,意思是它的時區(qū)跟system_time_zone一樣(注意system_time_zone的值固定下來后,數(shù)據(jù)庫宿主機(jī)的時區(qū)再改變,time_zone的值都是不變的,因?yàn)樗歉Ssystem_time_zone變量的,不是實(shí)時跟隨操作系統(tǒng)的)
存在 “為什么linux時區(qū)是對的,但是mysql的時區(qū)是錯的” 的疑惑,因?yàn)閱觤ysql服務(wù)時如果linux時區(qū)是錯的,把mysql的時區(qū)也帶錯了,只是后來你發(fā)現(xiàn)linux時區(qū)錯了于是改對了,但是你忘記啟動mysql的時候linux時區(qū)是錯的,而且你忘了是后來你才把linux的時區(qū)改正確的,所以你有這個的疑惑。我好像也有過這樣的疑惑
2、如何修改mysql的時區(qū)
永久修改,改配置文件,重啟mysql服務(wù)也有效
修改 my.cnf 文件,在 [mysqld] 節(jié)下增加 default-time-zone = '+08:00'
沒有這個文件的話,見后文,讓你的mysql啟動的時候讀取配置文件。
臨時修改,重啟mysql服務(wù)后丟失
選擇下面之一執(zhí)行即可。
- 修改會話級別定的,關(guān)閉會話后就會失效,也僅僅影響當(dāng)前會話窗口:
set time_zone='+08:00';
- 全局修改,重啟mysql服務(wù)后丟失,對所有會話生效:`set global time_zone=’+08:00’;
三、這個bug 是怎么產(chǎn)生的
下面基于mysql-connector-java-8.0.19 進(jìn)行分析,使用 com.mysql.cj.jdbc.Driver
在獲取java.sql.Connection的過程會確定時區(qū),調(diào)用如下方法
com.mysql.cj.protocol.a.NativeProtocol#configureTimezone /** * 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的time_zone讀取值
- 如果值是SYSTEM則使用system_time_zone的值
- 如果jdbc url配置了時區(qū)則使用url里的,如 jdbc:mysql://localhost:3306/test?useSSL=true&serverTimezone=Asia/Shanghai獲取的是字符串配置定的
如果獲取到CST,則因?yàn)樵趈ava里 TimeZone("CST")
表示美國中部時間,與mysql數(shù)據(jù)庫用 CST 表示中國時區(qū)的含義就不同了,bug就是這么產(chǎn)生的。
四、其他問題
1、CST究竟是美國還是中國
其實(shí)CST總共有4個含義
- China Standard Time:中國標(biāo)準(zhǔn)時間
- Central Standard Time (USA) :美國中部時間(中部就是在國土的中間的部分,例如芝加哥)
- Central Standard Time (Australia):澳大利亞中部時間
- Cuba Standard Time:古巴標(biāo)準(zhǔn)時間
就是因?yàn)檫@個含義不清,所以會有坑,體現(xiàn)在Java里就是:
// 不建議這么創(chuàng)建 TimeZone,這里的CST指的是美國中部時間,不是中國,從 TimeZone tz = TimeZone.getTimeZone("CST"); System.out.println(tz);// 可以看到偏移量是offset=-21600000,-21600000微秒=-6小時,所以實(shí)錘這里的CST指美國 // 建議創(chuàng)建 TimeZone 用 ZoneId,因?yàn)閆oneId 不允許 CST、JST 這種簡稱,能提前預(yù)防進(jìn)坑,如下 // ZoneId zoneId = ZoneId.of("CST");// 拋異常 ZoneId zoneId = ZoneId.of("GMT+8");// 明確指定,是OK的,或者 "區(qū)域/城市" 的寫法如 Asia/Shanghai TimeZone tz1 = TimeZone.getTimeZone(zoneId);
體現(xiàn)在Mysql里:
你根本不知道m(xù)ysql里的CST是中國還是美國,查出來都展示一樣
(關(guān)于system_time_zone和time_zone后續(xù)有解釋)
2、你說CST除了表示中國,也表示美國定的時區(qū),拿得出證據(jù)嗎?
是的!!! 動手做實(shí)驗(yàn)給你看 !!!
使用show variables like '%time_zone%';
查看mysql的時區(qū),目前是北京時間,查出
Variable_name Value system_time_zone CST time_zone SYSTEM
當(dāng)把操作系統(tǒng)(mysql的宿主機(jī)器的操作系統(tǒng))的時區(qū)改成東京時區(qū),并重啟mysql服務(wù)后(必須重啟服務(wù)),顯示
Variable_name Value system_time_zone JST time_zone SYSTEM
最后把操作系統(tǒng)時區(qū)改成美國中部時間(如芝加哥),并重啟mysql服務(wù)后,顯示
Variable_name Value system_time_zone CST time_zone SYSTEM
可以看到非???,無論是中國還是美國,都顯示CST,給人很大的誤解
五、其他
1、mybatis/jdbc 是怎么將日期存入到數(shù)據(jù)庫的,又是怎么查出來的
(這個實(shí)在有點(diǎn)復(fù)雜,要考慮mysql所在操作系統(tǒng)的時區(qū),也考慮跑你的mysql程序的web服務(wù)器的操作系統(tǒng)的時區(qū)。加又沒時間繼續(xù)研究了,緩一緩)
2、再次啰嗦補(bǔ)充
mysql里的 system_time_zone 和 time_zone是什么關(guān)系
system_time_zone 是mysql服務(wù)啟動時讀取宿主操作系統(tǒng)的時區(qū),當(dāng)時讀取到什么值就固定什么,是那個時刻的快照。這個值只有在被重新指定或重啟mysql服務(wù)的時候才可能變化。
time_zone就是表示所有連接到此mysql的客戶端(client)的時區(qū),不管你是什么形式的客戶端,是java通過jdbc連接的程序,還是Navicat這種圖形化的,還是命令行的,都叫客戶端。這個時區(qū)是可以被覆蓋的,可以被jdbcUrl中的時區(qū)覆蓋。
在jdbc的URL里配置了時區(qū)會怎么樣?
會使用這個時區(qū),在jdbcUrl里指定的時區(qū)優(yōu)先級最高,會覆蓋其他。
例如 jdbc:mysql://localhost:3306/test?useSSL=true&serverTimezone=Asia/Shanghai
- time_zone的SYSTEM是什么意思,是不是指 “操作系統(tǒng)” 的時區(qū)?
- time_zone的SYSTEM,是指值跟隨 system_time_zone 的值,不是說跟隨操作系統(tǒng)的值,操作系統(tǒng)的時區(qū)改了,如果 system_time_zone 不改,則 time_zone 也是不會改的(親自實(shí)驗(yàn)過了!!)
總結(jié)
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
MySQL中datetime和timestamp的區(qū)別及使用詳解
這篇文章主要介紹了MySQL中datetime和timestamp的區(qū)別及使用詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11mysql修改數(shù)據(jù)庫引擎的幾種方法總結(jié)
這篇文章主要給大家介紹了關(guān)于mysql修改數(shù)據(jù)庫引擎的相關(guān)資料,包括使用ALTERTABLE語句、更改默認(rèn)存儲引擎、使用MySQLWorkbench、導(dǎo)出和導(dǎo)入數(shù)據(jù)以及編寫腳本批量修改,每種方法都有其優(yōu)缺點(diǎn)和適用場景,需要的朋友可以參考下2024-11-11MySql中表單輸入數(shù)據(jù)出現(xiàn)中文亂碼的解決方法
這篇文章主要介紹了MySql中表單輸入數(shù)據(jù)出現(xiàn)中文亂碼的解決方法的相關(guān)資料,需要的朋友可以參考下2016-07-07linux mysql 數(shù)據(jù)庫開啟外部訪問設(shè)置指南
Linux下設(shè)置MySQL和允許外部機(jī)器訪問,具體目錄是具體情況而定,有的人是安裝了在個人目錄下,則找到對應(yīng)的目錄則可以2012-11-11