一个 “不存在的时间”

本文介绍了一次线上数据库同步中断,从排查到解决的过程。

回顾上篇文章:一次 mongoDB 异常崩溃

封面图片源自:pixabay Myriams-Fotos

一个 “不存在的时间”

线上数据库发生了一个奇怪的同步中断,从库的报错信息为 “Error'Incorrect datetime value: '1970-01-01 07:00:01' for column 'test' at row 1'on query.”

在系统中,我们一般使用时间戳(timestamp)来记录时间,时间戳的含义是指格林威治时间 1970 年 01 月 01 日 00 时 00 分 01 秒起至现在的总秒数。这个时间算上时差,我们使用的 CST 时间的时间戳原点应该是 1970 年 1 月 1 日 8:00,为什么会出现 1970 年 1 月 1 日 7:00 这个不应该存在于东八区的时间呢?

经过排查发现,该数据库集群主库的服务器系统时区为重庆时区,从库服务器系统时区为上海时区,MySQL 数据库的时区配置为 SYSTEM。然而正是这个重庆时区背后,隐藏着一些大问题。

怎么差了一个小时?

经排查,在重庆时区的数据库中执行 “select from_unixtime(0)” 得到的结果竟然与在上海时区的数据库中执行结果不同,重庆时区看起来应该是 “东七区” 时间。

难道重庆时区跟北京时间差了一个小时?赶紧执行 select now(),却发现时间准确的。

这到底是为什么呢?在查阅了相关资料后,原来原始是中国时区由于历史原因曾多次发生过变化,在建国后的一段时间里对于时区的使用存在一个混乱阶段。

原因追溯

由于我国国土幅员辽阔,根据地理进行时区划分实际上可划分为五个时区:东五区,东六区,东七区,东八区,东九区(其中东五区跟东九区并未完全覆盖)。中华民国时期,民国政府依据国际标准,将全国划分为昆仑时区、回藏时区(后改称新疆时区)、陇蜀时区、中原时区以及长白时区。

其中长白时区和昆仑时区这两个时区实际上是为半时区,由于不符合国际规定,后被取消不再使用。

1949 年新中国成立,全国开始推行北京时间,各地为了表达对北京的拥护,也分别开始在广播中使用北京时间,此时的 “北京时间” 政治意义占了较大比重,并不是科学上的平时,甚至不是标准时。

在胡继勤编著《时间和历法》(商务印书馆 1959 年版)书中表示,“解放以后新的标准时区尚未公布”,可见当时对于北京时间的使用只是 “暂时” 的。

可是查阅了很多资料都没有发现中国的时区是何时进行了统一,还是不能解释为什么重庆时区跟上海时区差一个小时,为什么之后又一致了?

后来在 tzinfo 里找到了线索。

(以下内容来自 [tz-announce] 2014f release of tz code and data available)

在 2014 年 8 月 tz 的 release note 内容中显示 ,此次 release 将中国的五个时区简化为两个,仅保留上海时间(UTC+8)跟乌鲁木齐时间(UTC+6),乌鲁木齐时间由原先的 UTC+8 变为 UTC+6。

这篇文章里还写了下面这段话:

Some zones have been turned into links, when they differed from existing zones only for older UTC offsets where the data were likely invented. These changes affect UTC offsets in pre-1970 time stamps only.

这下就能解释为什么相同时间戳在两个都应该是东八区的时区里,得到的结果却不一样的问题。

问题复现

知道了原因之后,我便对重庆时区进行了一下测试,下图是重庆时区 1980 年 5 月 1 日 1:00 和 0:00 的时间戳,可以看到在这一天重庆时区正式并入东八区。

我们创建了一个模拟线上场景的测试环境,主库从库均采用系统时间,主库时区为重庆时区,从库时区为上海时区。

创建了如下一张表:

CREATE TABLE `tz_test` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`val` varchar(10) DEFAULT NULL,

`tz` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8l;

在主库执行以下语句:

insert into tz_test(val,tz) values('t','1970-01-01 07:00:01');

发现 SQL 执行成功,从库未发生同步中断,但数据不一致,从库数据的时间列值为 0。

将主库从库的配置均变为严格模式,重启数据库,再次进行上述 SQL 操作,成功复现同步中断的情况。

解决方案

弄清楚原因之后,这个问题的本质实际上就是时区使用不当。我们可以通过如下命令修改 MySQL 默认时间配置:

mysql > set global time_zone =”+8:00”;

mysql > stop slave; start slave;

将时区由默认的 SYSTEM 改为预期的时区即可解决这个问题。而且修改后,对于含有大量时间操作的 SQL 还能起到一定的性能优化作用。

这个问题不仅仅存在于数据库中,只要是使用系统自带时间程序都有可能会遇到这个情况。可以通过执行 “date -d @0” 来确认自己的系统时区是否是符合自己预期的,避免未来为自己留下隐患。

参考资料

维基百科 - 北京时间词条

维基百科 - 时区列表

知乎 - 长白时区、陇蜀时区、新藏时区、昆仑时区究竟用到哪一年?

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180320G16LL700?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

同媒体快讯

扫码关注云+社区

领取腾讯云代金券