关于MySQL8.0的闰秒,官方文档是真狗啊

我在读MySQL最新8.0文档的时候,想预演一下实际情况中的闰秒,结果并不顺利。甚至有MySQL文档编写人员想当然的成分存在。所以特别整理了一篇文章,以供后人节省时间。如果有理解不对的地方,还希望热心读者留言指出。

名词概念,还请搜索关键字深入了解,这里只是为了方便文章理解,仅串起它们之间的基本关系:闰秒,跳秒,leap second,GMT,UTC,UT1;unix_timestamp、POSIX时间、utc_timestamp

本文介绍MySQL如何维护时区设置,如何加载时区支持所需的系统表,如何保持当前时区更改,以及如何启用MySQL闰秒支持。

我的环境:Win7(64bit) + MySQL 8.0.27 + SQL-yog

官方文档地址:https://dev.mysql.com/doc/refman/8.0/en/time-zone-support.html

  • 闰秒是什么?

  • MySQL在闰秒的时候会出现什么问题?

  • MySQL 8支持闰秒,该怎么设置?

一、闰秒

        闰秒是偶尔(平均7年)出现在6月底或12月底23:59:59之后,多增加1秒,以消除原子钟测量的精确时间(称为UTC)和不精确的太阳观测时(称为UT1)之间的差异。这是由于地球自转的不规则和地球自转减慢程度的不同而有所不同。

        每个UTC闰秒的插入,都是由国际地球自转服务(IERS)提前约六个月决定,以确保UTC和UT1读数之间的差值永远不会超过0.9秒。历史上一共出现了27次。互联网工程任务组织(IETF)会及时跟踪整理,可以点击查看https://www.ietf.org/timezones/data/leap-seconds.list

二、MySQL开启闰秒支持,导入官方时区数据

        默认安装的MySQL没有填充时区命名记录。用以下语句查看无记录:

mysql>SELECT COUNT(*) FROM mysql.time_zone_name;

+----------+

|  COUNT(*) |

+----------+

|        0 |

+----------+

       官方提供了导入工具:

  • Linux可以使用该命令:

        mysql_tzinfo_to_sql/usr/share/zoneinfo | mysql -u root -p mysql

  • Windows可以下载MySQL提供的最新数据:

        https://dev.mysql.com/downloads/timezones.html

打开页面后下载图中标识的zip文件,以便后续设置闰秒

图片

下载、解压\导入\重启MySQL。导入命令参考mysql -u root -p mysql < sql_file_name

这里先补充一个概念:

        除了闰秒之外,全球大部分国家都曾经定义过一个叫“夏令时”制度。夏天的时候日照时间变长,可以节约能源消耗,所以人为设定某一日晚上将时间提前一小时。待夏末的时候再把时间拨回来。我国仅86年到91年实行过夏令时,而有些国家现在还坚持着夏令时制度。这可是每年阶段性的“闰3600秒”

        所以我们可以清楚一件事:闰秒与否、是否执行夏令时等时间制度,是根据不同时区、不同国家/地区,多个维度共同作用,并非全球统一且永远不变的

        所以,我在本文第四部分,会分析刚刚MySQL提供的2021最新时区文件。

三、官方文档——支持时区闰秒【查看英文原版:5.1.15MySQL Server Time Zone Support

        根据以下用例,我无法得到最后的黄底结果!难道是官方文档有误?先来逐字分析

mysql>CREATE TABLE t1 (

         a INT,

         ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

         PRIMARYKEY(ts)

       );

Query OK, 0 rows affected (0.01 sec)  

-- 这里创建新表,ts字段是默认当前时间戳。之所以使用TimeStamp类型,是因为该类型在你更改时区的时候会自行随之改动。

mysql>-- change to UTC

-- 因为闰秒公告是以UTC+00:00时区发布的,为了后续select显示时保持一致,我们先修改当前Sessiontime_zone与其一致。不修改也可以,那么则会按我们+08:00显示

mysql>SET time_zone ='+00:00';

Query OK, 0 rows affected (0.00 sec)

mysql>-- Simulate NOW() = '2008-12-31 23:59:59'

-- 修改当前时间戳为'2008-12-31 23:59:59',即将发生闰秒的前一秒

mysql>SET timestamp = 1230767999;

Query OK, 0 rows affected (0.00 sec)

-- 向新表中插入一条数据,他的ts字段会取默认值“当前时间”

mysql>INSERT INTO t1 (a) VALUES(1);

Query OK,插入成功1 row affected (0.00 sec)

mysql>-- Simulate NOW() = '2008-12-31 23:59:60'

-- 修改当前时间戳+1秒,即IERS确定的闰秒:'2008-12-31 23:59:60'

mysql>SET timestamp = 1230768000;

Query OK, 0 rows affected (0.00 sec)

mysql>INSERT INTO t1 (a) VALUES(2);

Query OK,插入成功1 row affected (0.00 sec)

/*

如果IERS没有规定1230768000为闰秒的话,

那么这个TimeStamp值应该对于'2009-01-01 00:00:00'UTC

但是IERS规定这个要增加的闰秒叫'2008-12-31 23:59:60'

    '2008-12-31 23:59:60'不是TimeStamp类型的“合法范围”

    所以如果你用这个字符串进行查询,MySQL不会返回任何结果报错

*/

mysql>-- the leap value with seconds=60 is invalid

mysql>SELECT * FROM t1 WHERE ts = '2008-12-31 23:59:60';

Emptyset,2warnings(0.00 sec)

/*

我实际的显示是:

错误代码: 1525

Incorrect  TIMESTAMP value: '2008-12-31 23:59:60'

*/

-- 那么我们应该这样查看闰秒?

mysql>-- selecting using UNIX_TIMESTAMP value return leap value

mysql>SELECT * FROM t1 WHERE UNIX_TIMESTAMP(ts) = 1230768000;

+------+---------------------+

| a     |  ts                  |

+------+---------------------+

|    2  | 2008-12-31 23:59:59|      -- 注意这里:MySQL是用前一秒来显示闰秒

+------+---------------------+

1 row in set (0.00 sec)

我本地显示的是2009-01-01 00:00:00

-- 让我们看看现在t1表里的情况吧:

mysql>-- values differ internally but display the same

mysql>SELECT a, ts,UNIX_TIMESTAMP(ts) FROM t1;

+------+---------------------+--------------------+

| a     |  ts                  | UNIX_TIMESTAMP(ts) |

+------+---------------------+--------------------+

|    1  |  2008-12-31 23:59:59 |          1230767999 |

|    2 | 2008-12-31 23:59:59|         1230768000 |    -- 对,这里希望ts显示59

+------+---------------------+--------------------+

2 rows in set (0.00 sec)

我显示的是2009-01-01 00:00:00  |         1230768000

-- 最后,我们测试一下MySQL会不会犯错:

mysql>-- only the non-leap value matches

mysql>SELECT * FROM t1 WHERE ts ='2008-12-31 23:59:59';

+------+---------------------+

| a     |  ts                  |

+------+---------------------+

|    1  |  2008-12-31 23:59:59 |      -- 没错,我们只需要这一条

+------+---------------------+

1 row in set (0.00 sec)

mysql>SELECT * FROM t1 WHERE ts = '2009-01-01 00:00:00';

Emptyset, (0.00 sec)          -- 也是对的,并没有数据

-- a=2的那条闰秒记录呢?   上文找一下“那么我们应该这样查看闰秒?”

四、探索之路

虽然最终解决了黄底问题,但过程并不是很顺利。有几个关键信息要先搞清楚,我会在这一部分进行说明。着急知道如何解决的朋友,请直接看第五部分。

1. MySQL的TimeStamp类型的显示格式,严格使用POSIX格式。显示小时部分只用00~23,分部只用00~59,秒部只用00~59。那对于闰秒(60、61)的情况,MySQL的方案是以闰秒的前一秒替代显示,即是复用23:59:59。而不会显示60或者61。

2.我们根据第三部分里的用例,保持UTC+00:00时区不变,先测试没有开启闰秒配置时,2016年末的闰秒会怎样显示:

SELECT FROM_UNIXTIME(1483228799);

+---------------------+

2016-12-31 23:59:59         -- 闰秒前一秒,正常显示

+---------------------+

SELECT FROM_UNIXTIME(1483228800);

+---------------------+     -- IERS定义的闰秒,但MySQL还没设置

2017-01-01 00:00:00         -- 所以会显示次日00:00:00,正常

+---------------------+     -- 下面要想法子,让他显示成23:59:59

3. 官方下载的MySQL最新时区SQL文件,都写入了哪些表?

图片

(1)  time_zone只有两个字段:时区ID、是否使用闰秒。

(2)  time_zone_leap_second保存了目前所有的27次闰秒记录。

(3)  time_zone_name也是两个字段:时区ID,时区名称

(4)  time_zone_transition字段包括:时区ID、时间转变类型ID、转变时间值。

(5)  time_zone_transition_type字段有:时区ID、时间转变类型ID、偏移量、是否DST夏令时、时间标准缩写

         可见,除了“闰秒记录表”time_zone_leap_second之外,其余表都可以由“时区ID”串联起来的。那么我们先只看这张表:

4. “闰秒记录表”time_zone_leap_second

运行如下SQL:

SET time_zone = "+00:00";   -- 为了日期展现一致,统一回0时区

SELECT t.*, FROM_UNIXTIME(Transition_time-Correction)

FROM mysql.time_zone_leap_second t;

查看结果:

图片

看到最后一列,应该很明确了:

        以第一行举例,IERS决定以”1972-06-30 23:59:60”为闰秒,与其对应的MySQL的时间戳值为78796800。而MySQL只能显示到59秒,所以只要遇到大于等于78796800的时间戳,若需要显示日期格式时,就减掉Correction列的值,然后再转化成日期格式进行显示(最后一列即是如何)即达成闰秒的效果。举个例子:

图片

78796799 小于78796800,正常显示为”1972-06-30 23:59:59”;

78796800 等于78796800,减掉Correction列的值后,正确显示为”1972-06-30 23:59:59”;

78796801 大于78796800,减掉Correction列的值后,正确显示为”1972-07-01 00:00:00”。

当开启时区支持闰秒后,MySQL会自动为我们进行这种操作。

5. time_zone和time_zone_name表的作用

既然闰秒已经记录,那么我的时区是否支持闰秒,需要查time_zone表和time_zone_name表。

SELECT * FROM mysql.time_zone tz

LEFT JOIN  mysql.time_zone_name tzn

ON tz.time_zone_id = tzn.time_zone_id

WHERE NAME LIKE'%UTC%';

图片

经过上面的查询,我们知道UTC时区支持闰秒。那么,快试试吧!

SET time_zone = "UTC";      -- 区别于SET time_zone = "+00:00";

SELECT FROM_UNIXTIME(1483228825),FROM_UNIXTIME(1483228826),

       FROM_UNIXTIME(1483228827),FROM_UNIXTIME(1483228828);

图片

你可以再试试上海:SET time_zone = "Asia/Shanghai";

图片

~~ 成功!~~

五、解决问题

1. 导入MySQL官方Time_Zone文件

根据本文第二部分,下载好压缩文件后,解压;

导入到你的MySQL后,重启MySQL。

2.  创建测试表t2,执行测试SQL

mysql>CREATE  TABLE t2 (

         a INT,

         ts TIMESTAMPDEFAULTCURRENT_TIMESTAMP,

         PRIMARY KEY (ts)

       );

QUERY OK, 0 ROWS affected (0.01 sec) 



mysql>--  change to UTC

mysql>SET time_zone = "UTC";    -- 问题解决关键

QUERY OK, 0 ROWS affected (0.00 sec)



mysql>--  Simulate NOW() = '2008-12-31 23:59:59'

mysql> SELECTUNIX_TIMESTAMP('2008-12-31 23:59:59');

-- 修改当前时间戳为'2008-12-31 23:59:59',即将发生闰秒的前一秒

mysql>SETtimestamp= 1230768022;   -- 不再是理想值1230767999

QUERY OK, 0 ROWS affected (0.00 sec)



-- 向新表中插入一条数据,他的ts字段会取默认值“当前时间”

mysql>INSERTINTO t2 (a) VALUES (1);

QUERY OK, 1 ROW affected (0.00 sec)



mysql>--  Simulate NOW() = '2008-12-31 23:59:60'

mysql>SETtimestamp= 1230768023;

QUERY OK, 0 ROWS affected (0.00 sec)



mysql>INSERTINTO t2 (a) VALUES (2);

QUERY OK, 1 ROW affected (0.00 sec)



mysql>--  the leap value with seconds=60 is invalid

mysql>SELECT * FROM t1 WHERE ts = '2008-12-31 23:59:60';

错误代码: 1525

Incorrect TIMESTAMP VALUE: '2008-12-31 23:59:60'



mysql>SELECT * FROM t2 WHEREUNIX_TIMESTAMP(ts) = 1230768023;

+------+---------------------+

| a    | ts                  |

+------+---------------------+

|    2 | 2008-12-31 23:59:59  |     -- 终于正确显示闰秒了,MySQL以前闰秒前一秒进行显示

+------+---------------------+

1 ROW IN SET (0.00 sec)



mysql>--  values differ internally but display the same

mysql>SELECT a, ts, UNIX_TIMESTAMP(ts) FROM t2;

+------+---------------------+--------------------+

| a    | ts                  | UNIX_TIMESTAMP(ts) |

+------+---------------------+--------------------+

|    1 | 2008-12-31 23:59:59  |         1230768022 |

|    2 | 2008-12-31 23:59:59  |         1230768023 |    --  对,是59

+------+---------------------+--------------------+

2 ROWS IN SET (0.00 sec)

 

六、背后原因

-- MySQL全局time_zone,  当前会话Time_Zone

SELECT @@global.time_zone, @@session.time_zone;

+--------+-----+

| SYSTEM | UTC |

+--------+-----+

-- MySQL全局time_zone是MySQL启动时取my.cnf的配置

-- 若my.cnf中没有配置,则取操作系统的time_zone。

SELECT @@system_time_zone;

+----------------+

|�й���׼ʱ�� |                 -- Windows系统没有Zoneinfo库

+----------------+

MySQL官方的闰秒文档,吐槽三点:

  1. MySQL在配置文件my.cnf没有配置时区时,默认提取windows7的时区数据发生乱码错误,致使@@global.time_zone不可知;

  2. MySQL官方闰秒文档中使用的”+00:00”时区,并没有在自己提供的最新时区文件timezone_leaps.sql中提供数据支持;文档编写人员想当然的以为”+00:00”可以代表”UTC”.

  3. MySQL官方闰秒文档中使用的演示TimeStamp值是理想(理论)值,即1230767999对应'2008-12-3123:59:59',但实际生活中在此之前的23次闰秒已经实际占用了23个时间戳,应当予以说明并使用正确的值1230768023。这使我在正确导入官方下载的TimeZone数据、调整了time_zone="Asia/Shanghai"之后还是看到错误的结果.

 

 

经过测试:

         阿里云RDS设置的TimeZone是CST

         MySQL Docker镜像设置的是UTC

 

当“开启”MySQL闰秒配置后,只对以后新生成的TimeStamp类型的值起作用,而之前的TimeStamp数据需要我们自己进行对应处理。

图片

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老陈头7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值