Java與MySQL導(dǎo)致的時(shí)間不一致問題分析
時(shí)間戳與時(shí)區(qū)的關(guān)系
時(shí)間戳一般指的是Unix 時(shí)間戳:是從1970年1月1日(UTC/GMT的午夜)開始所經(jīng)過的秒數(shù),不考慮閏秒。
那么和時(shí)區(qū)又有什么關(guān)系呢?
public static void main(String[] args) throws ParseException { TimeZone bjTimeZone = TimeZone.getTimeZone("Asia/Shanghai"); TimeZone utcTimeZone = TimeZone.getTimeZone("UTC"); // 時(shí)間戳在不同時(shí)區(qū)下的日期 Date date = new Date(0L); System.out.println("時(shí)間戳 0 對(duì)應(yīng)的系統(tǒng)時(shí)間:" + date); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); sdf.setTimeZone(bjTimeZone); System.out.println("時(shí)間戳 0 在東八時(shí)區(qū)下表達(dá)的時(shí)間:" + sdf.format(date)); sdf.setTimeZone(utcTimeZone); System.out.println("時(shí)間戳 0 在UTC時(shí)區(qū)下表達(dá)的時(shí)間:" + sdf.format(date)); // 日期在不同時(shí)區(qū)下的時(shí)間戳 sdf.setTimeZone(bjTimeZone); System.out.println("2024-02-25 00:00:00 在東八時(shí)區(qū)下的時(shí)間戳:" + sdf.parse("2024-02-25 00:00:00").getTime()); sdf.setTimeZone(utcTimeZone); System.out.println("2024-02-25 00:00:00 在UTC時(shí)區(qū)下的時(shí)間戳:" + sdf.parse("2024-02-25 00:00:00").getTime()); }
時(shí)間戳 0 對(duì)應(yīng)的系統(tǒng)時(shí)間:Thu Jan 01 08:00:00 CST 1970
時(shí)間戳 0 在東八時(shí)區(qū)下表達(dá)的時(shí)間:1970-01-01 08:00:00
時(shí)間戳 0 在UTC時(shí)區(qū)下表達(dá)的時(shí)間:1970-01-01 00:00:00
2024-02-25 00:00:00 在東八時(shí)區(qū)下的時(shí)間戳:1708790400
2024-02-25 00:00:00 在UTC時(shí)區(qū)下的時(shí)間戳:1708819200
- 一個(gè)時(shí)間戳在不同時(shí)區(qū)下所表達(dá)的時(shí)間是不一樣的。
- 一個(gè)日期在不同時(shí)區(qū)下的時(shí)間戳是不同的。東八時(shí)區(qū)(北京時(shí)間)與 UTC 世界協(xié)調(diào)時(shí)間相差了八小時(shí)??梢酝ㄟ^時(shí)間戳的差值進(jìn)行計(jì)算驗(yàn)證:
(1708819200 - 1708790400) / 60 / 60 = 8
- 一個(gè)日期在不同時(shí)區(qū)下的時(shí)間戳的差值:時(shí)區(qū)間的偏移量
- 兩個(gè)時(shí)區(qū)不同,但時(shí)間相同的日期表達(dá)的意義也不一樣,就如北京八點(diǎn)與美國(guó)八點(diǎn)的區(qū)別
查詢、修改 Java 程序使用的時(shí)區(qū)
public static void main(String[] args) { // 查看默認(rèn)時(shí)區(qū)與時(shí)區(qū)ID TimeZone defaultTimeZone = TimeZone.getDefault(); ZoneId systemDefaultZoneId = ZoneId.systemDefault(); // sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=29,lastRule=null] System.out.println(defaultTimeZone); // Asia/Shanghai System.out.println(systemDefaultZoneId); // 設(shè)置默認(rèn)時(shí)區(qū) TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("UTC"))); // sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null] System.out.println(TimeZone.getDefault()); }
查詢、修改 MySQL 數(shù)據(jù)庫使用的時(shí)區(qū)
查詢:
show global variables like "%time_zone%";
Variable_name | Value |
---|---|
system_time_zone(系統(tǒng)時(shí)區(qū)) | UTC |
time_zone(會(huì)話時(shí)區(qū)) | SYSTEM |
系統(tǒng)時(shí)區(qū):UTC,即比東八時(shí)區(qū)慢8個(gè)小時(shí)??梢酝ㄟ^
SELECT NOW()
查詢當(dāng)前時(shí)間對(duì)比 PC 上的時(shí)間驗(yàn)證:MYSQL: 2024-02-24 19:07:31 / PC: 2024-02-25 03:07:31
。該值讀取的就是 MySQL 服務(wù)所在的操作系統(tǒng)上使用的時(shí)區(qū),以 Linux 系統(tǒng)為例,可通過date -R
查看:Sat, 24 Feb 2024 19:31:56 +0000。
會(huì)話時(shí)區(qū):采用系統(tǒng)時(shí)區(qū),即 UTC。
修改:系統(tǒng)時(shí)區(qū):修改系統(tǒng)時(shí)區(qū),即修改 MySQL 服務(wù)所在服務(wù)器的時(shí)區(qū),以 Linux 操作系統(tǒng)為例:cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
,將時(shí)區(qū)文件 copy 到 etc 目錄下且命名為 localtime。
會(huì)話時(shí)區(qū):
- SQL 的方式:
set global time_zone = '+8:00'
orset global time_zone = 'Asia/Shanghai'
- 修改
my.cnf
配置文件中的default-time-zone=Asia/Shanghai
屬性
JDBC 讀取并設(shè)置 MySQL 服務(wù)使用的時(shí)區(qū)的流程
以mysql-connector-j-8.0.33.jar
為例:
com.mysql.cj.protocol.a.NativeProtocol.configureTimeZone
設(shè)置MySQL服務(wù)使用的時(shí)區(qū)的流程:
- 優(yōu)先讀取
connectionTimeZone
或serverTimezone
jdbc 屬性作為會(huì)話使用的時(shí)區(qū) - 若配置的屬性為
SERVER
,則在第一次調(diào)用ServerSession.getSessionTimeZone()
時(shí)讀取數(shù)據(jù)庫中配置的時(shí)區(qū) - 若沒有配置則以本地服務(wù)器的時(shí)區(qū)作為 MySQL 服務(wù)器的時(shí)區(qū)
JDBC 如何應(yīng)用的時(shí)區(qū)
說明:當(dāng)前Java 程序東八時(shí)區(qū),MySQL服務(wù) UTC 時(shí)區(qū)。
存儲(chǔ)日期數(shù)據(jù)時(shí),com.mysql.cj.protocol.a.SqlTimestampValueEncoder.getString
對(duì)于 Date 類型字段值的處理:將Java程序時(shí)區(qū)下的日期的時(shí)間戳,轉(zhuǎn)為MySQL服務(wù)時(shí)區(qū)下的日期(2024-02-25 11:52:56 > 2024-02-25 03:52:56
)
查詢?nèi)掌跀?shù)據(jù)時(shí),com.mysql.cj.result.SqlTimestampValueFactory.localCreateFromDatetime
對(duì)于 Date 類型字段的處理:將MySQL服務(wù)時(shí)區(qū)下的日期的時(shí)間戳,轉(zhuǎn)為Java程序時(shí)區(qū)下的日期。(2024-02-24 21:34:55 > 2024-02-25 05:34:55
)
根據(jù)源代碼的實(shí)現(xiàn)可以發(fā)現(xiàn)一個(gè)規(guī)律:都是先將日期根據(jù)所屬時(shí)區(qū)轉(zhuǎn)換為時(shí)間戳后,在根據(jù)需要轉(zhuǎn)換的時(shí)區(qū)轉(zhuǎn)換為最終日期。
Java 程序時(shí)區(qū)與 MySQL 服務(wù)使用時(shí)區(qū)不一致導(dǎo)致的問題
在 JDBC 讀取并設(shè)置 MySQL 服務(wù)使用的時(shí)區(qū)的流程
中說到:MySQL 服務(wù)使用的時(shí)區(qū)會(huì)受到 jdbc 參數(shù)的影響,也就是說可能會(huì)出現(xiàn):實(shí)際的數(shù)據(jù)庫時(shí)區(qū)與 jdbc 參數(shù)聲明的時(shí)區(qū)是不一樣的。最壞情況下會(huì)出現(xiàn):Java 程序時(shí)區(qū)、jdbc 聲明時(shí)區(qū)、實(shí)際數(shù)據(jù)庫時(shí)區(qū)都是不同的。
不對(duì)每種情況的流程進(jìn)行逐個(gè)分析。一般開發(fā)時(shí)遇到時(shí)間不一致時(shí),大概都是分為以下的兩種情況:
Java 程序時(shí)區(qū) 與 jdbc 參數(shù)時(shí)區(qū)一致,實(shí)際數(shù)據(jù)庫時(shí)區(qū)不一致:這種情況下,會(huì)出現(xiàn)程序、數(shù)據(jù)庫中展示日期都是相同的,但兩個(gè)日期分別對(duì)應(yīng)的時(shí)間戳不相同
??雌饋硎菦]有問題,但實(shí)際上,數(shù)據(jù)庫中存儲(chǔ)的日期的時(shí)間戳已經(jīng)不是Java程序中該日期所對(duì)應(yīng)的時(shí)間戳了
。如最開始說到的:一個(gè)日期在不同時(shí)區(qū)下的時(shí)間戳是不同的
,那么表達(dá)的意義也不一樣,就如北京八點(diǎn)與美國(guó)八點(diǎn)的區(qū)別。整理一下 JDBC 驅(qū)動(dòng)包在這種情況下的處理流程:
1)存儲(chǔ):
2)讀?。軌蛟谵D(zhuǎn)為 Java 程序中的正確日期):
- 獲取日期在 Java 程序時(shí)區(qū)下的時(shí)間戳
- 根據(jù) JDBC 參數(shù)時(shí)區(qū)將時(shí)間戳轉(zhuǎn)為 MySQL 服務(wù)時(shí)區(qū)下的日期字符串(由于二者一致,所以結(jié)果沒有變化)
- 組裝為數(shù)據(jù)包發(fā)送給實(shí)際的 MySQL 服務(wù)
- MySQL 服務(wù)根據(jù)該日期字符串,轉(zhuǎn)為實(shí)際數(shù)據(jù)庫時(shí)區(qū)下的該日期(此時(shí)時(shí)間戳已經(jīng)不同了)
- 讀取實(shí)際數(shù)據(jù)庫中的日期字符串
- 根據(jù) JDBC 參數(shù)時(shí)區(qū)將日期轉(zhuǎn)為對(duì)應(yīng)時(shí)間戳
- 將時(shí)間戳在根據(jù) Java 程序時(shí)區(qū)轉(zhuǎn)為對(duì)應(yīng)的日期(由于與 JDBC 參數(shù)時(shí)區(qū)一致,最終程序中的日期還是相同的)
Java 程序時(shí)區(qū) 與 實(shí)際數(shù)據(jù)庫時(shí)區(qū)不一致:這種情況下不考慮 jdbc 參數(shù)時(shí)區(qū)的影響,會(huì)出現(xiàn)程序、數(shù)據(jù)庫中展示的日期不同,但兩個(gè)不同日期的時(shí)間戳是一致的
。捋一下 JDBC 處理流程:
1)存儲(chǔ):
- 獲取日期在 Java 程序時(shí)區(qū)下的時(shí)間戳
- 獲取實(shí)際 MySQL 服務(wù)的時(shí)區(qū),并轉(zhuǎn)為該時(shí)區(qū)下的日期字符串(由于二者不一致,所以轉(zhuǎn)換結(jié)果日期也不同)
- 組裝為數(shù)據(jù)包發(fā)送給實(shí)際的 MySQL 服務(wù)
- MySQL 服務(wù)根據(jù)該日期字符串,轉(zhuǎn)為實(shí)際數(shù)據(jù)庫時(shí)區(qū)下的該日期(此時(shí)展示的日期已經(jīng)不同,但時(shí)間戳還是相同的)
2)讀取(能夠在轉(zhuǎn)為 Java 程序中的正確日期):
- 讀取實(shí)際數(shù)據(jù)庫中的日期字符串
- 獲取實(shí)際 MySQL 服務(wù)的時(shí)區(qū),將日期轉(zhuǎn)為對(duì)應(yīng)時(shí)間戳
- 將時(shí)間戳在根據(jù) Java 程序時(shí)區(qū)轉(zhuǎn)為對(duì)應(yīng)的日期(雖然時(shí)區(qū)不同,但是時(shí)間戳還是同一個(gè),能夠在正確轉(zhuǎn)為 Java 程序中的正確日期)
僅依靠數(shù)據(jù)庫時(shí)區(qū)生成時(shí)間數(shù)據(jù)(這是一種特殊情況):
當(dāng)我們?cè)跒閯?chuàng)建、修改時(shí)間字段添加了以下自動(dòng)獲取時(shí)間屬性時(shí):
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間', `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時(shí)間'
1)存儲(chǔ):僅依賴數(shù)據(jù)庫時(shí)區(qū),不涉及時(shí)區(qū)轉(zhuǎn)換的問題
2)讀?。軌蛟谵D(zhuǎn)為 Java 程序中的正確日期):
- 讀取實(shí)際數(shù)據(jù)庫中的日期字符串
- 獲取實(shí)際 MySQL 服務(wù)的時(shí)區(qū),將日期轉(zhuǎn)為對(duì)應(yīng)時(shí)間戳
- 將時(shí)間戳在根據(jù) Java 程序時(shí)區(qū)轉(zhuǎn)為對(duì)應(yīng)的日期(雖然時(shí)區(qū)不同,但是時(shí)間戳沒有發(fā)生變化,能夠在正確轉(zhuǎn)為 Java 程序中的正確日期)
解決方式:
- 將 Java 程序時(shí)區(qū)、jdbc 聲明的參數(shù)時(shí)區(qū)、實(shí)際數(shù)據(jù)庫時(shí)區(qū)設(shè)置為相同的時(shí)區(qū)
- 將 Java 程序時(shí)區(qū)、實(shí)際數(shù)據(jù)庫時(shí)區(qū)設(shè)置為相同的時(shí)區(qū),且不設(shè)置 jdbc 聲明的參數(shù)時(shí)區(qū)這一干擾配置
到此這篇關(guān)于Java與MySQL導(dǎo)致的時(shí)間不一致問題分析的文章就介紹到這了,更多相關(guān)Java MySQL時(shí)區(qū)不一致 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- mysql8.0?my.ini?如何永久修改時(shí)區(qū)
- 解決MySQL時(shí)區(qū)日期時(shí)差8個(gè)小時(shí)的問題
- MySQL timestamp與時(shí)區(qū)問題的解決
- MySQL修改時(shí)區(qū)的方法圖文詳解
- mysql之關(guān)于CST和GMT時(shí)區(qū)時(shí)間轉(zhuǎn)換方式
- MySQL中的時(shí)區(qū)設(shè)置方式
- 關(guān)于mysql的時(shí)區(qū)問題
- MySQL時(shí)區(qū)差8小時(shí)的多種問題解決方法
- Mysql查看數(shù)據(jù)庫時(shí)區(qū)并設(shè)置時(shí)區(qū)的方法
- IDEA連接mysql時(shí)區(qū)問題解決
相關(guān)文章
SpringBoot 整合Tess4J庫實(shí)現(xiàn)圖片文字識(shí)別案例詳解
Tess4J是一個(gè)基于Tesseract OCR引擎的Java接口,可以用來識(shí)別圖像中的文本,說白了,就是封裝了它的API,讓Java可以直接調(diào)用,今天給大家分享一個(gè)SpringBoot整合Tess4j庫實(shí)現(xiàn)圖片文字識(shí)別的小案例2023-10-10Dubbo異步調(diào)用的實(shí)現(xiàn)介紹
dubbo默認(rèn)使用同步的方式調(diào)用。但在有些特殊的場(chǎng)景下,我們可能希望異步調(diào)用dubbo接口,從而避免不必要的等待時(shí)間,這時(shí)候我們就需要用到異步。那么dubbo的異步是如何實(shí)現(xiàn)的呢?下面就來看看這個(gè)問題2022-09-09SpringMVC 參數(shù)綁定之視圖傳參到控制器的實(shí)現(xiàn)代碼
這篇文章主要介紹了SpringMVC 參數(shù)綁定之視圖傳參到控制器的相關(guān)知識(shí),本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03常用的Java數(shù)據(jù)結(jié)構(gòu)知識(shí)點(diǎn)匯總
這篇文章主要介紹了常用的Java數(shù)據(jù)結(jié)構(gòu)知識(shí)點(diǎn)匯總,數(shù)據(jù)結(jié)構(gòu)分線性數(shù)據(jù)結(jié)構(gòu)和非線性數(shù)據(jù)結(jié)構(gòu),下面對(duì)此作詳細(xì)介紹,需要的小伙伴可以參考一下,希望對(duì)你的學(xué)習(xí)或工作有所幫助2022-03-03Spring CGLlB動(dòng)態(tài)代理實(shí)現(xiàn)過程解析
這篇文章主要介紹了Spring CGLlB動(dòng)態(tài)代理實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10