• 13 mysql date/time/datetime/year 的数据存储


    前言

    这里主要是 由于之前的一个 datetime 存储的时间 导致的问题的衍生出来的探究 

    探究的主要内容为 int 类类型的存储, 浮点类类型的存储, char 类类型的存储, blob 类类型的存储, enum/json/set/bit 类类型的存储 

    本文主要 的相关内容是 datetime/date/time/year 类类型的相关数据的存储  

    这部分数据 客户端 和 服务器这边的交互 主要是以字符串的形式交互 

    服务器这边的存储 将日期编码为 整形进行存储 

     

    最近碰到了一个这样的一个问题

    主要是在一个 增量同步的地方, 增量同步 tableA, 增量查询字段为 create_time, 类型为 datetime

    然后 业务组件中 select max(create_time) from tableA 中查询到的最大时间 居然比实际的最大时间 多了 13 个小时, 呵呵

    然后 这就导致了 增量同步存在问题

    下一次增量同步的时候, 将 增量同步的最大值更新为了 13个小时以后, 这样 根据这个时间去做增量查询 接下来的 13 个小时的任务执行 都查询不到 需要增量同步的记录

    然后 最终是发现 服务器的 default-time-zone 被调整了, 重置一下 default-time-zone 即可 

    我这里 mysql 服务器版本为 5.7.31

     

     

    mysql 中 datetime 的数据交互

    创建表 以及插入测试数据

    1. CREATE TABLE `tz_test` (
    2. `id` int(11) NOT NULL AUTO_INCREMENT,
    3. `field1` datetime DEFAULT NULL,
    4. PRIMARY KEY (`id`)
    5. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    6. INSERT INTO `test`.`tz_test`(`id`, `field1`) VALUES (1, '2023-04-24 16:25:40');

     

    测试脚本如下 

    1. package com.hx.test07;
    2. /**
    3. * Test06MysqlTimezone
    4. *
    5. * @author Jerry.X.He
    6. * @version 1.0
    7. * @date 2023/4/24 16:26
    8. */
    9. public class Test06MysqlTimezone {
    10. // Test06MysqlTimezone
    11. public static void main(String[] args) {
    12. String url = "jdbc:mysql://10.60.50.16:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&autoReconnectForPools=true&useSSL=false";
    13. String username = "root";
    14. String password = "root";
    15. JdbcTemplate jdbcTemplate = Test14GenExpertSql.getMysqlJdbcTemplate(url, username, password);
    16. String sql = " select * from tz_test; ";
    17. List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
    18. int x = 0;
    19. }
    20. }

     

    抓包拿到 mysql 响应解析如下 

    可见这里 wireshark 解析貌似是存在问题的, 前两个 packet 对应的是 字段列表的元数据 

    第三个 packet 是具体的数据, 包含了 packetLength, packetNumber, 字段id, 字段field1

    第四个 packet 是 eof 包 

    6e2fe246a7f54cdfbf94b9c80d88cbe3.png

     

    业务查询从服务器拿到的数据部分的如下, 可以看到的是 和上面一致

    可以看到的是 datetime 的交互是通过字符串的形式交互的, 因此 存取的时候的转换都是服务器自己处理的

    第一个字节 01 表示 id 数据长度 1 

    第二个字节 31 表示 id 为 1

    第三个字节 13 表示 field1 数据长度 19

    剩余十九个字节为 “2023-04-24 16:25:40”

    dafa944c1509418e89b896f7c37eadff.png

     

    再看一下 insert into 存放数据的时候的相关 包的情况

    整个 sql 语句是通过字符串的形式 交互给 mysql, mysql 自行解析 处理

    0fc6a991a61d47fd9d8d01f8f968b71a.png

     

     

    所以 客户端这边拿到的 datetime 的字符串 取决于服务器的时区配置, 以及客户端请求的时区配置 

    我们这边调整一下 客户端这边的 serverTimezone 的配置, 整个 url 更新成为

    jdbc:mysql://10.60.50.16:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&autoReconnectForPools=true&useSSL=false&serverTimezone=GMT%2B2

     

    查看一下 服务器响应的数据

    服务器相应的依旧是字符串的 “2023-04-24 16:25:40”

    所以说这个 serverTimeZone 的转换是在客户端这边 

    4e5138b1ce484106b08682f75d851c7f.png

     

    客户端这边具体的时间转换是在这里, 服务器响应回来的时间是 “2023-04-24 16:25:40”

    按照 GMT+2 的时间转换为时间戳为 “1682346340906”

    换算成 我们 GMT+8 的时间为 “2023-04-24 22:25:40”

    d0589e27c7cb41a4b3463c572ee1df68.png

     

    具体获取时区的地方, 是从 session 的 serverTimeZone 获取的, 也就是我们上面指定的 serverTimeZone

    efa3073838554fe4bbd3c2a308d6ec9e.png 

    业务这边查询, 拿到的是时间戳为 “1682346340906”

    然后按照 GMT+8 转换一下 “2023-04-24 22:25:40”

    2dc21b19895447429a3188256169d67a.png

     

     

     调整 mysql 的 default_time_zone

    --default_time_zone='+02:00'

    增加默认时区的配置, 然后 再来查看一下 情况

    服务器这边 响应回来的数据, 依然是 “2023-04-24 16:25:40”

    22fe62b36a42400590e9b0cb50ed2390.png

     

    然后客户端这边 按照 GMT+2 转换为时间戳

    所以说 这里 serverTimeZone 可能取自两个地方, 一个是服务器, 一个是客户端 

    ceae95e1ab4f45828836c546b4f2e2a0.png

     

    可以看到这里 拿到的 field1 结果也是 

    GMT+2 的 “2023-04-24 16:25:40”

    GMT+8 的 “2023-04-24 22:25:40”

    5d132794f2cb4364beea0241b8dfc7af.png

     

    我们来看一下这里 获取 serverTimeZone 的地方 

    优先获取的是 客户端配置的 timeZone

    其次获取的是服务器的 timeZone, 有两种配置方式 

    80e2c12de1034155bacb7e9e2c08eac2.png

     

    服务器这边的变量是用如下 sql 查询的, 并注册到 serverVariables 中 

    8018e302d60e432e847fe1419bb69d84.png 

    查询的 sql 如下 

    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

    63f12e6a02c84a9ca16ca8b9f78f974a.png

     

     

    与客户端的交互, 获取给定的 datetime 的编码之后的 longlong 表示 

    然后再 解码为目标格式 的字符串表示

    cff79e3a68cf410d9589a4be7ef39b99.png

     

    格式化的模板为 yyyy-MM-dd HH:mm:ss

    a9f5d75dcaa94f20ae3e6006299e7a0a.png

     

     

    mysql 服务器 datetime 的数据存储 

    然后 我们来看一下 mysql 这边 datetime 字段的存储, 测试语句如下

    客户端交互 字符串过去, 服务端拿到 这个字符串会做什么处理呢?

    insert into tz_zone (field1) values ('2023-04-24 16:25:40');

     

    首先如下地方是 将字符串转换为 MYSQL_TIME 的结构体, 处理就是解析出 年月日, 时分秒 等等信息 放到 MYSQL_TIME 中

    bbb91bdd50154b2eb4f1cb6e66e35187.png

     

    解析之后 MYSQL_TIME 中存储了传入 时间所表示的 年月日, 时分秒 等等信息

    11ac378230ed46349e5e92235efeadde.png 

    这是具体的存储方案, 将 MYSQL_TIME 编码成为一个 longlong

    存储方案 在备注中 解释的很明确

    1. /*
    2. 1 bit sign (used when on disk)
    3. 17 bits year*13+month (year 0-9999, month 0-12)
    4. 5 bits day (0-31)
    5. 5 bits hour (0-23)
    6. 6 bits minute (0-59)
    7. 6 bits second (0-59)
    8. 24 bits microseconds (0-999999)
    9. Total: 64 bits = 8 bytes
    10. SYYYYYYY.YYYYYYYY.YYdddddh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff
    11. */

     13662b3fbc2e47d0ac80790cf2d420eb.png

     

     

    然后 响应就是一个逆向的过程

    首先 还原成 MYSQL_TIME

    然后 再转换成为字符串

    整个过程中可以理解为 对于时间字符串的 编码, 解码

    抽象这个编码解码的过程, 可以理解为 服务器这边 存放 datetime 就是存放的一个字符串, 所以说 服务器这边 datetime的存放是不涉及时区的

    28308994974f4d6c89f5ba945639b1b4.png

     

     

    mysql 中 date/time/year 的机制和 datetime 类似 

    com.mysql.cj.result 相关实体是将协议的 字节序列 转换为 具体的基础数据类型的数据

     

    date

    4f82c88026324af1a2f03752b6363951.png

     

    time

    c72646e5cc954311a176ff6bf3bb5213.png 

     year

    1cc77ae2c15e4759bc7e02ec928730ec.png

     

    timestamp

    1bd3f7e1d8aa467bad98f50873936c14.png

     

     

    date 的格式化 

    c2fc5482b45e48abad4c58c9bf7a3ca0.png

     

     

     year 的格式化

    abe0a39050b04a87a6b617594625a689.png

     

     


     

     

     

  • 相关阅读:
    Nginx实现本地http转https请求
    [附源码]计算机毕业设计物品捎带系统Springboot程序
    C# 判断电脑是否联网
    Echarts异步数据与动画加载
    Java基础--阳光总在风雨后,请相信彩虹
    【区块链】usdt充值 离线签名 离线生成地址
    智能催收系统中自然语言理解模块设计
    notepad++设置中文界面
    LeetCode回溯算法子序列问题——491. 递增子序列
    企业密码管理器
  • 原文地址:https://blog.csdn.net/u011039332/article/details/130496310