JDBC与mysql同为CST时区导致数据库时间和客户端时间差13或者14小时
摘要
線上排查問題時候碰到一個奇怪的問題,代碼中讀取一天的記錄。代碼中設置時間是從零點到夜里二十四點。但是讀取出來的記錄的開始是既然是從13點開始的。然后看了JDBC的源碼發現主要原因是Mysql的CST時間與Java中CST時間是不一樣的,下面給出問題的排查過程。
情景再現
1、代碼中用的java.util.Date類型、換成TimeStamp類型也沒有解決問題
2、數據庫中用的TimeStamp類型
3、mysql 版本5.6.x
4、jdk版本1.8
5、mysql-connector-java 8.0.13
我的數據庫時區信息如下:
CST時間
CST時間有四種解釋,所以不同項目中可能代碼的意義不一樣,比如Mysql和Java。這也是這次錯誤的主要原因。Java和Mysql協商時區時把Mysql的CST時間當成了美國中部時間既UTC-5(美國從“3月11日”至“11月7日”實行夏令時,美國中部時間改為 UTC-05:00,其他時候是UTC-06:00)。我們國家是UTC+08:00 時區,所以差了13個小時(13小時還是14小時,取決于你傳遞給數據庫的時間),
- 美國中部時間 Central Standard Time (USA) UTC-05:00 / UTC-06:00
- 澳大利亞中部時間 Central Standard Time (Australia) UTC+09:30
- 中國標準時 China Standard Time UTC+08:00
- 古巴標準時 Cuba Standard Time UTC-04:00
CST in Java
SimpleDateFormat f1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); f1.setTimeZone(TimeZone.getTimeZone("CST")); System.out.println(f1.parse("2015-09-01 00:00:00"));上面代碼的輸出如下:
Tue Sep 01 13:00:00 CST 2015比實際的時間多了13個小時。所以你在Java中的時間被認為是UTC-5時間,而數據庫任務時間是UTC-8時間。這就是我們上面錯誤的原因。
源碼分析
關鍵的代碼在ConnectionImpl類中,代碼如下,已經去掉一些無關重要的代碼
private void initializePropsFromServer() throws SQLException {String connectionInterceptorClasses = this.propertySet.getStringProperty(PropertyKey.connectionLifecycleInterceptors).getStringValue();this.session.setSessionVariables();this.session.loadServerVariables(this.getConnectionMutex(), this.dbmd.getDriverVersion()); //查詢數據庫一些重要的系統配置this.autoIncrementIncrement = this.session.getServerSession().getServerVariable("auto_increment_increment", 1);this.session.buildCollationMapping();this.session.getProtocol().initServerSession();// 初始化會話,協商時區代碼就在里面checkTransactionIsolationLevel();this.session.checkForCharsetMismatch();this.session.configureClientCharacterSet(false);handleAutoCommitDefaults();其中this.session.loadServerVariables(this.getConnectionMutex(), this.dbmd.getDriverVersion());查詢Mysql重要配置。比如時區。具體查詢的信息如下:
StringBuilder queryBuf = new StringBuilder(versionComment).append("SELECT");queryBuf.append(" @@session.auto_increment_increment AS auto_increment_increment");queryBuf.append(", @@character_set_client AS character_set_client");queryBuf.append(", @@character_set_connection AS character_set_connection");queryBuf.append(", @@character_set_results AS character_set_results");queryBuf.append(", @@character_set_server AS character_set_server");queryBuf.append(", @@collation_server AS collation_server");queryBuf.append(", @@collation_connection AS collation_connection");queryBuf.append(", @@init_connect AS init_connect");queryBuf.append(", @@interactive_timeout AS interactive_timeout");if (!versionMeetsMinimum(5, 5, 0)) {queryBuf.append(", @@language AS language");}queryBuf.append(", @@license AS license");queryBuf.append(", @@lower_case_table_names AS lower_case_table_names");queryBuf.append(", @@max_allowed_packet AS max_allowed_packet");queryBuf.append(", @@net_write_timeout AS net_write_timeout");if (!versionMeetsMinimum(8, 0, 3)) {queryBuf.append(", @@query_cache_size AS query_cache_size");queryBuf.append(", @@query_cache_type AS query_cache_type");}queryBuf.append(", @@sql_mode AS sql_mode");queryBuf.append(", @@system_time_zone AS system_time_zone");queryBuf.append(", @@time_zone AS time_zone");if (versionMeetsMinimum(8, 0, 3) || (versionMeetsMinimum(5, 7, 20) && !versionMeetsMinimum(8, 0, 0))) {queryBuf.append(", @@transaction_isolation AS transaction_isolation");} else {queryBuf.append(", @@tx_isolation AS transaction_isolation");}queryBuf.append(", @@wait_timeout AS wait_timeout");this.session.getProtocol().initServerSession();這就是協商時區的代碼,也是我們重點需要關注的代碼。如下
public void configureTimezone() {// 獲取mysql時區配置,結果是SYSTEMString configuredTimeZoneOnServer = this.serverSession.getServerVariable("time_zone");//因為我的數據庫time_zone是SYSTEM,所以就使用system_time_zone作為數據的時區,如一開始mysql查詢結果,時區為CST,既configuredTimeZoneOnServer=CSTif ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) {configuredTimeZoneOnServer = this.serverSession.getServerVariable("system_time_zone");}// 從配置中查找你對時區的配置,如果你沒有這里為null。getPropertySet()就保存了你的數據庫用戶名、密碼、字符編碼啊等你在url鏈接中設置的屬性String canonicalTimezone = getPropertySet().getStringProperty(PropertyKey.serverTimezone).getValue();//因為我沒有配置serverTimezone屬性,所以canonicalTimezone==nullif (configuredTimeZoneOnServer != null) {// user can override this with driver properties, so don't detect if that's the caseif (canonicalTimezone == null || StringUtils.isEmptyOrWhitespaceOnly(canonicalTimezone)) {try { //協商java中的時區,因為Mysql為CST,所以這里也是CSTcanonicalTimezone = TimeUtil.getCanonicalTimezone(configuredTimeZoneOnServer, getExceptionInterceptor());} catch (IllegalArgumentException iae) {throw ExceptionFactory.createException(WrongArgumentException.class, iae.getMessage(), getExceptionInterceptor());}}}if (canonicalTimezone != null && canonicalTimezone.length() > 0) { //將剛剛得到的Java的時區設置到會話中this.serverSession.setServerTimeZone(TimeZone.getTimeZone(canonicalTimezone));}this.serverSession.setDefaultTimeZone(this.serverSession.getServerTimeZone());}再來看一下,如果給sql語句的占位符中傳遞值的時候代碼
this.tsdf = TimeUtil.getSimpleDateFormat(this.tsdf, "''yyyy-MM-dd HH:mm:ss", targetCalendar,targetCalendar != null ? null : this.session.getServerSession().getDefaultTimeZone());StringBuffer buf = new StringBuffer();buf.append(this.tsdf.format(x));if (this.session.getServerSession().getCapabilities().serverSupportsFracSecs()) {buf.append('.');buf.append(TimeUtil.formatNanos(x.getNanos(), 6));}buf.append('\'');setValue(parameterIndex, buf.toString(), MysqlType.TIMESTAMP);代碼中把Date或者TimeStamp轉換為String,而且用了協商的時區。
分析到這里,問題基本上說清楚了。那么我們如何解決這個問題呢?
總結
1、數據庫時區最好不要設置成CST,以免出現上面的錯誤
2、當數據庫中的時間用的是時間類型時候,Java中可以用String,但是不適應做國際化
3、在數據庫連接字符串中設置時區。如下(推薦的方式):
作者:sunny4handsome
鏈接:https://www.jianshu.com/p/c37b11472151
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。
總結
以上是生活随笔為你收集整理的JDBC与mysql同为CST时区导致数据库时间和客户端时间差13或者14小时的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《Spring设计思想》AOP设计思想与
- 下一篇: 敢不敢模拟超过 5 万的并发用户?