一、说明
同事问我数据库的时区是客户端属性还是服务端属性,我觉得这个问题十分有意思,之前没怎么留意,自己来做下实验。
首先介绍几个术语。
GMT(Greenwich Mean Time),格林尼治平均时间。
UTC(Coordinated Universal Time),协调世界时。
CST(China Standard Time),中国标准时间,也称北京时间。
根据维基百科,格林尼治标准时间的正午是指当平太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治平时基于天文观测本身的缺陷,目前已经被原子钟报时的协调世界时(UTC)所取代。协调世界时是世界上调节时钟和时间的主要时间标准,计算机中的网络时间协议(NTP, Network Time Protocol)就是用的协调世界时。不过,GMT和UTC相差不超过1秒,对绝大多数系统来说,可以认为这两个时间等价。中国的标准时间就是在UTC上面加8小时。
二、测试
测试使用Mysql8,CentOS7,Jdbc
我现在的时间是北京时间 2023-4-22 19:42,我将服务器的时间设置成任意一个时间,时区设置成东京,即(UTC+9)。
[root@xbz ~]# timedatectl list-timezones // 列出所有时区
[root@xbz ~]# timedatectl set-timezone Asia/Tokyo // 设置成东京时区
[root@xbz ~]# date -s '2023-4-20 15:00:00' // 设置任意时间
[root@xbz ~]# date -R // 查看系统时间
Thu, 20 Apr 2023 15:00:41 +0900
将Mysql数据库重启,Mysql关于时区有两个参数
system_time_zone 系统时区,在Mysql启动时会检查当前系统的时区并根据系统时区设置。参数不可修改。
time_zone 会话时区,默认为system,即使用全局参数system_time_zone的值。可以动态修改。
(root@localhost)[(none)]> show variables like '%zone%';
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | JST |
| time_zone | SYSTEM |
+------------------+--------+
会话时区的修改可以使用命令set global time_zone='+8:00'修改,或者在参数文件中设置default_time_zone='+8:00'。为了实验,我将客户端的时区设置为'+11:00'。
(root@localhost)[(none)]> set global time_zone='+11:00';
(root@localhost)[(none)]> show variables like '%zone%'; // 再开一个窗口查询
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | JST |
| time_zone | +11:00 |
+------------------+--------+
数据库服务器在UTC+9,数据库客户端时区设置的是UTC+11。分别查服务器的时间和数据库的时间,数据库的时间相比服务器的时间要快了2小时,这就是参数time_zone在起作用。
[root@xbz ~]# date -R
Thu, 20 Apr 2023 15:10:58 +0900(root@192.168.1.82)[(none)]> select sysdate();
+---------------------+
| sysdate() |
+---------------------+
| 2023-04-20 17:10:45 |
+---------------------+
再来看一个例子,我在数据库中分别创建一个datetime和timestamp两个字段,插入同一个时间。然后将客户端的时区换一下,会发现timestamp的时间也跟着发生变化了,这其实是由于MySQL将timestamp列的值从当前时区转换为UTC时间进行存储,查询时,将数据从UTC转换为检索的当前时区。
(root@192.168.1.82)[hello]> create table t1(id int, dt datetime, tt timestamp);
(root@192.168.1.82)[hello]> insert into t1 values (1, '2022-01-01 12:00:00', '2022-01-01 12:00:00');
(root@192.168.1.82)[hello]> select * from t1;
+------+---------------------+---------------------+
| id | dt | tt |
+------+---------------------+---------------------+
| 1 | 2022-01-01 12:00:00 | 2022-01-01 12:00:00 |
+------+---------------------+---------------------+(root@192.168.1.82)[hello]> set time_zone='+12:00';
(root@192.168.1.82)[hello]> select * from t1;
+------+---------------------+---------------------+
| id | dt | tt |
+------+---------------------+---------------------+
| 1 | 2022-01-01 12:00:00 | 2022-01-01 13:00:00 |
+------+---------------------+---------------------+
接下来测试下Jdbc中的表现
public class JdbcTimeZoneTest {public static void main(String[] args) throws Exception {String user = "scott";String password = "tiger";String driver = "com.mysql.cj.jdbc.Driver";String url = "jdbc:mysql://192.168.1.82/hello"; Class.forName(driver);Connection connection = DriverManager.getConnection(url, user, password);String sql = "select sysdate()";Statement statement = connection.createStatement();ResultSet resultSet = statement.executeQuery(sql);while (resultSet.next()) {System.out.println(resultSet.getTimestamp(1));}}
}
运行结果:
2023-04-20 19:02:44.0
mysql客户端的结果:
(root@192.168.1.82)[(none)]> select sysdate();
+---------------------+
| sysdate() |
+---------------------+
| 2023-04-20 19:02:47 |
+---------------------+
可以看到jdbc跟mysql客户端显示的时间一致。
url加入这个参数?serverTimezone=Asia/Shanghai
运行结果:
2023-04-20 19:03:19.0
可以看到时间好像没什么变化
当我把jdbc所运行的机器时间调整成(UTC+3)
再运行一次,结果:
2023-04-20 14:04:17.0
mysql客户端的结果:
(root@192.168.1.82)[(none)]> select sysdate();
+---------------------+
| sysdate() |
+---------------------+
| 2023-04-20 19:04:35 |
+---------------------+
jdbc所显示的时间为什么会比mysql客户端显示的时间慢5个小时呢,这是由于serverTimezone=Asia/Shanghai是在东八区,而我的本机设置的时区是东三区,差了5个小时,所以相较于Mysql客户端就有5个小时的差距。所以serverTimezone要么不设置,要么设置成jdbc所运行的机器的时区。
三、总结
- 设置好服务器的时区和mysql的时区,避免一些不必要的坑。