使用java代码里面传进去的LocalDateTime.now()方法。但是插入后数据我发现有问题,插入的时间比我当前的实际时间少14个小时。
一、问题现象:
java代码
User record = new User()
.setDeleted(Boolean.FALSE)
.setCreateTime(LocalDateTime.now())
.setUpdateTime(LocalDateTime.now());
userMapper.insert(record);
实际插入的数据库时间少了14个小时。
二、排查问题:
查询数据库时间和服务器时间是否一致:
服务器当前时间:
date
Tue Feb 11 16:54:24 CST 2025
数据库当前时间:
select now();
可以看出基本一致。不存在差距;
查询数据库的时区配置:
show variables like '%time_zone%';
可疑点:CST
三、查询验证: CST
百度百科的解释:
问一下DeepSeek:
CST(Central Standard Time) 是北美中部标准时间,属于 UTC-6 时区,主要应用于美国、加拿大、墨西哥等国家/地区。
- 夏令时切换:CST 在夏令时期间会切换为 CDT(Central Daylight Time,UTC-5),时间提前1小时。
- 夏令时的时间范围 (美国从“3月11日”至“11月7日”实行夏令时)
四、结论出来了:
我们国内使用的时区是: UT+8:00
当前时间是 2025年2月11日,不属于美国的夏令时。所以 美时区是 UTC-6 。
如何解析CST,取决于:Mysql驱动版本和逻辑。(第六点会详细解释原因)
显然,这里解析为了:北美CST America/Chicago
所以: 实际上数据库存入的时间和现在是差 14个小时。
问题找到了。怎么解决?
五、解决:
一、手动修改MySQL的时区
通过命令执行:
set global time_zone ="+08:00";
set time_zone = "+08:00";
Flush privileges;
通过修改my.cnf 配置文件:
[mysqld]
Default-time-zone = "+08:00"
二、在JDBC连接数据库的字符串中添加时区配置项:serverTimezone=Asia/Shanghai
**jdbc:mysql://***xxx***:3306/*database_name?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8
六、深度解析原因:
采用deepSeek 的回答。
ConnectionImpl.initializePropsFromServer()
中时区获取机制详解
在 MySQL JDBC 驱动(如 mysql-connector-java
8.0+)中,com.mysql.cj.jdbc.ConnectionImpl.initializePropsFromServer()
方法负责在建立数据库连接后,从 MySQL 服务器获取关键配置参数,包括时区信息。以下是其获取时区的详细流程及潜在问题:
1. 时区获取流程
当客户端(Java 应用)通过 JDBC 连接 MySQL 时,驱动会执行以下步骤获取时区:
- 查询服务器时区:
执行 SQL 语句SHOW VARIABLES LIKE 'time_zone'
,获取 MySQL 服务器的全局时区配置(如SYSTEM
或America/Chicago
)。 - 解析时区信息:
- 如果
time_zone
值为SYSTEM
,则进一步查询SHOW VARIABLES LIKE 'system_time_zone'
,获取操作系统时区(如CST
)。 - 驱动尝试将
system_time_zone
转换为 Java 可识别的时区 ID(例如CST
可能被解析为America/Chicago
或Asia/Shanghai
,取决于驱动版本和逻辑)。
- 如果
- 缓存时区:
最终时区信息会被缓存到连接属性中,供后续时间戳处理使用。
2. 潜在问题:CST 歧义性
CST 的歧义性是核心问题根源:
- MySQL 返回的
system_time_zone
可能是CST
,但它在不同上下文中代表不同时区:- 北美 CST:
UTC-6
(例如America/Chicago
)。 - 中国 CST:
UTC+8
(例如Asia/Shanghai
)。
- 北美 CST:
- 若驱动错误解析
CST
,会导致 Java 应用和数据库时区不一致,例如:- 数据库实际使用
America/Chicago
(UTC-6),但驱动误判为Asia/Shanghai
(UTC+8),导致时间差 14 小时。
- 数据库实际使用
3. 源码关键逻辑
在 ConnectionImpl.initializePropsFromServer()
中,时区处理的关键代码如下(简化版):
// 查询服务器时区配置
String timeZoneVariable = queryServerVariable("time_zone");
if ("SYSTEM".equals(timeZoneVariable)) {
String systemTimeZone = queryServerVariable("system_time_zone");
// 将 system_time_zone(如 CST)转换为 Java 时区 ID
TimeZone javaTimeZone = TimeZone.getTimeZone(systemTimeZone);
this.serverTimezoneTZ = javaTimeZone;
}
问题点:
TimeZone.getTimeZone("CST")
可能返回America/Chicago
(北美 CST)或Asia/Shanghai
(中国 CST),取决于驱动实现或 JVM 配置。- MySQL 驱动早期版本(如 5.x)默认将
CST
解析为Asia/Shanghai
,导致北美用户时间错误。
4. 解决方案
(1) 明确指定 MySQL 时区
在 MySQL 配置文件中(my.cnf
或 my.ini
)强制指定时区名称,避免使用歧义的 CST
:
[mysqld]
default-time-zone = 'Asia/Shanghai'
(2) JDBC 连接参数强制时区
在 JDBC URL 中通过 serverTimezone
参数覆盖驱动自动解析的时区:
jdbc:mysql://localhost:3306/db?serverTimezone=Asia/Shanghai
(3) 升级 MySQL 驱动
使用最新版驱动(如 8.0.28+),其对时区解析逻辑进行了优化:
<!-- Maven 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
(4) 验证时区解析结果
在代码中打印驱动解析的时区,确认是否与预期一致:
Connection conn = DriverManager.getConnection(url, user, password);
String serverTimezone = conn.getServerTimezone();
System.out.println("驱动解析的服务器时区:" + serverTimezone); // 应输出 Asia/Shanghai
5. 关键配置参数
参数 | 作用 |
---|---|
serverTimezone | 强制指定 MySQL 服务器时区,覆盖驱动自动解析逻辑。 |
useLegacyDatetimeCode | 设置为 false (默认值),启用新版时区处理逻辑,避免旧版驱动的 CST 解析问题。 |
connectionTimeZone | 高级参数,直接指定连接时区(需驱动版本支持)。 |
6. 总结
- 根本原因:
CST
的歧义性导致驱动解析时区错误。 - 解决关键:通过
serverTimezone
参数或 MySQL 配置强制指定时区,消除歧义。 - 验证手段:打印驱动解析的时区,确保与数据库实际时区一致。
通过上述方法,可确保 ConnectionImpl.initializePropsFromServer()
正确获取时区,避免数据库与 Java 应用时间差问题。