交流群:462197261站长百科站长论坛热门标签收藏本站北冥有鱼 互联网前沿资源第一站 助力全行业互联网+
点击这里给我发消息
  • 当前位置:
  • 关于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时区问题内容请搜索北冥有鱼以前的文章或继续浏览下面的相关文章希望大家以后多多支持北冥有鱼!


    广而告之:
    热门推荐:
    深入浅析knockout源码分析之订阅

    Knockout.js是什么? Knockout是一款很优秀的JavaScript库,它可以帮助你仅使用一个清晰整洁的底层数据模型(data model)即可创建一个富文本且具有良好的显示和编辑功能的用户界面。任何时候你的局部UI内容需要自动更新(比如:依赖于用户行为的改变或者外部的数据源发生变化···

    Asp.Net套用母版页后元素ID不一致(个人总结)

    在内容页里,在这个标签对里: <asp:Content ID=”content” ContentPlaceHolderID=”MainContent” runat=”server”> </asp: Content > 一、form的ID变化。 <form id=”form1″ runat=”server”> <form name=”aspnetForm” method=”post” action=”r_Balance.a···

    Ajax.net Sys未定义错误解决办法

    查了很多处理日志,说的都是在Web.Config里面加什么语句,就是下面这些: 复制代码 代码如下:< httpHandlers>   < remove verb="*" path="*.asmx"/>   < add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandl···

    Bootstrap CSS布局之列表

    本文实例为大家分享了Bootstrap CSS布局中的列表布局,供大家参考,具体内容如下 列表 普通列表ul li 有序列表ol li 去点列表.list-unstyled 内联列表.list-inline 定义列表dl dt dd 水平定义列表dl-horizontal ul, ol { margin-top: 0; margin-bottom: 10px; } ul ul···

    JavaScript高级程序设计 阅读笔记(十二) js内置对象Math

    Math对象的属性 E:值e,自然对数的底 LN10:10的自然对数 LN2:2的自然对数 LOG2E:以2为底E的对数 LOG10E:以10为底E的对数 PI:值派 SQRT1_2:1/2 的平方根 SQRT2:2的平方根 Math对象的方法:最大值与最小值 min()&&max()用于取一组数中的最小值跟最大值。 ···

    PHP基于工厂模式实现的计算器实例

    本文实例讲述了PHP基于工厂模式实现的计算器。分享给大家供大家参考。具体如下: abstract class Calculator { private $number1; private $number2; public $result; /** * @return the $number2 */ public function getNumber2() { return $this->number2; ···

    Javascript中的函数声明与函数表达式(奇技淫巧)

    举一个例子: [Ctrl+A 全选 注:如需引入外部Js需刷新才能执行] 试一下就知道这段代码的意思就是声明一个函数,然后立刻执行,因为Javascript中的变量作用域是基于函数的,所以这样可以避免变量污染,但这里的位运算符“~”乍一看让人摸不到头脑,如果去掉它再运行则会···

    原生js基于canvas实现一个简单的前端截图工具代码实例

    这篇文章主要介绍了原生js基于canvas实现一个简单的前端截图工具代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 先看效果 代码如下 <!DOCTYPE html> <html> <head> <meta charset="···

    Access创建一个简单MIS管理系统

    所谓MIS管理系统,是一个由人、计算机及其他外围设备等组成的能进行信息的收集、传递、存贮、加工、维护和使用的系统。MIS管理系统是一种新兴的技术,那么下文中就给大家介绍Access这个有历史的数据库系统如何创建一个简单的MIS管理系统。   MIS管理系统也是一种很···

    PHP使用函数用法详解

    1.php_check_syntax 这个函数可以用来检查特定文件中的PHP语法是否正确。 <?php $error_message = ""; $filename = "./php_script.php"; if(!php_check_syntax($filename, &$error_message)) { echo "Errors were found in the file $filename: $error_ } else ···