mysqldump的tz-utc选项引发的MySQL时区问题

背景

在使用mysqldump备份过滤timestamp字段数据时,查看就按导出的文本文件,发现数据与在server端查看的数据不一致

环境

操作系统

centos7.4 MySQL5.7.24

[root@node1 ~]# date
Mon Dec 17 11:21:14 CST 2018

[root@node1 ~]# date -R
Mon, 17 Dec 2018 11:21:20 +0800

注:上面命令输出了+0800表示东八区,也就是我们国家的时间。相反,如果是-0800表示美国旧金山所在的时区,西八区。我们在安装系统的时候,如果地区选择了Asia/Shanghai,那么系统的时区就是东八区。

MySQL

mysql> show variables like '%time_zone';
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | UTC |
| time_zone | +08:00 |
+------------------+--------+
2 rows in set (0.02 sec)

mysql> show variables like '%log_time%';
+----------------+--------+
| Variable_name | Value |
+----------------+--------+
| log_timestamps | SYSTEM |
+----------------+--------+
1 row in set (0.01 sec)

注:

  • system_time_zone
    The server system time zone. When the server begins executing, it inherits a time zone setting from the machine defaults, possibly modified by the environment of the account used for running the server or the startup script. The value is used to set system_time_zone. Typically the time zone is specified by the TZ environment variable. It also can be specified using the --timezone option of the mysqld_safe script.

The system_time_zone variable differs from time_zone. Although they might have the same value, the latter variable is used to initialize the time zone for each client that connects.

  • time_zone
    The current time zone. This variable is used to initialize the time zone for each client that connects. By default, the initial value of this is 'SYSTEM' (which means, “use the value of system_time_zone”). The value can be specified explicitly at server startup with the --default-time-zone option.

Note:If set to SYSTEM, every MySQL function call that requires a timezone calculation makes a system library call to determine the current system timezone. This call may be protected by a global mutex, resulting in contention.

  • system_time_zone是在MySQL启动后读取操作系统的时区作为MySQL server时区选项,不可手动修改;而time_zone是MySQL server真正使用的时区,可以在配置文件中--default-time-zone手动修改,一般选项为system和+8:00,system即使用操作系统的时区;+8:00代表是以UTC为标准,与UTC相差的时间的时区。

关于时间的几个标准,如下所示:

  • CST:中国标准时间(China Standard Time),这个解释可能是针对RedHat Linux。
  • JST:日本标准时间(Japan Standard Time)。
  • UTC:协调世界时,又称世界标准时间,简称UTC,从英文国际时间/法文协调时间”Universal Time/Temps Cordonné”而来。中国大陆、香港、澳门、台湾、蒙古国、新加坡、马来西亚、菲律宾、澳洲西部的时间与UTC的时差均为+8,也就是UTC+8。
  • GMT:格林尼治标准时间(旧译格林威治平均时间或格林威治标准时间;英语:Greenwich Mean Time,GMT)是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。

注:我们国家跨越了东五区、东六区、东七区、东八区、东九区五个时区,一般都统一采用东八区计时时间。

方法一:使用tzselect设置时区

$ tzselect 
Please identify a location so that time zone rules can be set correctly.
Please select a continent or ocean.
 1) Africa(非洲)
 2) Americas(美洲)
 3) Antarctica(南极洲)
 4) Arctic Ocean(北极洋)
 5) Asia(亚洲)
 6) Atlantic Ocean(大西洋)
 7) Australia(大洋洲)
 8) Europe(欧洲)
 9) nIndian Ocea(印度洋)
10) Pacific Ocean(太平洋)
11) none - I want to specify the time zone using the Posix TZ format.
#? 5 #这里选择亚洲
Please select a country.
 1) Afghanistan    18) Israel    35) Palestine
 2) Armenia    19) Japan    36) Philippines
 3) Azerbaijan    20) Jordan    37) Qatar
 4) Bahrain    21) Kazakhstan    38) Russia
 5) Bangladesh    22) Korea (North)    39) Saudi Arabia
 6) Bhutan    23) Korea (South)    40) Singapore
 7) Brunei    24) Kuwait    41) Sri Lanka
 8) Cambodia    25) Kyrgyzstan    42) Syria
 9) China    26) Laos    43) Taiwan
10) Cyprus    27) Lebanon    44) Tajikistan
11) East Timor    28) Macau    45) Thailand
12) Georgia    29) Malaysia    46) Turkmenistan
13) Hong Kong    30) Mongolia    47) United Arab Emirates
14) India    31) Myanmar (Burma)    48) Uzbekistan
15) Indonesia    32) Nepal    49) Vietnam
16) Iran    33) Oman    50) Yemen
17) Iraq    34) Pakistan
#? 9 #选择中国
Please select one of the following time zone regions.
1) Beijing Time
2) Xinjiang Time
#? 1 #选择北京

The following information has been given:

 China
 Beijing Time

Therefore TZ='Asia/Shanghai' will be used.
Local time is now:    Wed Mar 29 10:59:45 CST 2017.
Universal Time is now:    Wed Mar 29 02:59:45 UTC 2017.
Is the above information OK?
1) Yes
2) No
#? yes
Please enter 1 for Yes, or 2 for No.
#? 1

You can make this change permanent for yourself by appending the line
 TZ='Asia/Shanghai'; export TZ
to the file '.profile' in your home directory; then log out and log in again.

Here is that TZ value again, this time on standard output so that you
can use the /bin/tzselect command in shell scripts:
Asia/Shanghai

注:tzselect命令只告诉你选择的时区的写法,并不会生效。你可以在.profile、.bash_profile或者/etc/profile中设置正确的TZ环境变量并导出。 例如在.bash_profile里面设置 TZ=’Asia/Shanghai'; export TZ并使其生效。

例子,将系统时区设置为东九区(日本时间)

[root@node1 ~]# export TZ="Asia/Tokyo"

[root@node1 ~]# date
Mon Dec 17 12:20:37 JST 2018

[root@node1 ~]# date -R
Mon, 17 Dec 2018 12:20:41 +0900

方法二:复制相应的时区文件,替换系统时区文件或者创建链接文件

在/usr/share/zoneinfo/下面有很多时区文件(跟tzselect显示的一样),可以复制这些时区文件覆盖/etc/localtime文件,或修改符号链接/etc/locatime对应的文件。

[root@node1 ~]# ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

[root@node1 ~]# date
Mon Dec 17 11:21:14 CST 2018

[root@node1 ~]# date -R
Mon, 17 Dec 2018 11:21:20 +0800

注:如果有时候,执行了上面命令后,使用date -R发现时区设置没有生效,有可能是因为你在profile或.bash_profile里面设置了TZ。

在线修改时区

root@localhost : (none):23: > show variables like '%time_zone'; 
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | UTC |
| time_zone | +08:00 |
+------------------+--------+
2 rows in set (0.00 sec)

root@localhost : (none):23: > select now();
+---------------------+
| now() |
+---------------------+
| 2018-12-17 11:26:02 |
+---------------------+
1 row in set (0.00 sec)

root@localhost : (none):26: > set time_zone="+9:00";
Query OK, 0 rows affected (0.00 sec)

root@localhost : (none):26: > show variables like '%time_zone%'; 
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | UTC |
| time_zone | +09:00 |
+------------------+--------+
2 rows in set (0.01 sec)

root@localhost : (none):26: > select now();
+---------------------+
| now() |
+---------------------+
| 2018-12-17 12:26:21 |
+---------------------+
1 row in set (0.00 sec)

配置文件修改时区

log_timestamps=SYSTEM
default-time-zone = '+8:00'

修改完需要重启MySQL

问题:使用mysqldump时过滤timestamp类型字段

root@localhost : (none):44: > show variables like '%time_zone%';
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | CST |
| time_zone | +08:00 |
+------------------+--------+
2 rows in set (0.00 sec)

root@localhost : (none):45: > select * from test.test2 where create_time< "2018-12-17 11:37:00";     
+----+---------------------+
| id | create_time |
+----+---------------------+
| 1 | 2018-12-17 11:35:57 |
| 2 | 2018-12-17 11:36:07 |
| 3 | 2018-12-17 11:36:09 |
| 4 | 2018-12-17 11:36:12 |
| 5 | 2018-12-17 11:36:17 |
| 6 | 2018-12-17 11:36:35 |
| 7 | 2018-12-17 11:36:49 |
| 8 | 2018-12-17 11:36:53 |
+----+---------------------+
8 rows in set (0.00 sec)

root@localhost : (none):45: > select * from test.test2 where create_time> "2018-12-17 11:37:00"; 
+----+---------------------+
| id | create_time |
+----+---------------------+
| 9 | 2018-12-17 11:37:02 |
| 10 | 2018-12-17 11:37:03 |
+----+---------------------+
2 rows in set (0.00 sec)

UTC时区下使用mysqldump备份

[root@node2 ~]# date
Mon Dec 17 04:01:50 UTC 2018

[root@node2 ~]# date -R
Mon, 17 Dec 2018 04:01:56 +0000

[root@node2 ~]# mysqldump -upowdba -hnode1 -pabc123 --where 'create_time< "2018-12-17 11:37:00"' --databases test --tables test2 > /root/zone_2.sql

[root@node2 ~]# vim zone_2.sql 
……
--
-- Dumping data for table `test2`
--
-- WHERE: create_time< "2018-12-17 11:37:00"

LOCK TABLES `test2` WRITE;
/*!40000 ALTER TABLE `test2` DISABLE KEYS */;
INSERT INTO `test2` VALUES (1,'2018-12-17 03:35:57'),(2,'2018-12-17 03:36:07'),(3,'2018-12-17 03:36:09'),(4,'2018-12-17 03:36:12'),(5,'2018-12-17 03:36:17'),(6,'2018-12-17 03:36:35'),(7,'2018-12-17 03:36:49'),(8,'2018-12-17 03:36:53'),(9,'2018-12-17 03:37:02'),(10,'2018-12-17 03:37:03');
/*!40000 ALTER TABLE `test2` ENABLE KEYS */;
UNLOCK TABLES;
SET @@SESSION.SQL_LOG_BIN = @MYSQLDUMP_TEMP_LOG_BIN;
……

[root@node2 ~]# mysqldump -upowdba -hnode1 -pabc123 --where 'create_time > "2018-12-17 11:37:00"' --databases test --tables test2 > /root/zone_1.sql 

[root@node2 ~]# vim zone_1.sql
……
--
-- Dumping data for table `test2`
--
-- WHERE: create_time > "2018-12-17 11:37:00"

LOCK TABLES `test2` WRITE;
/*!40000 ALTER TABLE `test2` DISABLE KEYS */;
/*!40000 ALTER TABLE `test2` ENABLE KEYS */;
UNLOCK TABLES;
SET @@SESSION.SQL_LOG_BIN = @MYSQLDUMP_TEMP_LOG_BIN;
……

总结

mysqldump默认开启tz-utc,使用UTC作为默认时区;当MySQL server时区不为UTC时,会出现timestamp字段数据不一致;如MySQL server使用的是CST,那么mysqldump导出时间出现-8小时情况。

解决

  • timestamp字段修改为datetime
  • mysqldump使用--skip-tz-utc规避时区的影响
  • MySQL server使用UTC时区

关于时区的调优

网上有文章是说在默认time_zone=system下,使用timestamp字段在大量请求时引发的CPU SYS高的问题。https://mp.weixin.qq.com/s/AtyaIP92L6KnZFB9bQA3ug
大概意思就是讲,对于使用timestamp的场景,MySQL在访问timestamp字段时会做时区转换,当time_zone设置为system时(默认),MySQL访问每一行的timestamp字段时,都会通过libc的时区函数,获取Linux设置的时区,在这个函数中会持有mutex,当大量并发SQL需要访问timestamp字段时,会出现mutex竞争。MySQL访问每一行都会做这个时区转换,转换完后释放mutex,所有等待这个mutex的线程全部唤醒,结果又会只有一个线程会成功持有mutex,其余又会再次sleep,这样就会导致context switch非常高但qps很低,系统吞吐量急剧下降。

总结下文章,就是当time_zone=system的时候,查询timestamp字段,会调用系统的时区做时区转换,有全局锁__libc_lock_lock的保护,导致线程并发环境下,系统性能受限。如果将time_zone=’+8:00’则不会调用系统时区,则不会触发系统时区转换,使用mysql自身转换,大大提高了性能。

参考链接:
http://www.ywnds.com/?p=8835

猜你喜欢

转载自yq.aliyun.com/articles/679912