关于Java中的mysql时区问题详解

前言

话说工作十多年,mysql 还真没用几年。起初是外企银行,无法直接接触到 DB;后来一直从事架构方面,也多是解决问题为主。

这次搭建海外机房,围绕时区大家做了一番讨论。不说最终的结果是什么,期间有同事认为 DB 返回的是 UTC 时间。

这里简单做个验证,顺便看下时区的问题到底是如何处理。

环境

openjdk version “1.8.0_242”
mysql-connector-java “8.0.20”
mysql “5.7” 时区 TZ=Europe/London

本地时区 GMT+8

创建个简单的库test及表user, 表结构如下:

CREATE TABLE `user` (
 `name` varchar(50) NOT NULL,
 `birth_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=latin1

插入一条测试数据:

mysql> insert into `user`
  -> values ('Tom', time('2020-05-15 08:00:00'));
Query OK, 1 row affected (0.01 sec)

mysql> select * from user;
+------+---------------------+
| name | birth_date     |
+------+---------------------+
| Tom | 2020-05-14 08:00:00 |
+------+---------------------+
1 row in set (0.00 sec)

测试代码:

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useSSL=false", "root", "root");
Statement stmt = conn.createStatement();
stmt.execute("select * from user where name = 'Tom'");
ResultSet rs = stmt.getResultSet();
while (rs.next()) {
  Timestamp timestamp = rs.getTimestamp("birth_date");
  System.out.println(timestamp.toLocalDateTime().toString());
}

执行结果:

2020-05-14T15:00

分析

程序的执行过程同时用 wireshark 抓了包。可以看到一次查询,做了这么多次的交互(包含了会话初始化)。这里可以看到 #177 的交互返回查询的结果:Tom 2020-05-14 08:00:00,与 DB 中的数据相符。可见,返回的并不是 UTC 时间。

在 TCP 抓包结果中 #155 的查询语句:

/* mysql-connector-java-8.0.20 (Revision: afc0a13cd3c5a0bf57eaa809ee0ee6df1fd5ac9b) */
SELECT @@session.auto_increment_increment AS auto_increment_increment,
    @@character_set_client       AS character_set_client,
    @@character_set_connection     AS character_set_connection,
    @@character_set_results      AS character_set_results,
    @@character_set_server       AS character_set_server,
    @@collation_server         AS collation_server,
    @@collation_connection       AS collation_connection,
    @@init_connect           AS init_connect,
    @@interactive_timeout       AS interactive_timeout,
    @@license             AS license,
    @@lower_case_table_names      AS lower_case_table_names,
    @@max_allowed_packet        AS max_allowed_packet,
    @@net_write_timeout        AS net_write_timeout,
    @@performance_schema        AS performance_schema,
    @@query_cache_size         AS query_cache_size,
    @@query_cache_type         AS query_cache_type,
    @@sql_mode             AS sql_mode,
    @@system_time_zone         AS system_time_zone,
    @@time_zone            AS time_zone,
    @@transaction_isolation      AS transaction_isolation,
    @@wait_timeout           AS wait_timeout;

服务端返回的 time_zone 为 BST。与本地时区的转换,由 mysql 的 connector 自动完成。

进阶

时区自动转换

实现源码:

ResultSetImpl源码

this.defaultTimestampValueFactory = new SqlTimestampValueFactory(pset, null, this.session.getServerSession().getServerTimeZone());@Overridepublic Timestamp getTimestamp(int columnIndex) throws SQLException {
  checkRowPos();
  checkColumnBounds(columnIndex);  return this.thisRow.getValue(columnIndex - 1, this.defaultTimestampValueFactory);
}

如何确认服务端时区?

使用会话中的服务端时区进行服务端时区。会话初始化时会进行时区的确认,比如前面获取的到BST。确认时区的逻辑在NativeProtocol#configureTimezone()中:

public void configureTimezone() {
  #从mysql的响应获取 time_zone 和 system_time_zone 的设置
  String configuredTimeZoneOnServer = this.serverSession.getServerVariable("time_zone");

  if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) {
    configuredTimeZoneOnServer = this.serverSession.getServerVariable("system_time_zone");
  }

  #从 jdbc url 参数 serverTimezone 获取时区
  String canonicalTimezone = getPropertySet().getStringProperty(PropertyKey.serverTimezone).getValue();

  if (configuredTimeZoneOnServer != null) {
    //如果 jdbc url 中未通过 serverTimezone 指定时区。则从TimeZoneMapping.properties中获取mysql 回传的时区缩写对应的标准时区,比如此处的 BST => Europe/London
    //会出现无法映射的情况,不如 CEST 无法映射到 => Europe/Berlin,可以指定自定义的 Properties 文件进行映射
    // 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());
      }
    }
  }

  //如果 jdbc url 中通过 serverTimezone 指定了时区,则优先使用该时区
  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());
    }
  }

}

关于 serverTimezone 的官方说明

Override detection/mapping of time zone. Used when time zone from server doesn't map to Java time zone

修改一下 jdbc url,通过serverTimezone指定时区为 GMT+8:jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useSSL=false

再次执行代码:

2020-05-14T08:00

总结

到此这篇关于关于Java中mysql时区问题的文章就介绍到这了,更多相关Java中mysql时区问题内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • MySQL timestamp的类型与时区实例详解

     MySQL timestamp的类型与时区 MySQL的timestamp类型时间范围between '1970-01-01 00:00:01' and '2038-01-19 03:14:07',超出这个范围则值记录为'0000-00-00 00:00:00',该类型的一个重要特点就是保存的时间与时区密切相关,上述所说的时间范围是UTC(Universal Time Coordinated)标准,指的是经度0度上的标准时间,我国日常生活中时区以首都北京所处的东半球第8区为基准,统一使用东8区

  • mysql中url时区的陷阱该如何规避详解

    前言 最近在使用mysql的6.0.x以上的jar的时候,需要在代码url的链接里面指定serverTimezone.就会出现异常: 1.未指定serverTimezone xml里面配置url <property name="url" value="jdbc:mysql://localhost:3306/mybatisstudy"/> 出现的异常 Caused by: com.mysql.cj.core.exceptions.InvalidConnec

  • MySQL修改时区的方法小结

    本文实例总结了MySQL修改时区的方法.分享给大家供大家参考,具体如下: 说明:这里总结记录修改mysql时区的三种方法. 方法一:通过mysql命令行模式下动态修改 1.1 查看mysql当前时间,当前时区 > select curtime(); #或select now()也可以 +-----------+ | curtime() | +-----------+ | 15:18:10 | +-----------+ > show variables like "%time_zon

  • 有关 PHP 和 MySQL 时区的一点总结

    PHP 脚本端的市区设置可以在 php.ini 下设置 date.timezone 键的值为 'Asia/Shanghai' 即可.但是通常共享虚拟主机本身没有修改 php.ini 权限.这个时候就应该在程序公共部分加入 ini_set('date.timezone','Asia/Shanghai');动态修改 php.ini 的设置.之后可以测试一下时间是否正确: var_dump(date());如果服务器的本地时间是正确的,那么一般就能解决问题了.附,PHP 5.1 以上提供了专门的函数修

  • mysql时区问题

    用convert_tz转换时区,你可以用      show   variables   like   'time_zone';      得到时区,如果返回的是"system"的话,你可以用      show   variables   like   'system_time_zone';      得到结果.

  • MySQL查看和修改时区的方法

    今天发现有一个程序插入的时间不对,而该字段是配置的默认值 CURRENT_TIMESTAMP,初步判断是数据库的时区设置问题. 查看时区 登录数据库查看时区配置: mysql> show variables like '%time_zone%'; +------------------+--------+ | Variable_name | Value | +------------------+--------+ | system_time_zone | EDT | | time_zone |

  • 详解MySQL查询时区分字符串中字母大小写的方法

    如果你在mysql有唯一约束的列上插入两行值'A'和'a',Mysql会认为它是相同的,而在oracle中就不会.就是mysql默认的字段值不区分大小写?这点是比较令人头痛的事.直接使用客户端用sql查询数据库. 发现的确是大小不敏感 . 通过查询资料发现需要设置collate(校对) . collate规则: *_bin: 表示的是binary case sensitive collation,也就是说是区分大小写的 *_cs: case sensitive collation,区分大小写 *

  • 关于Java中的mysql时区问题详解

    前言 话说工作十多年,mysql 还真没用几年.起初是外企银行,无法直接接触到 DB:后来一直从事架构方面,也多是解决问题为主. 这次搭建海外机房,围绕时区大家做了一番讨论.不说最终的结果是什么,期间有同事认为 DB 返回的是 UTC 时间. 这里简单做个验证,顺便看下时区的问题到底是如何处理. 环境 openjdk version "1.8.0_242" mysql-connector-java "8.0.20" mysql "5.7" 时区

  • Java中的动态和静态编译实例详解

    Java中的动态和静态编译实例详解 首先,我们来说说动态和静态编译的问题. Q: java和javascript有什么区别?    总结了一下:有以下几点吧: 1.首先从运行环境来说java代码是在JVM上编译成class文件,而javascript则直接在浏览器上加载运行. 2.由第一点可看出,java代码需要编译,而javascript不需要编译. 3.从语言性质来说,java是一种高级编程语言,对变量检查要求严格,javascript只是一个简单的解释性的脚本语言,对变量检查及要求很弱.

  • Java中反射机制和作用详解

    前言 很多刚学Java反射的同学可能对反射技术一头雾水,为什么要学习反射,学习反射有什么作用,不用反射,通过new也能创建用户对象. 那么接下来大师就带你们了解一下反射是什么,为什么要学习反射? 下面我们首先通过一个实例来说明反射的好处: 方法1.不用反射技术,创建用户对象,调用sayHello方法 1.1 我们首先创建一个User类 package com.dashi; /** * Author:Java大师 * User对象,包含用户的id和姓名以及sayHello方法 */ public

  • Java中JDBC的使用教程详解

    目录 概念 快速入门 步骤 代码实现 详解各个对象 DriverManager:驱动管理对象 Connection:数据库连接对象 Statement:执行sql的对象 ResultSet:结果集对象,封装查询结果 PreparedStatement:执行sql的对象 抽取JDBC工具类 : JDBCUtils 分析 代码实现 练习 JDBC控制事务 事务 操作 使用Connection对象来管理事务 代码 概念 Java DataBase Connectivity  Java 数据库连接, J

  • Java中的zookeeper常用命令详解

    目录 1.zkCli.sh客户端 2.多节点类型创建 3.查询节点 4.set数据 5.删除节点 6.权限设置 7.其他命令 注意我这里用的是官方最稳定的版本3.7.1,版本之间有个别命令是有差距的! 1.zkCli.sh客户端 zkCli.sh可以理解成客户端,也可以理解成命令行工具,把命令交给他,让他和zk的服务端打交道.类似于mysql,我们安装完mysql想要执行命令,那么就必须要通过mysql -u账号 -p密码进入命令行工具里面,才能执行sql. 在zookeeper/bin 目录下

  • Java中注解与原理分析详解

    目录 一.注解基础 二.注解原理 三.常用注解 1.JDK注解 2.Lombok注解 四.自定义注解 1.同步控制 2.类型引擎 一.注解基础 注解即标注与解析,在Java的代码工程中,注解的使用几乎是无处不在,甚至多到被忽视: 无论是在JDK源码或者框架组件,都在使用注解能力完成各种识别和解析动作:在对系统功能封装时,也会依赖注解能力简化各种逻辑的重复实现: 基础接口 在Annotation的源码注释中有说明:所有的注解类型都需要继承该公共接口,本质上看注解是接口,但是代码并没有显式声明继承关

  • Java 中This用法的实例详解

     Java 中This用法的实例详解 用类名定义一个变量的时候,定义的只是一个引用,外面可以通过这个引用来访问这个类里面的属性和方法. 那们类里面是够也应该有一个引用来访问自己的属性和方法纳? 呵呵,Java提供了一个很好的东西,就是 this 对象,它可以在类里面来引用这个类的属性和方法.先来个简单的例子: public class ThisDemo { String name="Mick"; public void print(String name){ System.out.pr

  • Java中正则表达式的使用和详解(下)

    在上篇给大家介绍了Java中正则表达式的使用和详解(上),具体内容如下所示: 1.常用正则表达式 规则 正则表达式语法   一个或多个汉字 ^[\u0391-\uFFE5]+$  邮政编码 ^[1-9]\d{5}$ QQ号码 ^[1-9]\d{4,10}$  邮箱 ^[a-zA-Z_]{1,}[0-9]{0,}@(([a-zA-z0-9]-*){1,}\.){1,3}[a-zA-z\-]{1,}$  用户名(字母开头 + 数字/字母/下划线) ^[A-Za-z][A-Za-z1-9_-]+$ 手

  • java 中enum的使用方法详解

    java 中enum的使用方法详解 enum 的全称为 enumeration, 是 JDK 1.5 中引入的新特性,存放在 java.lang 包中. 下面是我在使用 enum 过程中的一些经验和总结. 原始的接口定义常量 public interface IConstants { String MON = "Mon"; String TUE = "Tue"; String WED = "Wed"; String THU = "Thu

  • java 中自定义OutputFormat的实例详解

    java 中 自定义OutputFormat的实例详解 实例代码: package com.ccse.hadoop.outputformat; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.StringTokenizer; import org.apache.hadoop.conf.Configuration; import org.apa

随机推荐