MySQL?時(shí)區(qū)與?serverTimezone詳解
TL;DR
- 手動(dòng)為 MySQL 指定非偏移量的時(shí)區(qū),以避免
TIMESTAMP
類型夏令時(shí)問題和時(shí)區(qū)轉(zhuǎn)化性能瓶頸 - TIMESTAMP 范圍:'1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07'
- 連接 MySQL 數(shù)據(jù)庫時(shí),serverTimezone 參數(shù)用于指定數(shù)據(jù)庫服務(wù)器的時(shí)區(qū),需要設(shè)置為與 MySQL 服務(wù)端相同的時(shí)區(qū)
MySQL 時(shí)區(qū)設(shè)置影響 TIMESTAMP 類型數(shù)據(jù)和部分時(shí)間函數(shù)
MySQL 會(huì)話時(shí)區(qū)設(shè)置會(huì)影響 TIMESTAMP
和 時(shí)間函數(shù)(NOW()、CURDATE()、CURTIME()、CURRENT_TIMESTAMP())
存儲(chǔ) TIMESTAMP
類型數(shù)據(jù)時(shí),MySQL 會(huì)根據(jù)當(dāng)前會(huì)話的時(shí)區(qū)將時(shí)間轉(zhuǎn)換為 UTC 時(shí)間,MySQL 實(shí)際存儲(chǔ)的是 UTC 時(shí)間。檢索時(shí) MySQL 根據(jù)會(huì)話的時(shí)區(qū)將存儲(chǔ)的 UTC 時(shí)間轉(zhuǎn)換為會(huì)話對(duì)應(yīng)時(shí)區(qū)的時(shí)間。而 DATETIME 類型的字段存儲(chǔ)的時(shí)間值是原始值,不受時(shí)區(qū)影響
MySQL 默認(rèn)使用 SYSTEM 時(shí)區(qū)(即操作系統(tǒng)的時(shí)區(qū)),每個(gè)需要時(shí)區(qū)計(jì)算的 MySQL 函數(shù)調(diào)用都會(huì)調(diào)用系統(tǒng)庫來確定當(dāng)前系統(tǒng)時(shí)區(qū)。此調(diào)用可能受到全局互斥體的保護(hù),從而導(dǎo)致爭(zhēng)用,建議顯式設(shè)置時(shí)區(qū)
查詢當(dāng)前時(shí)區(qū)
# time_zone:MySQL 使用 SYSTEM 的時(shí)區(qū) # system_time_zone:SYSTEM 為 CST 時(shí)區(qū) show variables like "%time_zone%"; +------------------+--------+ | Variable_name | Value | +------------------+--------+ | system_time_zone | CST | | time_zone | SYSTEM | +------------------+--------+
不同會(huì)話時(shí)區(qū)對(duì) 時(shí)間函數(shù) 的影響
# 當(dāng)前時(shí)區(qū) # 查看當(dāng)前的全球和會(huì)話時(shí)區(qū)值 SELECT @@GLOBAL.time_zone, @@SESSION.time_zone; SELECT NOW(), CURDATE(), CURTIME(), CURRENT_TIMESTAMP(); set time_zone = 'America/New_York'; SELECT NOW(), CURDATE(), CURTIME(), CURRENT_TIMESTAMP();
不同會(huì)話時(shí)區(qū)對(duì) TIMESTAMP 類型的影響
# UTC +8 set time_zone = 'Asia/Shanghai'; CREATE TABLE events ( id INT AUTO_INCREMENT PRIMARY KEY, event_name VARCHAR(255) NOT NULL, event_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, event_datetime DATETIME DEFAULT CURRENT_TIMESTAMP ); INSERT INTO events (event_name, event_timestamp, event_datetime) VALUES ('10.24 15:45:00', '2022-10-24 15:45:00', '2022-10-24 15:45:00'); INSERT INTO events (event_name, event_timestamp, event_datetime) VALUES ('12.24 15:45:00', '2022-12-24 15:45:00', '2022-12-24 15:45:00');
SELECT * FROM events; +----+----------------+---------------------+---------------------+ | id | event_name | event_timestamp | event_datetime | +----+----------------+---------------------+---------------------+ | 1 | 10.24 15:45:00 | 2022-10-24 15:45:00 | 2022-10-24 15:45:00 | | 2 | 12.24 15:45:00 | 2022-12-24 15:45:00 | 2022-12-24 15:45:00 | +----+----------------+---------------------+---------------------+ 2 rows in set (0.00 sec)
# 僅修改當(dāng)前會(huì)話的時(shí)區(qū) set time_zone = 'America/New_York';
SELECT * FROM events; +----+----------------+---------------------+---------------------+ | id | event_name | event_timestamp | event_datetime | +----+----------------+---------------------+---------------------+ | 1 | 10.24 15:45:00 | 2022-10-24 03:45:00 | 2022-10-24 15:45:00 | <- 夏令時(shí),相差 12 小時(shí) | 2 | 12.24 15:45:00 | 2022-12-24 02:45:00 | 2022-12-24 15:45:00 | <- 平時(shí)相差 13 小時(shí) +----+----------------+---------------------+---------------------+ 2 rows in set (0.00 sec)
紐約 UTC 時(shí)差通常為 UTC-5(EST),夏令時(shí)為 UTC-4(EDT),所以將原本的會(huì)話從上海(UTC+8) 轉(zhuǎn)到紐約時(shí),TIMESTAMP
相差了 13 或 12(夏令時(shí)) 小時(shí),所以為了自動(dòng)轉(zhuǎn)換夏令時(shí),指定時(shí)區(qū)最好使用時(shí)區(qū)名詞 Asia/Shanghai
,避免使用偏移量:'+08:00'
JDBC 連接 MySQL 時(shí) serverTimezone 對(duì)于 TIMESTAMP 類型的影響
連接 MySQL 時(shí)我們使用 URL:jdbc:mysql://192.168.1.2:3306/mydb?useSSL=false&serverTimezone=Asia/Shanghai
這里的 serverTimezone
參數(shù)用于指定連接到 MySQL 數(shù)據(jù)庫時(shí)所使用的時(shí)區(qū),不顯示指定使用 JVM 默認(rèn)時(shí)區(qū)
MySQL 服務(wù)端處理 TIMESTAMP
:寫入時(shí)根據(jù)會(huì)話時(shí)區(qū)轉(zhuǎn)為 UTC 時(shí)間戳存儲(chǔ),讀取時(shí)將 UTC 還原為會(huì)話時(shí)區(qū)的時(shí)間,保證了寫入和讀取數(shù)據(jù)的一致。數(shù)據(jù)庫會(huì)話時(shí)區(qū)與 JVM 時(shí)區(qū)相同時(shí),JVM 讀寫的 TIMESTAMP
一致,如果不一致就會(huì)出現(xiàn)問題,serverTimezone
就是為了告訴 JDBC 從 MySQL 服務(wù)端獲取到的 TIMESTAMP
是什么時(shí)區(qū),知道了它所使用的時(shí)區(qū),JDBC 就可以進(jìn)行預(yù)處理
MyBatis 在處理 TIMESTAMP
類型的數(shù)據(jù)時(shí)會(huì)有一些差異,實(shí)體映射為 Timestamp
或 Date
在讀寫時(shí)會(huì)進(jìn)行上面提到的預(yù)處理,而 LocalDateTime
則不會(huì)
JDBC 讀取 TIMESTAMP 類型數(shù)據(jù)時(shí)
JDBC 執(zhí)行命令時(shí),調(diào)用不同的 ResultSet 方法會(huì)有不同結(jié)果:
- ResultSet 的 getString 方法:直接讀取時(shí)間,即直接返回 數(shù)據(jù)庫根據(jù)會(huì)話時(shí)區(qū)轉(zhuǎn)化后的時(shí)間
- ResultSet 的 getTimestamp 方法:將 數(shù)據(jù)庫根據(jù)會(huì)話時(shí)區(qū)轉(zhuǎn)化后的時(shí)間 根據(jù) serverTimezone 設(shè)置的時(shí)區(qū)進(jìn)行轉(zhuǎn)化,得到 數(shù)據(jù)庫根據(jù)會(huì)話時(shí)區(qū)轉(zhuǎn)化后的時(shí)間 對(duì)應(yīng)的 UTC 時(shí)間毫秒戳,然后將這個(gè) UTC 毫秒時(shí)間戳轉(zhuǎn)換為
Timestamp
類型(它本身不包含時(shí)區(qū)信息),打印時(shí)會(huì)根據(jù) JVM 的時(shí)區(qū)轉(zhuǎn)化為對(duì)應(yīng)的時(shí)區(qū)時(shí)間
getTimestamp
轉(zhuǎn)化 Timestamp
的源碼在:com.mysql.cj.result.SqlTimestampValueFactory
這里的 this.connectionTimeZone
就是連接 url 中指定的 serverTimezone
假設(shè) MySQL 默認(rèn)設(shè)置的會(huì)話時(shí)區(qū)為 Asia/Shanghai
,通過默認(rèn)會(huì)話讀取該 TIMESTAMP 的值為:2022-10-24 15:45:00。而 MySQL 實(shí)際存儲(chǔ)的 TIMESTAMP 為 UTC 時(shí)間:2022-10-24 07:45:00。MySQL JDBC 驅(qū)動(dòng)通過默認(rèn)會(huì)話獲取該值時(shí),MySQL 會(huì)自動(dòng)根據(jù)默認(rèn)時(shí)區(qū)提供轉(zhuǎn)化好時(shí)間:2022-10-24 15:45:00,驅(qū)動(dòng)則會(huì)根據(jù) serverTimezone
配置的時(shí)區(qū),將 MySQL 的時(shí)間轉(zhuǎn)化為 Calendar
對(duì)象,通過 c.getTimeInMillis()
獲取對(duì)應(yīng)的 UTC 時(shí)間戳,用于創(chuàng)建 Timestamp
對(duì)象
JDBC 寫入 TIMESTAMP 類型:
- now()寫入,數(shù)據(jù)庫 server 端會(huì)獲取數(shù)據(jù)庫當(dāng)前時(shí)區(qū)
- 按照字符串寫入:MySQL 服務(wù)端根據(jù)會(huì)話時(shí)區(qū)轉(zhuǎn)成對(duì)應(yīng)的 UTC 毫秒數(shù)存儲(chǔ)
- 通過變量綁定寫入:傳入 Timestamp 對(duì)象,JDBC 將其編碼為 serverTimezone 所代表的時(shí)間字符串,類似:
2022-06-22 03:29:29
,然后發(fā)送給 MySQL 服務(wù)端
驗(yàn)證
import org.junit.jupiter.api.Test; import java.sql.*; import java.util.TimeZone; public class JDBCTest { private static String url = "jdbc:mysql://host:3306/mydb?useSSL=false&serverTimezone=UTC"; private static String username = "root"; private static String password = ""; @Test void testInsertTimestamp() { TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai")); try (Connection connection = DriverManager.getConnection(url, username, password); PreparedStatement ps = connection .prepareStatement("insert into events(id,event_name,event_timestamp,event_datetime) values (1,'now()',now(),now())");) { ps.execute(); }catch (Exception e){ e.printStackTrace(); } try (Connection connection = DriverManager.getConnection(url, username, password); PreparedStatement ps = connection .prepareStatement("insert into events(id,event_name,event_timestamp,event_datetime) values (2,'2022-06-22 03:29:29','2022-06-22 03:29:29', '2022-06-22 03:29:29')");) { ps.execute(); }catch (Exception e){ e.printStackTrace(); } try (Connection connection = DriverManager.getConnection(url, username, password); PreparedStatement ps = connection .prepareStatement("insert into events(id,event_name,event_timestamp,event_datetime) values (3,'1733539800000L',?,?)")) { // Sat Dec 07 2024 02:50:00 GMT+0000 // Sat Dec 07 2024 10:50:00 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間) long timestamp = 1733539800000L; Timestamp ts1 = new Timestamp(timestamp); Timestamp ts2 = new Timestamp(timestamp); ps.setTimestamp(1, ts1); ps.setTimestamp(2, ts2); ps.execute(); // 根據(jù) serverTimezone 將 Timestamp 預(yù)處理為 UTC 時(shí)間:2024-12-07 02:50:00 // 相當(dāng)于執(zhí)行下列 SQL // insert into events(id,event_name,event_timestamp,event_datetime) values (3,'1733539800000L','2024-12-07 02:50:00','2024-12-07 02:50:00') }catch (Exception e){ e.printStackTrace(); } } @Test void testGetTimestamp() { TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai")); try (Connection connection = DriverManager.getConnection(url, username, password); PreparedStatement ps = connection .prepareStatement("select * from events where id=3"); ResultSet rs = ps.executeQuery();) { while (rs.next()) { // getTimestamp is 2024-12-07 10:50:00.0 // 根據(jù) serverTimezone,認(rèn)定數(shù)據(jù)庫時(shí)區(qū)為 UTC,轉(zhuǎn)化為 本地 Asia/Shanghai 需要 +8,則預(yù)處理為:2024-12-07 10:50:00.0 System.out.println("getTimestamp is " + rs.getTimestamp("event_timestamp")); // getString is 2024-12-07 02:50:00 System.out.println("getString is " + rs.getString("event_datetime")); } }catch (Exception e){ e.printStackTrace(); } } }
實(shí)驗(yàn)環(huán)境:
MySQL 8.0.40
mysql-connector-j 9.1.0
mybatis-spring-boot-starter 3.0.4
參考資料
7.1.15 MySQL Server Time Zone Support
13.2.2 The DATE, DATETIME, and TIMESTAMP Types
一文講透MySQL driver讀取時(shí)間時(shí)的時(shí)區(qū)處理
MySQL 中存儲(chǔ)時(shí)間的最佳實(shí)踐
到此這篇關(guān)于MySQL 時(shí)區(qū)與 serverTimezone的文章就介紹到這了,更多相關(guān)mysql serverTimezone內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- MySQL?時(shí)區(qū)與?serverTimezone詳解
- com.mysql.jdbc.Driver 和 com.mysql.cj.jdbc.Driver的區(qū)別及設(shè)定serverTimezone的方法
- IDEA連接MySQL提示serverTimezone的問題及解決方法
- IDEA連接mysql又報(bào)錯(cuò)!Server returns invalid timezone. Go to tab and set serverTimezone prope的問題
- 一文徹底搞懂MySQL?TimeStamp時(shí)區(qū)問題
- MySQL時(shí)區(qū)差8小時(shí)的多種問題解決方法
- MySQL中的時(shí)區(qū)設(shè)置方式
相關(guān)文章
MySQL數(shù)據(jù)庫定時(shí)任務(wù)舉例講解
最近項(xiàng)目里面的后臺(tái)需要用到定時(shí)任務(wù),而MySQL從5.0開始自帶了定時(shí)事件操作,所以學(xué)習(xí)下并做下記錄,下面這篇文章主要給大家介紹了關(guān)于MySQL數(shù)據(jù)庫定時(shí)任務(wù)的相關(guān)資料,需要的朋友可以參考下2023-06-06ubuntu20.04配置mysql8.0的實(shí)現(xiàn)步驟
本文主要介紹了ubuntu20.04配置mysql8.0的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05MySQL-MMM安裝指南(Multi-Master Replication Manager for MySQL)
這篇文章主要介紹了mysql Multi-Master Replication Manager for MySQL的安裝方法,需要的朋友可以參考下2014-02-02mysql如何創(chuàng)建和刪除唯一索引(unique key)
這篇文章主要介紹了mysql如何創(chuàng)建和刪除唯一索引(unique key)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12Debian 6.02 (squeeze)下編譯安裝 MySQL 5.5的方法
Debian 6.02 (squeeze)下編譯安裝 MySQL 5.5的方法,需要的朋友可以參考下。2011-12-12Mysql導(dǎo)入導(dǎo)出時(shí)遇到的問題解決
這篇文章主要給大家介紹了關(guān)于Mysql導(dǎo)入導(dǎo)出時(shí)遇到問題的解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Mysql具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08