MySQL数据时区问题以及datetime和timestamp类型存储的差异
作者:世樹
MySQL数据时区问题及datetime和timestamp类型存储的差异
问题:
查询不同数据库上表中记录时间差距8小时。
昨天协助其他地区同事解决客户查询到不同数据中心时间差距8小时的问题。原因就是时区不同。
解决方案:
设置服务器的时区都为北京时间,即修改数据库服务器的time_zone值为“+8:00”解决。
这个参数,可以在通过mysqld命令启动数据库的时候加上参数 --default-time-zone=timezone来设置时区,
也可以通过my.cnf配置文件的[mysqld]标签里增加 default-time-zone='timezone'这一行来设置。
如果希望即时生效,也可以通过命令修改全局或者会话级别的time_zone的值:
命令如下:
修改全局time_zone的值 set global time_zone='+8:00'; 或 修改当前会话的time_zone set time_zone='+8:00';
其他:
因为使用的云数据库也有在海外地区的,所以针对我们有些表的字段是timestamp类型的情况和遇到的问题,也一起整理了一下。
测试环境为 Windows MySQL 5.7.22-log。
表结构如下:
CREATE TABLE `ee` ( `id` int(11) NOT NULL AUTO_INCREMENT, `date1` datetime(6) DEFAULT NULL, `date2` time(2) DEFAULT NULL, `date3` timestamp(3) NULL DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
当前数据库服务器的时区设置为(这是本次测试起始的系统时区情况):
即:system_time_zone参数值为+2:00。
test1
测试目的:同一个会话的time_zone值变化时,插入记录的时间显示的“不同”;
当前数据库time_zone参数情况如下;
root@iris>show global variables like'%time_zone%'; +------------------+--------+ | Variable_name | Value | +------------------+--------+ | system_time_zone | | | time_zone | SYSTEM |# 数据库使用时区跟system一致,即当前系统时区,是耶路撒冷(+2:00),等同于set time_zone='+2:00'了。 +------------------+--------+
向表iris.ee中插入数据:
INSERT INTO iris.ee(date1,date2,date3) VALUES(NOW(),NOW(),NOW());
查询表数据:
root@iris>select * from iris.ee; +----+----------------------------+-------------+-------------------------+ | id | date1 | date2 | date3 | +----+----------------------------+-------------+-------------------------+ | 1 | 2018-12-07 09:55:52.000000 | 09:55:52.00 | 2018-12-07 09:55:52.000 | +----+----------------------------+-------------+-------------------------+ 1 row in set (0.00 sec)
修改当前会话的时区:
root@iris>set time_zone='+8:00'; Query OK, 0 rows affected (0.00 sec)
再次插入数据,并查看表数据:
root@iris>INSERT INTO iris.ee(date1,date2,date3) VALUES(NOW(),NOW(),NOW()); Query OK, 1 row affected (0.18 sec) #查看表数据 root@iris>select * from iris.ee; +----+----------------------------+-------------+-------------------------+ | id | date1 | date2 | date3 | +----+----------------------------+-------------+-------------------------+ | 1 | 2018-12-07 09:55:52.000000 | 09:55:52.00 | 2018-12-07 17:55:52.000 | | 2 | 2018-12-07 17:57:29.000000 | 17:57:29.00 | 2018-12-07 17:57:29.000 | # 看date1的值:原来时区是耶路撒冷(+2:00),改为“+8:00”后,时间变晚了6个小时,从九点变成17点。(分秒的差距是由于执行间隔时间造成) +----+----------------------------+-------------+-------------------------+ 2 rows in set (0.00 sec)
注意:这里修改time_zone都是会话变量,同一个会话里进行的。
test2
测试目的:修改会话级别和全局级别变量time_zone的差异;
当前表记录为:
root@iris>select * from iris.ee; +----+----------------------------+-------------+-------------------------+ | id | date1 | date2 | date3 | +----+----------------------------+-------------+-------------------------+ | 1 | 2018-12-07 09:55:52.000000 | 09:55:52.00 | 2018-12-07 22:55:52.000 | | 2 | 2018-12-07 17:57:29.000000 | 17:57:29.00 | 2018-12-07 22:57:29.000 | | 3 | 2018-12-07 23:52:08.000000 | 23:52:08.00 | 2018-12-07 23:52:08.000 | | 4 | 2018-12-07 18:52:31.000000 | 18:52:31.00 | 2018-12-07 23:52:31.000 | | 5 | 2018-12-07 19:04:08.000000 | 19:04:08.00 | 2018-12-08 00:04:08.000 | | 6 | 2018-12-07 19:04:13.000000 | 19:04:13.00 | 2018-12-08 00:04:13.000 | | 7 | 2018-12-07 19:04:34.000000 | 19:04:34.00 | 2018-12-08 00:04:34.000 | +----+----------------------------+-------------+-------------------------+ 7 rows in set (0.00 sec)
当使用全局级别变量时:
会话1:
会话2:
❤上面两图显示两个会话当前global time_zone都是“+8:00”。
分别在会话1和会话2中插入记录:
session1: INSERT INTO iris.ee(id,date1,date2,date3) VALUES(11,NOW(),NOW(),NOW()); session2: INSERT INTO iris.ee(id,date1,date2,date3) VALUES(22,NOW(),NOW(),NOW());
查询表记录:
会话1:
会话2:
❤上面两图表示当前插入的记录在两个会话中时间显示是(相对)一样的(都是同一个时区、当前实际时间--即,我看到手表上显示的时间。)
当使用会话级参数设置时:
会话1:
会话2:
❤上面两图表示,当会话1时区为“+1:00”、会话2时区为“+11:00”,相差10小时。
注意
(1)插入的记录:
datetime类型字段,保存的时间都是当前会话所设置的时区相应时间点(即,好比我两个会话都是修改了计算机的系统时间的时区,然后执行insert。执行命令时看到计算机上显示的时间)。
如下图;
而,timestamp类型字段,id=33 和id=44 的记录,是一样的(即,虽然我设定两个会话所在时区不同,但是我同时(假使是同时执行实际,我切换会话执行,时间也就隔了12秒,看上图)在两个会话里insert了,此时他们换算成我所在的时区的时间都是一样的,都跟我现在看到我手表的时间是一样的。),这也是我要说的第二个注意点,timestamp的时区性。
(2)时间类型不同导致数据显示结果的不同:
这里涉及到一个问题,timestamp字段类型有时区性,上一个注意事项提到的。
datetime类型的字段的记录,记录的都是数据插入的时候当前会话所获取到的time_zone相应的时区的即时时间。就算会话或者说服务器设置的时区改变了了,表里这个字段的值也不会发生变化。
但是timestamp类型的字段的值会随着服务器时区的变化,自动换算成相应的时间。即在不同时区,查询到同一个条记录此字段的值会不一样。
可能说的比较绕。 还是不明白,可以再来看第二个例子。
例1:
表的数据没做任何update,在同一个会话里,修改了两次time_zone,看到的表ee里字段date3 (timestamp类型)的值变化了,date1(datetime类型)没变。
- time_zone='+0:00'
root@iris>set time_zone='+0:00'; Query OK, 0 rows affected (0.00 sec) root@iris>select * from iris.ee; +----+----------------------------+-------------+-------------------------+ | id | date1 | date2 | date3 | +----+----------------------------+-------------+-------------------------+ | 1 | 2018-12-07 09:55:52.000000 | 09:55:52.00 | 2018-12-07 09:55:52.000 | | 2 | 2018-12-07 17:57:29.000000 | 17:57:29.00 | 2018-12-07 09:57:29.000 | +----+----------------------------+-------------+-------------------------+ 2 rows in set (0.00 sec)
- time_zone='+13:00'
root@iris>set time_zone='+13:00'; Query OK, 0 rows affected (0.00 sec) root@iris>select * from iris.ee; +----+----------------------------+-------------+-------------------------+ | id | date1 | date2 | date3 | +----+----------------------------+-------------+-------------------------+ | 1 | 2018-12-07 09:55:52.000000 | 09:55:52.00 | 2018-12-07 22:55:52.000 | | 2 | 2018-12-07 17:57:29.000000 | 17:57:29.00 | 2018-12-07 22:57:29.000 | +----+----------------------------+-------------+-------------------------+ 2 rows in set (0.00 sec) root@iris>
总结
上面两次对本会话的time_zone进行修改,得到date1字段的值没变,date3字段的值变了。
所以,表中有需要使用时间类型的字段时候,需要根据业务情况选择合适类型,datetime或者timestamp。
当然,这两个类型的差别并不仅限于其时区性,
datetime取值范围:0000-00-00 00:00:00 ~ 9999-12-31 23:59:59;
timestamp取值范围:1970-01-01 08:00:01!2038-01-19 11:14:07 。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。