闰秒

说道闰秒,可能很多人没有听说过,而闰年和闰月应该被很多人熟知。

闰年是指该年有366日,即较平常年份多出一日。闰年是为了弥补因人为历法规定的年度天数365日和平均回归年的大约365.24219日的差距而设立的。不同历法有不同置闰方法。儒略历每4年置1闰日,平均1年是365.25日。格里高利历每400年少3次闰日,平均是365.2425日。多出来的一天为2月29日。(参考:https://zh.m.wikipedia.org/zh-sg/%E9%97%B0%E5%B9%B4

我们生活里说的闰月通常是指农历(阴阳历)的闰月。闰月没有固定是一年的中的哪一个月,是动态的,总的规则是19年加7个闰月,即“19年7闰法”。(参考:http://www.gov.cn/govweb/fwxx/kp/2009-06/16/content_1341232.htm

那什么是闰秒呢?

为确定时间,世界上有两种常用的时间计量系统:基于地球自转的世界时(UT)(参考:https://zh.wikipedia.org/zh-sg/%E4%B8%96%E7%95%8C%E6%97%B6
)和基于原子振荡周期的国际原子时(TAI)(参考:https://zh.wikipedia.org/zh-sg/%E5%9C%8B%E9%9A%9B%E5%8E%9F%E5%AD%90%E6%99%82)。由于两种测量方法不同,随着时间推移,两个计时系统结果会出现差异,因此有了协调世界时的概念。
协调世界时以国际原子时秒长为基础,在时刻上尽量接近世界时。1972年的国际计量大会决定,当国际原子时与世界时的时刻相差达到0.9秒时,协调世界时就增加或减少1秒,以尽量接近世界时,这个修正被称作闰秒。
闰秒实际上是为适应地球自转的脚步而对国际原子时的人为增减。依据国际地球自转服务组织对国际原子时与世界时的监测数据,当两者之差达到0.9秒时,该机构就向全世界发布公告,在下一个6月或12月最后一天的最后一分钟,实施正闰秒或负闰秒。
自1972年协调世界时正式使用至今,全球已经实施了27次正闰秒调整,最近一次的闰秒调整是格林尼治时间2016年12月31日。从协调世界时正式使用以来,地球自转一直处于不断减慢的趋势,因此迄今为止的闰秒都是正闰秒,但相关科研发现,自2020年年中以来,地球自转速率呈现加快趋势,这意味着未来也可能会出现负闰秒。(参考:闰秒是什么,将何去何从  http://www.news.cn/tech/2022-11/19/c_1129142173.htm)

历史上已实行的闰秒日期
630 1231
1972 +1 +1
1973 0 +1
1974 0 +1
1975 0 +1
1976 0 +1
1977 0 +1
1978 0 +1
1979 0 +1
1980年 0 0
1981 +1 0
1982 +1 0
1983 +1 0
1984年 0 0
1985 +1 0
1986年 0 0
1987 0 +1
1988年 0 0
1989 0 +1
1990 0 +1
1991年 0 0
1992 +1 0
1993 +1 0
1994 +1 0
1995 0 +1
1996年 0 0
1997 +1 0
1998 0 +1
1999年 0 0
2000年 0 0
2001年 0 0
2002年 0 0
2003年 0 0
2004年 0 0
2005 0 +1
2006年 0 0
2007年 0 0
2008 0 +1
2009年 0 0
2010年 0 0
2011年 0 0
2012 +1 0
2013年 0 0
2014年 0 0
2015 +1 0
2016 0 +1
2017年 0 0
2018年 0 0
2019年 0 0
2020年 0 0
2021年 0 0
2022年 0
总计 11 16
27
目前TAIUTC秒差
37

之前我有写过关于monotonic clock的内容。当时就提到闰秒,正的闰秒会导致wall clock比较时出现负值,所以我们需要使用monotonic clock来克服这一个问题。那为什么呢?我们来看看目前如何处理正值的闰秒的,1998-12-31日的闰秒为例:

TAI (1 January 1999) UTC (31 December 1998 to 1 January 1999) Unix time
1999-01-01T00:00:30.00 1998-12-31T23:59:59.00 915148799.00
1999-01-01T00:00:30.50 1998-12-31T23:59:59.50 915148799.50
1999-01-01T00:00:31.00 1998-12-31T23:59:60.00 915148800.00
1999-01-01T00:00:31.50 1998-12-31T23:59:60.50 915148800.50
1999-01-01T00:00:31.75 1998-12-31T23:59:60.75 915148800.75
1999-01-01T00:00:32.00 1999-01-01T00:00:00.00 915148800.00
1999-01-01T00:00:32.25 1999-01-01T00:00:00.25 915148800.25
1999-01-01T00:00:32.50 1999-01-01T00:00:00.50 915148800.50
1999-01-01T00:00:33.00 1999-01-01T00:00:01.00 915148801.00

可以看到,发生闰秒的1998-12-31 23:59:59之后不是1999年1月1日00:00:00,而是中间多了一个1998-12-31 23:60:60,这两秒的Unix timestamp是一样的。所以如果在使用毫秒级、微秒级、纳秒级的时间戳时,在这两秒里就可能出现负的时间戳差值,比如:23:59:59.600 ~ 23:60:60.000,差值就是一个负值。

写在最后,使用闰秒这种处理方法已被证明具有破坏性,特别是在二十一世纪,尤其是在依赖精确时间戳或时间关键程序控制的服务中。相关国际标准机构一直在讨论是否继续这种做法。(参考:https://zh.wikipedia.org/zh-sg/%E9%97%B0%E7%A7%92

MySQL的Character Set和collate

常见的charset:

一般常用的charset是utf8、utf8mb4。

如下是MySQL 8的文档描述:

  • utf8mb4: A UTF-8 encoding of the Unicode character set using one to four bytes per character.
  • utf8mb3: A UTF-8 encoding of the Unicode character set using one to three bytes per character. This character set is deprecated in MySQL 8.0, and you should use utfmb4
  • utf8: An alias for utf8mb3. In MySQL 8.0, this alias is deprecated; use utf8mb4utf8 is expected in a future release to become an alias for utf8mb4.

总结来说,utf8mb4字符集支持最多使用4个字节来存储一个字符,这样就支持很多额外的特殊字符,比如emoji符号。可以说utf8mb3是个历史产物,当年MySQL支持UTF-8时,UTF标准里一个字符最多只占用3个字节。而后UFT-8标准更新后最多使用4个字节后,MySQL在5.5版本开始新增utf8mb4以支持4个字节存储一个字符。而到目前为止(2023-01)MySQL的utf8还是utf8mb3的别名,当然将来可能uft8会被指向utf8mb4。

 

以上都是老生常谈的知识了,大家无脑的使用utf8mb4就可以了。今天要说的其实是collate,collate主要用于排序和字符比较。因为最近遇到一个问题,就是sql查询时,使用 name like ‘%keywords%’时,MySQL是区分大小写匹配的,一看表的collate是utf8mb4_bin,破案了。那么我们来看看MySQL有哪些常用的collate。

 

utf8mb4_bin: 将字符串每个字符用二进制数据编译存储,区分大小写,而且可以存二进制的内容。

utf8mb4_general_ci:ci即case insensitive,不区分大小写。是一个遗留的校对规则,不支持扩展,它仅能够在字符之间进行逐个比较,没有实现Unicode排序规则,在遇到某些特殊语言或者字符集,排序结果可能不一致。但是,在绝大多数情况下,这些特殊字符的顺序并不需要那么精确。

utf8mb4_unicode_ci:是基于标准的Unicode来排序和比较,能够在各种语言之间精确排序,Unicode排序规则为了能够处理特殊字符的情况,实现了略微复杂的排序算法。

utf8mb4_general_cs:基本同utf8mb4_general_ci,区别是区分大小写。

utf8mb4_unicode_cs:基本同utf8mb4_unicode_ci,区别是区分大小写。

大家可以看到规律了吧:

*_bin: 表示的是binary case sensitive,也就是说是区分大小写的

*_cs: case sensitive,区分大小写

*_ci: case insensitive,不区分大小写

 

我本地MySQL8 配置文件中字符集相关变量

show variables like '%character%';

Variable_name    Value

character_set_client  utf8mb4

character_set_connection       utf8mb4

character_set_database   utf8mb4

character_set_filesystem  binary

character_set_results utf8mb4

character_set_server utf8mb4

character_set_system       utf8mb3

……


show CHARSET like 'utf%'; -- 查看MySQL的default COLLATE


Charset Description  Default collation Maxlen

utf16     UTF-16 Unicode utf16_general_ci 4

utf16le  UTF-16LE Unicode    utf16le_general_ci     4

utf32     UTF-32 Unicode utf32_general_ci 4

utf8mb3       UTF-8 Unicode   utf8_general_ci   3

utf8mb4       UTF-8 Unicode   utf8mb4_0900_ai_ci  4

这里我们会发现一个特殊的collate:utf8mb4_0900_ai_ci ,那这个0900表示什么了?原来utf8mb4_0900_ai_ci 是MySQL8新增的collate,中间的0900,表示的是Unicode 9.0的规范。对应的之前就有的utf8mb4_unicode_520_ci、utf8mb4_unicode_ci等。

 

References:

  1. https://dev.mysql.com/doc/refman/8.0/en/charset-unicode-sets.html
  2. https://dev.mysql.com/doc/refman/8.0/en/charset-unicode-utf8mb4.html
  3. https://zh.wikipedia.org/zh-hans/UTF-8
  4. https://dev.mysql.com/doc/refman/8.0/en/charset-database.html
  5. https://dev.mysql.com/doc/refman/8.0/en/charset-collation-names.html
  6. https://www.lifesailor.me/archives/2676.html