mysql的timestamp会存在时区问题?

相对于某一时区的时间,是本地时间,比如东8区的2020-02-23 08:00:00,是中国人的本地时间,而在此时,日本人的本地时间是2020-02-23 09:00:00,所以本地时间都是与某一时区相关的,脱离时区看本地时间,是没有意义的,因为你并不知道这具体是指的什么时间点。

比如在Java中,Date对象是绝对时间,通过SimpleDateFormat格式化出来的yyyy-MM-dd HH:mm:ss形式的时间字符串,是本地时间,如果SimpleDateFormat没有调用setTimeZone()显示指定时区,那么默认用的是jvm运行在的操作系统上的时区,我们开发机上的时区基本都是GMT+8

timestamp与datetime区别


如下,我创建了一张表,里面time_stamp是timestamp类型,date_time是datetime类型,create_timestamp、create_datetime是timestamp与datetime类型,但是它们可以由数据库自动生成。

CREATE TABLE time_test (

id bigint unsigned,

time_stamp timestamp,

date_time datetime,

create_timestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’,

create_datetime datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’,

PRIMARY KEY (id)

)

复制代码

1、首先将数据库时区设置为+8:00,即中国的东8区

set_timezone

2、然后如下手动插入一个固定时间的数据,以及用now()函数插入当前时间

insert_data

3、当插入完数据后,然后我们修改当前会话的时区为+9:00,即日本的东9区,然后再次查看数据

mod_timestamp

4、如上,定义为timestamp类型的列time_stampcreate_timestamp不管是手动插入的,还是now()函数插入的,东9区都比东8区的时间大1个小时,这是正确的,说明timestamp类型是时区相关的,然而定义为datetime类型的date_timecreate_datetime字段,时间都没有变化,这说明datetime类型是时区无关的。

结论:

timestamp在存储上是包含时区的,而datetime是不包含时区,说明网上的第一种说法是对的。

再看个例子

我们将东8区的的2020-02-23 08:00:00转换为unix时间缀(绝对时间),再插入数据库试试?

如下,使用linux的date命令转换时间串为unix时间缀:

$ “date” --date=“2020-02-23 08:00:00 +08:00” +%s

1582416000

复制代码

然后用mysql的from_unixtime()函数,将unix时间缀转换为mysql时间类型来插入数据。

from_unixtime

如上,查询出来的时间,也是东9区的9点,时间也是正确的。

为什么网上又说timestamp类型存在时区问题?


我发现网上说timestamp有时区问题,都是应用端插入数据,然后到数据库中去看,结果发现时间不一样,因此我打算在Java中写个Demo试一下,看能不能重现这个问题。

1、首先,下面是Java中Entity的定义,与上面的time_test表对应,注意,这里面时间属性都是用Date类型定义的,如下:

TimeTest

2、然后,我写了两个接口/insert/queryAll来插入与查询数据,如下:

insert_queryAll

3、然后我把数据库的时区设置为+09:00时区,即日本的东9区,如下:

set_timezone

4、然后调用/insert接口插入数据,注意我接口传入的时间是东8区的8点,如下:

postman_insert

5、插入完后,去数据库中查询一把,如下:

query_insert

可以看到,time_stamp字段时间是9点,且我已将数据库时区设置为东9区,东9区的9点与东8区的8点,这两个时间实际是相等的,因此时间数据没错。

6、然后我使用/queryAll接口将数据查询出来,如下:

postman_queryAll

timeStamp属性是1582416000000,这是毫秒级的时间缀,秒级则是1582416000,对应是东8区的2020-02-23 08:00:00,时间数据也没错!

7、然后我又将mysql时区修改回+8:00,并重启我们的java应用,如下:

set_timezone

8、再查询一下数据,如下:

postman_queryAll

timeStamp属性还是1582416000000,时间没有变化,这也是正确的。

那为什么网上会说timestamp存在时区问题?


经过一翻查看,我发现他们都提到了jdbc的serverTimezone,会不会是这个配置错误导致的呢?就先试试吧!

1、如图,我把数据库时区修改回+9:00时区,然后故意把jdbc的url上的serverTimezone配置为与数据库不一致的GMT+8时区,然后重启java应用,如下:

set_timezone

url: jdbc:mysql://localhost:3306/testdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8

复制代码

其中GMT%2B8就是GMT+8,因为在url上需要urlencode,所以就变成了GMT%2B8

2、重新插入数据,注意插入的时间还是东8区的8点,如下:

postman_insert

3、然后,我再到数据库中查询一把,如下:

query_insert

time_stamp中时间竟然是8点!要知道我们虽然插入的是东8区的8点,但当前会话可是东9区的,东8区的8点等于东9区的9点,所以正确显示应该为9点才对,时间差了1小时!

4、然后,我又调用/queryAll接口查询了一把,想看看mybatis查询出来的时间数据对不对,如下:

postman_queryAll

可以看到timeStamp1582416000000,秒级是1582416000,这个时间就是东8区的8点,东9区的9点啊!查询出来的时间竟然是正确的,为什么???

serverTimezone的本质


为了找出问题所在,我调试了一下mysql的jdbc驱动代码,终于弄明白了原因,主要可以看看如下这几点:

1.mysql驱动创建连接后,会调用com.mysql.jdbc.ConnectionImpl#configureTimezone()来配置此连接的时区,如果配置了serverTimezone,则会使用serverTimezone配置的时区,没配置时会去取数据库中的time_zone变量,这就是为什么我们没有配置serverTimezone变量时,结果也是正确的。

//若使用普通驱动,使用此方法配置mysql连接的时区

com.mysql.jdbc.ConnectionImpl#configureTimezone()

//若使用cj驱动,使用此方法配置mysql连接的时区

com.mysql.cj.protocol.a.NativeProtocol#configureTimezone()

复制代码

2.调用jdbc的setTimestamp()方法时,实际调用的是com.mysql.cj.jdbc.ClientPreparedStatement#setTimestamp(),这里面会根据serverTimezone指定的时区,将对应的Timestamp对象转换为serverTimezone指定时区的本地时间字符串。

3.执行sql语句时,会执行com.mysql.cj.jdbc.ClientPreparedStatement#execute(),这里面sendPacket变量保存着真实会发送到mysql的sql语句。

注:看的是8.0.11版本mysql-connector-java驱动源码,不同版本代码会稍有差异,比如5.2.16版本驱动,jdbc url上需要同时配置这两个配置:useTimezone=true&serverTimezone=GMT%2B8,且setTimestamp()对应的是com.mysql.jdbc.PreparedStatement#setTimestampInternal方法。

原理总结如下:

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

因此,当serverTimeZone与数据库实际时区不一致时,会发生时区转换错误,导致时间偏差,如下:

a、比如sql参数是一个Date对象,时间值是东8区的2020-02-23 08:00:00,注意它里面存储的可不是2020-02-23 08:00:00这个字符串,它是Date对象(绝对时间),只是我用文字表达出来是东8区的2020-02-23 08:00:00

b、然后,由于serverTimeZone配置的是东8区,mysql驱动会将这个Date对象转为2020-02-23 08:00:00,注意这时已经是字符串了,然后再将sql发送给mysql,注意这里的sql里面已经将Date参数替换为2020-02-23 08:00:00了,因为Date对象本身是无法走网络的。

c、然后mysql数据库接收到这个时间字符串2020-02-23 08:00:00后,由于数据库时区配置是东9区,它会认为这个时间是东9区的,它会以东9区解析这个时间字符串,这时数据库保存的时间是东9区的2020-02-23 08:00:00,也就是东8区的2020-02-23 07:00:00,保存的时间就偏差了1个小时。

d、查询结果里时间为什么又对了呢,因为查询结果返回了东9区的时间字符串,而java应用又将其理解为是东8区的时间,负负得正了!

将serverTimezone与mysql时区保持一致

so,那么如果我们将serverTimezone配置改正确,即与数据库保持一致时,应该查询到的时间就会是错的,会少1个小时。

1、jdbc url中使用与数据库一样的东9区GMT+9,如下:

url: jdbc:mysql://localhost:3306/testdb?serverTimezone=GMT%2B9&useUnicode=true&characterEncoding=utf8

复制代码

其中的GMT%2B9,即是GMT+9

2、然后重启Java应用,再查询一把看看,如下:

postman_queryAll

返回的是毫秒级时间缀1582412400000,秒级就是1582412400,使用linux的date命令转换为时间字符串形式:

$ “date” --date=“@1582412400” +“%F %T %z”

2020-02-23 07:00:00 +0800

复制代码

看到没,它是东8区的7点,刚好差了1个小时。

3、所以,使用mysql的timestamp类型时,对于java应用来说,一定要保证jdbc url中的serverTimezone与数据库中的时区配置是一致的。

另外一点是,当没有配置serverTimezone时,mysql驱动会自动读取mysql server中配置的时区,这里面也有坑!如下:

mysql驱动自动读取数据库时区的坑

3.1 mysql安装好后,默认时区是SYSTEM,而SYSTEM指的是system_time_zone变量的时区,如下:

3.2 当mysql驱动读到time_zone变量是SYSTEM时,会再去读取system_time_zone变量,而system_time_zone对于国内来说,默认是CST,这是一个混乱的时区,是4个不同时区的缩写,如下:

cst

对于Linux或MySQL,会认为CST是中国标准时间(+8:00),但Java却认为CST是美国标准时间(-6:00)(注:可能和Java运行在Windows中有关):

如下,linux中CST等于+0800,即中国时区:

$ “date” +“%F %T %Z %z”

2021-09-12 18:35:49 CST +0800

复制代码

如下,java中CST等于-06:00,美国时区:

java_timezone

3.3 因此mysql驱动取到CST这个时区值时,它会以为这是-6:00时区,但MySQL却理解为+8:00时区,因此MySQL时区一定不要配置为CST,而要配置为具体的时区,如+8:00,但如果MySQL时区为CST且不可修改的情况下,一定要配置jdbc的serverTimezone为清晰的时区(如:GMT+8)。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

经过日积月累, 以下是小编归纳整理的深入了解Java虚拟机文档,希望可以帮助大家过关斩将顺利通过面试。
由于整个文档比较全面,内容比较多,篇幅不允许,下面以截图方式展示 。







由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
多,篇幅不允许,下面以截图方式展示 。

[外链图片转存中…(img-RdD8a3c1-1712051423850)]
[外链图片转存中…(img-IjMw1Zd5-1712051423851)]
[外链图片转存中…(img-jAoo4h0z-1712051423851)]
[外链图片转存中…(img-2lfkB8r8-1712051423852)]
[外链图片转存中…(img-8K5CUj12-1712051423852)]
[外链图片转存中…(img-KFiRQEGH-1712051423852)]
[外链图片转存中…(img-p5HLZLIE-1712051423853)]

由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值