事情背景
最近做了一个小程序商城项目(点击可查看,欢迎关注交流),测试时发现mysql数据库中的时间字段有的比当前北京时间晚8个小时,有的却是正常的,不用想肯定是时区问题,但诡异的是并非所有时间都有问题... 经过一番排查,发现问题的根本在于小小的时区问题牵涉的不止数据库,这里的坑还真不少,索性总结一下。
项目系统配置介绍
先简单交代一下这个项目相关的配置:
1、后端使用Java springboot框架,连接mysql的配置如下
jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
2、mysql是用docker直接部署的,启动命令如下:
docker run --rm --name mysql-test -e MYSQL_ROOT_PASSWORD=123456 -d mysql:latest
mysql时间字段类型是datetime,用CURRENT_TIMESTAMP设置了默认值,例如:
`add_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
问题分析
我们先分析一下都有哪些地方涉及时区的问题。
1、mysql本身的时区
我们登录mysql看看时区是啥,查询时区的语句如下:
mysql> show variables like '%time_zone%';
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | UTC |
| time_zone | SYSTEM |
+------------------+--------+
2 rows in set (0.29 sec)
mysql> select now();
+---------------------+
| now() |
+---------------------+
| 2021-05-12 11:41:05 |
+---------------------+
1 row in set (0.01 sec)
果然,time_zone说明mysql使用system的时区,system_time_zone说明system使用UTC时区,查到的时间确实晚了8个小时。
现在确定了mysql的时区确实有问题,但依然无法解释为啥有的数据时间是正常的
2、jdbc连接设置
我们再来看看jdbc连接串,发现其中有一个时区参数serverTimezone=Asia/Shanghai。
jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
3、分析结论
现在,就可以解释为啥有的时间正常,有的不正常了:
- 由于mysql本身的时区有问题,所以用CURRENT_TIMESTAMP设置了默认值的字段是通过mysql写入的自然会有问题
- 由于jdbc连接串设置了时区参数,所以通过Java程序写入数据库的时间是正常的
由此,我们可以看到时区的问题涉及到了mysql本身及其所处的系统(这里是docker,docker镜像默认的时区就是UTC),还有jdbc代码层面。下面就从这3个方面具体说说怎么彻底解决时区问题
解决方案
1、设置docker系统的时区
我们首先要保证mysql所处系统(这里是docker)的时区是正确的,设置docker的时区有两种方式
方法一:映射本机的/etc/localtime(这种方式要保证docker所处的系统时区是正确的)
docker run --rm --name mysql-test -e MYSQL_ROOT_PASSWORD=123456 -v /etc/localtime:/etc/localtime -d mysql:latest
方法二:设置环境变量-e TZ=Asia/Shanghai
docker run --rm --name mysql-test -e MYSQL_ROOT_PASSWORD=123456 -e TZ=Asia/Shanghai -d mysql:latest
修改启动命令后再来看看mysql的时区
mysql> show variables like '%time_zone%';
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | CST |
| time_zone | SYSTEM |
+------------------+--------+
2 rows in set (0.00 sec)
好了,变成CST了,看看生成的时间好像一切都正常了,但是真的一定没问题吗,搜索一下CST的含义你就会发现问题。原来CST居然能表示四种时区,分别为:
1.美国中部时间 Central Standard Time (USA) UTC-05:00 / UTC-06:00
2.澳大利亚中部时间 Central Standard Time (Australia) UTC+09:30
3.中国标准时间 China Standard Time UTC+08:00
4.古巴标准时间 Cuba Standard Time UTC-04:00
这就导致jdbc去获取时区的时候,有可能就会出现问题,比如有可能把CST解析为UTC-5或者UTC-6时区,所以设置jdbc连接的时区参数也是很有必要的。
2、设置mysql的时区
通过第一步我们发现仅仅设置系统的时区是不够的,所以强烈建议把mysql的时区直接设置为+8:00(即我们所处的东8区),修改mysql的时区也有两种方式,可看情况使用
1.临时修改
set global time_zone = '+8:00'; #修改mysql全局时区为北京时间,但是重启mysql服务这个设置依然会失效
set time_zone = '+8:00'; #修改当前会话时区
flush privileges; #立即生效
2.永久修改
修改配置文件【linux:my.cnf,windows:my.ini】
在[mysqld]区域部分加上:default-time_zone = '+8:00'
然后重启mysql服务即可。
修改后看一下mysql时区,变成了+08:00
mysql> show variables like '%time_zone%';
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | CST |
| time_zone | +08:00 |
+------------------+--------+
2 rows in set (0.11 sec)
3、设置jdbc连接的时区参数
从我这个事情可以看出,不管mysql的时区对不对,如果jdbc读到的时区不正确就会导致写入数据库的时间不对,所以仅仅通过前两步并不能保证万无一失,因此一定要在jdbc连接串中加上时区参数serverTimezone=Asia/Shanghai
最后概括一下避免时区问题的方法:
- 首先保证系统时区正确
- jdbc连接中添加时区参数serverTimezone=Asia/Shanghai
- time_zone参数建议尽量设置为+8:00,不要使用有歧义的CST(即使是系统默认的_SYSTEM_值)。另外尽量使用datetime类型而不是timestamp类型,这个建议跟字段类型也有关系,这里就不展开阐述了,如有问题欢迎与我交流
文章开头提到的项目地址,欢迎关注交流:
Github:github.com/frank-say/b…
Gitee:gitee.com/frank-say/b…
点击体验