MySQL 时区与 serverTimezone

news/2024/12/19 8:46:48/文章来源:https://www.cnblogs.com/aaronlinv/p/18615343

TL;DR

  1. 手动为 MySQL 指定非偏移量的时区,以避免 TIMESTAMP 类型夏令时问题和时区转化性能瓶颈
  2. TIMESTAMP 范围:'1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07'
  3. 连接 MySQL 数据库时,serverTimezone 参数用于指定数据库服务器的时区,需要设置为与 MySQL 服务端相同的时区

MySQL 时区设置影响 TIMESTAMP 类型数据和部分时间函数

MySQL 会话时区设置会影响 TIMESTAMP 和 时间函数(NOW()、CURDATE()、CURTIME()、CURRENT_TIMESTAMP())

存储 TIMESTAMP 类型数据时,MySQL 会根据当前会话的时区将时间转换为 UTC 时间,MySQL 实际存储的是 UTC 时间。检索时 MySQL 根据会话的时区将存储的 UTC 时间转换为会话对应时区的时间。而 DATETIME 类型的字段存储的时间值是原始值,不受时区影响

MySQL 默认使用 SYSTEM 时区(即操作系统的时区),每个需要时区计算的 MySQL 函数调用都会调用系统库来确定当前系统时区。此调用可能受到全局互斥体的保护,从而导致争用,建议显式设置时区

查询当前时区

# time_zone:MySQL 使用 SYSTEM 的时区
# system_time_zone:SYSTEM 为 CST 时区
show variables like "%time_zone%";
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | CST    |
| time_zone        | SYSTEM |
+------------------+--------+

不同会话时区对 时间函数 的影响

# 当前时区 
# 查看当前的全球和会话时区值
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();

不同会话时区对 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)
# 仅修改当前会话的时区
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 |    <- 夏令时,相差 12 小时
|  2 | 12.24 15:45:00 | 2022-12-24 02:45:00 | 2022-12-24 15:45:00 |    <- 平时相差 13 小时
+----+----------------+---------------------+---------------------+
2 rows in set (0.00 sec)

纽约 UTC 时差通常为 UTC-5(EST),夏令时为 UTC-4(EDT),所以将原本的会话从上海(UTC+8) 转到纽约时,TIMESTAMP 相差了 13 或 12(夏令时) 小时,所以为了自动转换夏令时,指定时区最好使用时区名词 Asia/Shanghai,避免使用偏移量:'+08:00'

JDBC 连接 MySQL 时 serverTimezone 对于 TIMESTAMP 类型的影响

连接 MySQL 时我们使用 URL:jdbc:mysql://192.168.1.2:3306/mydb?useSSL=false&serverTimezone=Asia/Shanghai

这里的 serverTimezone 参数用于指定连接到 MySQL 数据库时所使用的时区,不显示指定使用 JVM 默认时区

MySQL 服务端处理 TIMESTAMP :写入时根据会话时区转为 UTC 时间戳存储,读取时将 UTC 还原为会话时区的时间,保证了写入和读取数据的一致。数据库会话时区与 JVM 时区相同时,JVM 读写的 TIMESTAMP 一致,如果不一致就会出现问题,serverTimezone 就是为了告诉 JDBC 从 MySQL 服务端获取到的 TIMESTAMP 是什么时区,知道了它所使用的时区,JDBC 就可以进行预处理

MyBatis 在处理 TIMESTAMP 类型的数据时会有一些差异,实体映射为 TimestampDate 在读写时会进行上面提到的预处理,而 LocalDateTime 则不会

JDBC 读取 TIMESTAMP 类型数据时

JDBC 执行命令时,调用不同的 ResultSet 方法会有不同结果:

  • ResultSet 的 getString 方法:直接读取时间,即直接返回 数据库根据会话时区转化后的时间
  • ResultSet 的 getTimestamp 方法:将 数据库根据会话时区转化后的时间 根据 serverTimezone 设置的时区进行转化,得到 数据库根据会话时区转化后的时间 对应的 UTC 时间毫秒戳,然后将这个 UTC 毫秒时间戳转换为 Timestamp 类型(它本身不包含时区信息),打印时会根据 JVM 的时区转化为对应的时区时间

getTimestamp 转化 Timestamp 的源码在:com.mysql.cj.result.SqlTimestampValueFactory

这里的 this.connectionTimeZone 就是连接 url 中指定的 serverTimezone

假设 MySQL 默认设置的会话时区为 Asia/Shanghai,通过默认会话读取该 TIMESTAMP 的值为:2022-10-24 15:45:00。而 MySQL 实际存储的 TIMESTAMP 为 UTC 时间:2022-10-24 07:45:00。MySQL JDBC 驱动通过默认会话获取该值时,MySQL 会自动根据默认时区提供转化好时间:2022-10-24 15:45:00,驱动则会根据 serverTimezone 配置的时区,将 MySQL 的时间转化为 Calendar 对象,通过 c.getTimeInMillis() 获取对应的 UTC 时间戳,用于创建 Timestamp 对象

JDBC 写入 TIMESTAMP 类型:

  • now()写入,数据库 server 端会获取数据库当前时区
  • 按照字符串写入:MySQL 服务端根据会话时区转成对应的 UTC 毫秒数存储
  • 通过变量绑定写入:传入 Timestamp 对象,JDBC 将其编码为 serverTimezone 所代表的时间字符串,类似:2022-06-22 03:29:29,然后发送给 MySQL 服务端

验证

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 = "";@Testvoid 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 (中国标准时间)long timestamp = 1733539800000L;Timestamp ts1 = new Timestamp(timestamp);Timestamp ts2 = new Timestamp(timestamp);ps.setTimestamp(1, ts1);ps.setTimestamp(2, ts2);ps.execute();// 根据 serverTimezone 将 Timestamp 预处理为 UTC 时间:2024-12-07 02:50:00// 相当于执行下列 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();}}@Testvoid 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// 根据 serverTimezone,认定数据库时区为 UTC,转化为 本地 Asia/Shanghai 需要 +8,则预处理为:2024-12-07 10:50:00.0System.out.println("getTimestamp is " + rs.getTimestamp("event_timestamp"));// getString is 2024-12-07 02:50:00System.out.println("getString is " + rs.getString("event_datetime"));}}catch (Exception e){e.printStackTrace();}}
}

实验环境:
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读取时间时的时区处理
修改mysql时区的三种方法
MySQL 中存储时间的最佳实践

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/855219.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【详解】Ftrans安全数据摆渡系统,让数据传输更安全高效且可控!

基于提高安全性、满足合规性要求、保护核心资产、提高性能和可靠性以及风险管理等多方面的考虑,企业一般会选择网络隔离,有助于企业确保网络安全和数据保护,为企业的稳健发展提供有力保障。 一、网络隔离的必要性 1、提高安全性 1)限制潜在攻击者的横向移动:网络隔离可以限…

服务后台报临时上传路径/tmp/tongweb.xxxxxxxxxxxxxxxx.9999/work/Tongweb/localhost/ROOT无效

报错: 当服务后台报org.springframework.web.multipart.MultiPartException,failed to parsemultipart servlet request; nested exception is java.io.IOException,临时上传路径/tmp/tongweb.79923423523523135.9090/work/Tongweb/localhost/ROOT无效;是因为linux过段时间…

在 VS Code 中可以免费使用 GitHub Copilot了!

今天,有一个重大的好消息要分享给大家: 从现在开始,我们可以在 Visual Studio Code 中,免费使用强大的 GitHub Copilot 进行开发啦! 每个人都可以享受到 AI 加持下的丝滑开发体验! 那就让我们一起来看看如何能免费地用上 GitHub Copilot 吧! 1. 下载最新版本的 VS Code…

从底层源码深入分析Bean的实例化

生命周期的整体流程 Spring 容器可以管理 singleton 作用域 Bean 的生命周期,在此作用域下,Spring 能够精确地知道该 Bean 何时被创建,何时初始化完成,以及何时被销毁。 而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给…

abc 蓝题方法整理

abc 蓝题方法整理 trick 基本算法 搜索 最优性剪枝/保证枚举合法保证复杂度 : 42, 166 约束与两边有关系考虑双向搜索 : 54 数据范围较小考虑爆搜: 81, 162 从可能性较少的位置向较多的位置搜 : 162 记忆化搜索优化 : 162 meet-in-the-middle处理总状态不多的搜索 : 162, 193 边…

读图数据库实战笔记07高级数据建模技术

高级数据建模技术1. 高级数据建模技术 1.1. 大多数现实生活中的应用程序(如推荐引擎或个性化应用程序)所需的模型比社交网络示例的单顶点、单边数据模型复杂得多 1.2. 三种高级数据建模技术1.2.1. 使用通用标签提高性能1.2.2. 将属性移动到边,以简化遍历1.2.3. 对数据进行反…

AWQ:激活-软件权重量化

AWQ:激活-软件权重量化 大型语言模型(LLM)已经改变了许多人工智能应用程序。设备上的LLM变得越来越重要:在边缘设备上本地运行LLM,可以降低云计算成本并保护用户隐私。然而,天文模型的大小和有限的硬件资源带来了巨大的部署挑战。提出了激活感知权重量化(AWQ),这是一种…

用于显微镜的掩模自编码器是细胞生物学的可扩展学习

用于显微镜的掩模自编码器是细胞生物学的可扩展学习将显微镜图像特征化用于生物研究仍然是一个重大挑战,特别是对于跨越数百万张图像的大规模实验。这项工作探讨了弱监督分类器和自监督掩码自编码器(MAE),在使用越来越大的模型骨干和显微镜数据集进行训练时的缩放特性。结果…

转发:《AI芯片开发核心技术详解》、《智能汽车传感器:原理设计应用》新书推荐

由清华大学出版社资深编辑赵佳霓老师策划编辑的新书《AI芯片开发核心技术详解》已经出版,京东、淘宝天猫、当当等网上,相应陆陆续续可以购买。该书强力解析AI芯片的核心技术开发,内容翔实、知识点新颖、实践性很强、图文并茂。 由清华大学出版社资深编辑赵佳霓老师…

博客园修饰:音乐播放器+鼠标特效

音乐播放器首先申请js权限然后在页脚html代码中添加<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/aplayer@1.10.0/dist/APlayer.min.css"> <script src="https://blog-static.cnblogs.com/files/yjlaugus/APlayer.min.js&quo…

45. jQuery

1. jQuery介绍 1.1 概念jQuery 是一个快速、小型且功能丰富的 JavaScript 库。它使 比如 HTML 文档遍历和操作、事件处理、 动画和 Ajax 通过易于使用的 API 变得更加简单,该 API 可以在 多种浏览器。结合了多功能性和 可扩展性,jQuery 改变了数百万人的编写方式 JavaScript …

服务治理Consul篇

服务中心Consul 光从名字上就能看出他是个头头。Consul的本意是“领事,总督”。就像战场上的将军,带一帮喽啰去打仗,首先他要有个花名册,记录下他们有哪些战士,姓甚名谁,抡大刀还是耍长枪,他要知道哪个战士的能力如何,该对付多少敌人,哪个战士失踪了,或者战伤了,该不…