时区问题

名词解释

简称 全称 说明
GMT Greenwich Mean Time 格林威治标准时间 ; 英国伦敦格林威治定为0°经线开始的地方
UTC Coordinated Universal Time 世界协调时间;经严谨计算得到的时间,精确到秒,误差在0.9s以内, 是比GMT更为精确的世界时间
CST 四个不同时区的缩写:
1. Central Standard Time (USA) UT-6:00 美国标准时间
2. Central Standard Time (Australia) UT+9:30 澳大利亚标准时间
3. China Standard Time UT+8:00 中国标准时间
4. Cuba Standard Time UT-4:00 古巴标准时间
在中国说CST是UTC+8,在美国说CST是UTC-6...
DST Daylight Saving Time 夏季节约时间,即夏令时;是为了利用夏天充足的光照而将时间调早一个小时,北美、欧洲的许多国家实行夏令时


时区配置

查看linux时区

# date -R
Wed, 11 May 2022 10:27:53 +0800

查看mysql时区

SELECT @@GLOBAL.time_zone, @@SESSION.time_zone;

查看jvm时区

# jps
559464 Jps
288513 jar 
# ./jinfo 288513 | grep timezone
user.timezone = Asia/Shanghai

serverTimeZone参数

jdbc连接串中有一个serverTimeZone参数,作用是指定web服务器和mysql服务器的会话期间的mysql服务器时区,即临时指定mysql服务器的时区。

For Connector/J 8.0.23 and later, serverTimezone is an alias for connectionTimeZone. For Connector/J 8.0.22 and earlier, serverTimezone was used to override the session time zone setting on the server.
Former connection option 'serverTimezone' is still valid as an alias of this one but may be deprecated in the future.

serverTimeZone的值
LOCAL: 驱动程序假定连接时区与 JVM 默认时区相同
SERVER: 驱动程序会尝试从 MySQL 服务器会话变量 'time_zone' 或 'system_time_zone' 上配置的值中检测会话时区
自定义: 必须是JVM 和 MySQL 都支持的时区

不同时区配置的优先级

jvm启动参数 > jvm进程所在服务器时区
jdbc参数 > mysql本身配置 > mysql所在服务器时区


实验

根据若干实验,对比timestamp与datetime,以及LocalDateTime、Date、ZonedDateTime

实验一

新建一张test表,c_datetime列是datetime类型,c_timestamp是timestamp类型,插入一行数据

image.png

修改mysql时区,从GMT+8改成GMT+7,查询test表
image.png

实验结论

  1. datetime不受mysql修改时区影响,效果相当于字符串,存什么查出来就是什么
  2. timestamp会根据新的时区展示日期和时间

实验二

数据库中有一列datetime类型的列,值为2022-01-01 00:00:00
Entity用不同的类型接收这一列,结果如下

JVM时区 serverTimeZone String LocalDateTime Date ZonedDateTime
GMT+8 GMT+8 2022-01-01 00:00:00 2022-01-01T00:00:00 2021-12-31T16:00:00.000+00:00 2022-01-01T00:00:00+08:00
GMT+8 GMT+7 2022-01-01 00:00:00 2022-01-01T00:00:00 2021-12-31T17:00:00.000+00:00 2022-01-01T00:00:00+07:00

实验结论

  1. LocalDateTime本身无时区属性,相当于字符串
  2. 如果Entity用Date或ZonedDateTime接收datetime类型,要确保serverTimeZone不变,否则数据就出错了

实验三

数据库中有一列timestamp类型的列,值为2022-01-01T00:00:00+08:00
用不同的类型接收这一列,结果如下

JVM时区 serverTimeZone String LocalDateTime Date ZonedDateTime
GMT+8 GMT+8 2022-01-01 00:00:00 2022-01-01T00:00:00 2021-12-31T16:00:00.000+00:00 2022-01-01T00:00:00+08:00
GMT+8 GMT+7 2022-01-01 00:00:00 2022-01-01T00:00:00 2021-12-31T17:00:00.000+00:00 2022-01-01T00:00:00+07:00

实验结论

  1. 如果Entity用Date或ZonedDateTime接收timestamp类型,要确保serverTimeZone不变,否则数据就出错了

实验四

数据库中有一列timestamp类型的列,Entity通过ZonedDateTime接收。
此时JVM时区、serverTimeZone、mysql时区都是GMT+8

按以下步骤操作

  1. 修改mysql时区为GMT+7(global+session)
  2. insert 一行记录,ZonedDateTime值为2022-01-01T00:00:00+08:00
  3. timestamp列在GMT+7时区下,值为2022-01-01 00:00:00

问题来了,GMT+7时区下,timestamp列的值,为什么不是2021-12-31 23:00:00
(用Date类型也会有一样的问题)

原因如下:
mysql驱动在发送sql前,会将ZonedDateTime对象参数,根据serverTimeZone配置的时区转化为日期字符串后,再发送sql请求给mysql server。
同样在mysql server返回查询结果后,结果中的日期值也是日期字符串,mysql驱动会根据serverTimeZone配置的时区,将日期字符串转化为Date对象。

插入流程如下

image.png

此时,执行查询请求,Entity拿到的ZonedDateTime值为2022-01-01T00:00:00+08:00,没有问题,流程如下
image.png

接下来,把mysql时区转成GMT+8,跟serverTimeZone保持一致。
再次执行查询请求(需要重启springboot),最后Entity拿到的ZonedDateTime值为2022-01-01T01:00:00+08:00,是错误的,流程如下
image.png

实验结论

  1. serverTimeZone和mysql时区不一致时,用timestamp类型可能导致数据出错


总结

timestamp、datetime的对比

  1. timestamp本身没有时区问题,只是因为serverTimeZone和mysql时区不一致,才会有时区问题
  2. timestamp时区是明确的,datetime时区不明确,需要根据上下文才能知道时区
  3. timestamp不允许空值
  4. timestamp可读性不如datetime
  5. timestamp最大值是2038-01-19 11:14:07(GMT+8),如果有的业务场景需要存最大值之后的时间(比如房产证有效期截止日),就没法用timestamp

LocalDateTime、Date、ZonedDateTime的对比

  1. Date/ZonedDateTime时区是明确的,LocalDateTime时区不明确,需要根据上下文才能知道时区
  2. Entity使用Date/ZonedDateTime时,db到Entity有隐式转换,容易因为修改配置导致bug(参考实验二、实验三)
  3. ZonedDateTime可以在内存里表示一个非JVM时区的时间,而Date只能表示JVM时区时间,而且ZonedDateTime的API比Date更加灵活易用

强制约束

  1. jdbc参数serverTimeZone一定要配
    原因: 如果jdbc没配serverTimeZone,而且Entity使用Date/ZonedDateTime,一旦修改mysql server时区,数据就错了,而且mysql server的时区比较复杂(文件、全局配置、会话配置、混乱的CST),容易踩坑
  2. jvm启动参数user.timezone一定要配
    原因: 如果jvm没配user.timezone,则jvm默认时区取决于操作系统,操作系统的时区非常复杂,好几个文件和环境变量在控制,容易踩坑

建议约束

  1. 数据库时间类型使用datetime
  2. Entity时间类型使用LocalDateTime(LocalDate、LocalTime)或者ZonedDateTime

你可能感兴趣的:(时区问题)