问题描述

最近经业务部门反馈,我们有个 APP 升级后,在 2023-01-09 22:47 发现,当前账号的最近登录时间为 2023-01-10 03:47,相差了 5 个小时。

  1. 第一轮排查:我们第一印象会认为是刚升级的代码出了问题,有可能是日期格式化没处理好,也有可能是 JSON 序列化没有指定时区,经过 Git diff 排查,我们并没有修改相关代码。
  2. 第二轮排查:可能是有人动了生产配置!排除了配置中心的改动,也检查了 K8s 的 Deployment 是否正确设置了 TZ=Asia/Shanghai,依然无果。
  3. 第三轮排查:提取线上部署的 jar,经过对比,我们发现 MySQL 驱动从原来的 5.1.45 升级到了 8.0.22 版本。

原因分析

  1. 生产数据库的配置是 time_zone=SYSTEM(对应 system_time_zone=CST,CST 时区没有标准,我们在使用 Java 客户端连接默认会把 CST 解析为时区+3,也就是当时事故出现的 5 小时误差)。

  1. 应用配置连接数据库的 jdbc-url 没有显示设置时区。MySQL驱动如果升级版本到 8.0.11 和 8.0.22 区间,jdbc-url 必须显式设置时区。从 8.0.23 版本开始,官网修复了这个问题,不再需要显示设置时区。


从下面对比 8.0.22 和 8.0.23 版本的变化,可以看到官网把 8.0.22 版本获取 MySQL 服务端的代码移除了,默认获取客户端自身的系统变量,例如,在 K8s 环境设置的 TZ=Asia/Shanghai变量。

为什么 5.1.45 版本没有这个问题呢?从源码可以看出,5.1.x 版本,不显示设置时区,代码逻辑会直接绕过设置。

在我们拿到 PreparedStatement 设置插入的字段会自动使用我们程序所在的系统时区。

解决方案

备选方案 优缺点
显示设置 jdbc-url 字符集:serverTimezone=GMT%2B8 推荐,不用改动程序
升级 MySQL 驱动到 8.0.23 以上 变更太大,需要重新发布生产