浅谈mysql的timestamp存在的时区问题 |
简介众所周知,mysql中有两个时间类型,timestamp与datetime,但当在网上搜索timestamp与datetime区别时,会发现网上有不少与时区有关的完全相反的结论,主要两种: timestamp没有时区问题,而datetime有时区问题,原因是timestamp是以UTC格式存储的,而datetime存储类似于时间字符串的形式,示例博文:MySQL 中 datetime 和 timestamp 的区别与选择timestamp也有时区问题,示例博文:mysql中timestamp时区的问题 两种观点让人迷惑,那timestamp到底会不会有时区问题呢? 答:因为mysql数据库未指定所在时区默认为美国中部时间 serverTimezone=Asia/Shanghai show variables like ‘%time_zone%'; set time_zone='+08:00'; select now(); 基本概念时区: 绝对时间: 如unix时间缀,是 本地时间: 相对于某一时区的时间,是本地时间,比如东8区的 比如在Java中, 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区 2、然后如下手动插入一个固定时间的数据,以及用now()函数插入当前时间 3、当插入完数据后,然后我们修改当前会话的时区为 4、如上,定义为 结论: 再看个例子 $ "date" --date="2020-02-23 08:00:00 +08:00" +%s 1582416000 然后用mysql的 如上,查询出来的时间,也是东9区的9点,时间也是正确的 。 为什么网上又说timestamp类型存在时区问题?我发现网上说timestamp有时区问题,都是应用端插入数据,然后到数据库中去看,结果发现时间不一样,因此我打算在Java中写个Demo试一下,看能不能重现这个问题 。 1、首先,下面是Java中Entity的定义,与上面的time_test表对应,注意,这里面时间属性都是用Date类型定义的,如下: 2、然后,我写了两个接口 3、然后我把数据库的时区设置为 4、然后调用 5、插入完后,去数据库中查询一把,如下: 可以看到,time_stamp字段时间是9点,且我已将数据库时区设置为东9区,东9区的9点与东8区的8点,这两个时间实际是相等的,因此时间数据没错 。 6、然后我使用
7、然后我又将mysql时区修改回 8、再查询一下数据,如下:
那为什么网上会说timestamp存在时区问题?经过一翻查看,我发现他们都提到了jdbc的 url: jdbc:mysql://localhost:3306/testdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8 其中 2、重新插入数据,注意插入的时间还是东8区的8点,如下: 3、然后,我再到数据库中查询一把,如下:
4、然后,我又调用 可以看到 serverTimezone的本质为了找出问题所在,我调试了一下mysql的jdbc驱动代码,终于弄明白了原因,主要可以看看如下这几点: 1.mysql驱动创建连接后,会调用 //若使用普通驱动,使用此方法配置mysql连接的时区 com.mysql.jdbc.ConnectionImpl#configureTimezone() //若使用cj驱动,使用此方法配置mysql连接的时区 com.mysql.cj.protocol.a.NativeProtocol#configureTimezone() 2.调用jdbc的 3.执行sql语句时,会执行
原理总结如下: 因此,当serverTimeZone与数据库实际时区不一致时,会发生时区转换错误,导致时间偏差,如下: 将serverTimezone与mysql时区保持一致so,那么如果我们将serverTimezone配置改正确,即与数据库保持一致时,应该查询到的时间就会是错的,会少1个小时 。 1、jdbc url中使用与数据库一样的东9区 url: jdbc:mysql://localhost:3306/testdb?serverTimezone=GMT%2B9&useUnicode=true&characterEncoding=utf8 其中的 2、然后重启Java应用,再查询一把看看,如下: 返回的是毫秒级时间缀 $ "date" --date="@1582412400" +"%F %T %z" 2020-02-23 07:00:00 +0800 看到没,它是东8区的7点,刚好差了1个小时 。 3、所以,使用mysql的timestamp类型时,对于java应用来说,一定要保证jdbc url中的serverTimezone与数据库中的时区配置是一致的 。 mysql驱动自动读取数据库时区的坑 3.1 mysql安装好后,默认时区是 3.2 当mysql驱动读到time_zone变量是 对于Linux或MySQL,会认为CST是中国标准时间(+8:00),但Java却认为CST是美国标准时间(-6:00)(注:可能和Java运行在Windows中有关): $ "date" +"%F %T %Z %z" 2021-09-12 18:35:49 CST +0800 如下,java中CST等于 3.3 因此mysql驱动取到CST这个时区值时,它会以为这是 Entity中日期属性是String呢?1、我们将Entity对象中的时间属性改为String(不推荐),如下: 2、然后也写两个接口, 3、然后插入数据,注意这时我是直接将无时区的8点,作为参数给到sql的,如下: 4、然后再查询一把,如下: 如上所示,time_stamp字段值是8点,但此时数据库时区是东9区,所以这是东9区的8点 。 url: jdbc:mysql://localhost:3306/testdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8 6、再次插入数据,参数还是无时区的8点,如下: 7、再查询一把,如下: 如上所示,time_stamp字段值是8点,但现在数据库时间是东8区,所以这是东8区的8点 。 url: jdbc:mysql://localhost:3306/testdb?serverTimezone=GMT%2B9&useUnicode=true&characterEncoding=utf8 现在serverTimezone与数据库中不一致,数据库是东8区,serverTimezone是东9区 。 10、然后再查询一把,如下: time_stamp字段值还是8点,数据库是东8区,所以这是东8区的8点,但我们serverTimezone与数据库的时区不一致啊,没看到时间有偏差,为什么? 解释一下 这样的话,似乎java中日期类型用时间字符串来存还好些,不容易出错,但请再认真考虑一下,调用方传了一个无时区的8点,数据库自作主张,就将其认为是东9区的8点,但如果这个时间字符串实际是东8区的8点呢?这时如果保存到数据库中为东9区的8点,那数据就存错了! 那如果目前api接口就传的无时区的时间串,Entity中就定义的String,怎么解决呢? SimpleDateFormat sdf = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss'); sdf.setTimeZone(TimeZone.getTimeZone("GMT+8")); Date date = sdf.parse("2020-02-23 08:00:00"); 3、然后如果Entity中时间属性定义的是String,那么我们要再将Date对象以数据库的时区格式化为对应的时间字符串,比如数据库时区是东9区,那么格式化后就是 SimpleDateFormat sdf = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss'); sdf.setTimeZone(TimeZone.getTimeZone("GMT+9")); String dateStr = sdf.format(date); entity.setTimeStamp(dateStr); 4、然后将Entity保存到mysql中的,就也会是东9区的2020-02-23 09:00:00,结果正确 。 所以,使用String类型来存储时间数据,要想将时间值保存正确,超级麻烦,不建议在实际开发中这种使用 。 最佳实践1、大多数团队会规定api中传递时间要用unix时间缀,因为如果你传一个 2、Mybatis中Entity定义要与数据库定义一致,数据库中是timestamp,那么Entity中要定义为Date对象,因为mysql驱动在执行sql时,会自动根据serverTimezone配置帮你转换为数据库时区的时间串,如果你自己来转换,你极有可能因为忘记调用 3、jdbc的serverTimezone参数,要配置正确,当不配置时,mysql驱动会自动读取mysql server的时区,此时一定要将mysql server的时区指定为清晰的时区(如: 4、如果数据库时区修改后,jdbc的serverTimezone也要跟着修改,并重启Java应用,就算没有配置serverTimezone,也需要重启,因为mysql驱动初始化连接时,会将当前数据库时区缓存到一个java变量中,不重启Java应用它不会变 。 数据库中用timestamp还是int来存储时间?如果用int型时间缀存储,不管数据库时区是啥,都不影响,因为存储的是绝对时间,看起来完美解决了时区问题 。 另外,用int存储时间还有如下3个不好的点: 开发人员看到这个字段后,无法一目了然的了解到这个时间缀大概是个什么时间,需要去转换一下,会很繁琐 。像 当然,也并不是建议不用int,这是见仁见智的,不管用timestamp还是int,都没有致命性问题的 。 总结timestamp本身是没有时区问题的,时区问题是由于serverTimezone配置错误、mysql使用CST这种混乱时区或Entity中将日期定义String类型导致的 。 到此这篇关于浅谈mysql的timestamp存在的时区问题的文章就介绍到这了,更多相关mysql timestamp时区问题内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持! |