5.1.15 MySQL 服务器时区支持

文章目录


官方文档地址: 5.1.15 MySQL Server Time Zone Support


本节介绍 MySQL 维护的时区设置,如何加载支持命名时间所需的系统表,如何跟上时区的变化,以及如何启用跳秒支持。

从 MySQL 8.0.19 开始,时区偏移也支持插入DATETIME值;参见 11.2.2 DATE,DATETIME,和 TIMESTAMP 类型

有关复制设置中的时区设置的信息,参见 17.5.1.14 复制与系统功能17.5.1.33 复制和时区

时区变量

MySQL 服务器维护几个时区设置:

1、系统时区。当服务器启动时,它尝试自动确定主机的时区,并使用它来设置system_time_zone系统变量。此后该值不会改变。

要在启动时显式地指定 MySQL 服务器的系统时区,在启动mysqld之前设置环境变量TZ。如果使用mysqld_safe启动服务器,它的--timezone选项提供了另一种设置系统时区的方法。TZ--timezone的允许值依赖于系统。参考操作系统文档,看看哪些值是可以接受的。

2、服务器当前时区。全局系统变量time_zone表示服务器当前运行的时区。初始的time_zone值为'SYSTEM',表示服务器时区与系统时区一致。

注意

如果设置为 SYSTEM,每个需要时区计算的 MySQL 函数调用都会调用一个系统库来确定当前的系统时区。这个调用可能被一个全局互斥锁保护,从而导致争用。

全局服务器的初始时区值可以在启动时通过命令行上的--default time-zone选项显式指定,也可以在选项文件中(比如my.cnf(Unix 操作系统)或者my.ini(Windows))添加以下行:

default-time-zone='timezone'

如果你有SYSTEM_VARIABLES_ADMIN权限(或者已弃用的SUPER权限),你可以在运行时使用下面语句设置全局服务器时区值:

SET GLOBAL time_zone = timezone;

3、每个会话的时区。每个连接的客户端都有自己的会话时区设置,由会话time_zone变量给出。最初,会话变量的值来自全局time_zone变量,但是客户端可以通过以下语句更改自己的时区:

SET time_zone = timezone;

设置会话时区会影响时区敏感的时间值的显示和存储。这包括NOW()CURTIME()等函数显示的值,以及TIMESTAMP列中存储的值和从TIMESTAMP列中检索的值。TIMESTAMP列的值将从会话时区转换为 UTC 用于存储,从 UTC 转换为会话时区用于检索。

会话时区设置不影响UTC_TIMESTAMP()等函数显示的值,也不影响DATETIMEDATETIME列中的值。这些数据类型的值也不存储 UTC;只有在TIMESTAMP值转换时用到时区。如果需要DATETIMEDATETIME值的特定于区域设置的计算,请将它们转换为 UTC,执行计算,然后再转换回来。

当前全局时区和会话时区值可以这样检索:

SELECT @@GLOBAL.time_zone, @@SESSION.time_zone;

时区值可以用几种格式给出,但都不区分大小写:

  • 一个'SYSTEM'值,表示服务器时区与系统时区一致。
  • 一个表示相对于 UTC 的偏移量的字符串,格式为[H]H:MM,前缀为+-,例如'+10:00''-6:00',或'+05:30'。前导零可用于小时值小于 10 的情况;在这种情况下,MySQL在存储和检索值时,前面加一个 0。MySQL 将'-00:00''-0:00'转换为'+00:00'

    在 MySQL 8.0.19 之前,这个值必须在'-12:59''+13:00'范围内,包括这两个值;从 MySQL 8.0.19 开始,允许的范围是'-14:00''+14:00',包括这两值在内。
  • 一个命名的时区,如'Europe/Helsinki''US/Eastern''MET''UTC'

注意

只有已经创建并填充了mysql数据库中的时区信息表,才可以使用命名时区。否则,使用指定时区会导致错误:

mysql> SET time_zone = 'UTC';
ERROR 1298 (HY000): Unknown or incorrect time zone: 'UTC'

填充时区表

mysql系统模式中存在几个表来存储时区信息(参见 5.3 mysql 系统架构)。MySQL 安装过程创建了时区表,但没有加载它们。要手动执行此操作,请使用以下说明。

注意

加载时区信息不一定是一次性操作,因为信息偶尔会发生变化。当这些变化发生时,使用旧规则的应用程序就会过时,您可能会发现有必要重新加载时区表,以保持 MySQL 服务器使用当前的信息。

如果您的系统有自己的 zoneinfo 数据库(描述时区的文件集),请使用mysql_tzinfo_to_sql程序来加载时区表。这类系统的例子有 Linux、macOS、FreeBSD 和 Solaris。这些文件的一个可能位置是/usr/share/zoneinfo目录。如果您的系统没有 zoneinfo 数据库,您可以使用一个可下载的包,如本节后面所述。

要从命令行加载时区表,请将 zoneinfo 目录路径名传递给mysql_tzinfo_to_sql,并将输出发送到mysql程序中。例如:

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

这里显示的mysql命令假设您使用root这样的帐户连接到服务器,该帐户具有修改mysql系统模式中的表的权限。请根据实际情况调整连接参数。

mysql_tzinfo_to_sql读取系统的时区文件,并从中生成 SQL 语句。mysql处理这些语句来加载时区表。

mysql_tzinfo_to_sql也可以用于加载单个时区文件或生成跳秒信息:

1、要加载与时区名称tz_name对应的单个时区文件tz_file,请如下所示调用mysql_tzinfo_to_sql

mysql_tzinfo_to_sql tz_file tz_name | mysql -u root -p mysql

使用这种方法,您必须执行一个单独的命令,为服务器需要了解的每个指定区域加载时区文件。

2、如果你的时区必须考虑跳秒,初始化闰秒信息如下,其中tz_file是你的时区文件的名称:

mysql_tzinfo_to_sql --leap tz_file | mysql -u root -p mysql

在运行mysql_tzinfo_to_sql之后,重新启动服务器,这样它就不会继续使用任何以前缓存的时区数据。

如果您的系统没有 zoneinfo 数据库(例如 Windows),你可以使用包含 SQL 语句的包,可以在 MySQL 开发者社区下载:

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

警告

如果你的系统有 zoneinfo 数据库,不要使用可下载的时区包。请使用 mysql_tzinfo_to_sql 加载程序。否则,您可能会导致 MySQL 和系统上其他应用程序之间的日期时间处理不同。

要使用已经下载的 SQL 语句时区包,先解压它,然后将解压后的文件内容加载到时区表中:

mysql -u root -p mysql < file_name

然后重新启动服务器。

警告

不要使用包含 MyISAM 表的可下载时区包。这适用于较老的 MySQL 版本。MySQL 现在使用 InnoDB 来处理时区表。尝试用 MyISAM 表替换它们会导致问题。

及时了解时区的变化

当时区规则改变时,使用旧规则的应用程序将过时。为了保持最新,有必要确保您的系统使用的是当前时区信息。对于MySQL来说,有很多因素需要考虑:

  • 如果 MySQL 服务器的时区设置为SYSTEM,操作系统的时间会影响 MySQL 服务器的时间。确保您的操作系统使用最新的时区信息。对于大多数操作系统,最新的更新或服务包会让系统为时间更改做好准备。请查看您的操作系统供应商的网站,以获取处理时间变化的更新。
  • 如果您将系统的/etc/localtime时区文件替换为使用不同于mysqld启动时使用的规则的版本,重新启动mysqld,使其使用更新后的规则。否则,mysqld可能不会注意到系统更改了它的时间。
  • 如果你在 MySQL 中使用命名时区,请确保 MySQL 数据库中的时区表是最新的:

    如果您的系统有自己的 zoneinfo 数据库,请在 zoneinfo 数据库更新时重新加载 MySQL 时区表。

    对于没有自己的 zoneinfo 数据库的系统,请检查 MySQL 开发者社区的更新。当新的更新可用时,下载它并使用它来替换当前时区表的内容。

    对于这两种方法的说明,请参考填充时区表,mysqld缓存它所查找的时区信息,因此在更新时区表后,重启mysqld以确保它不会继续提供过时的时区数据。

如果您不确定指定的时区是否可用(用于服务器的时区设置或由客户端设置自己的时区),请检查时区表是否为空。下面的查询确定包含时区名的表是否有行:

mysql> SELECT COUNT(*) FROM mysql.time_zone_name;
+----------+
| COUNT(*) |
+----------+
|        0 |
+----------+

计数为零表示表为空。在这种情况下,目前没有应用程序使用命名时区,您不需要更新表(除非您希望启用命名时区支持)。大于零的计数表明表不是空的,其内容可用于指定时区支持。在这种情况下,请确保重新加载时区表,以便使用已命名时区的应用程序可以获得正确的查询结果。

为了检查您的 MySQL 安装是否正确地更新了夏令时规则,使用如下的测试。该示例使用的值是美国 3 月 11 日凌晨 2 点发生的 2007 年 DST 1 小时更改。

测试使用这个查询:

SELECT
  CONVERT_TZ('2007-03-11 2:00:00','US/Eastern','US/Central') AS time1,
  CONVERT_TZ('2007-03-11 3:00:00','US/Eastern','US/Central') AS time2;

这两个时间值表示夏令时发生变化的时间,使用命名时区需要使用时区表。期望的结果是两个查询返回相同的结果(输入时间,在'US/Central'时区转换为等效的值)。

在更新时区表之前,你会看到这样一个不正确的结果:

+---------------------+---------------------+
| time1               | time2               |
+---------------------+---------------------+
| 2007-03-11 01:00:00 | 2007-03-11 02:00:00 |
+---------------------+---------------------+

更新表之后,你应该会看到正确的结果:

+---------------------+---------------------+
| time1               | time2               |
+---------------------+---------------------+
| 2007-03-11 01:00:00 | 2007-03-11 01:00:00 |
+---------------------+---------------------+

时区跳秒支持

跳秒值返回的时间部分以:59:59结尾。这意味着像NOW()这样的函数可以在跳跃秒的时间内连续返回两到三次相同的值。时间部分以:59:60:59:61结尾的文字时间值仍然被认为是无效的。

如果需要在跳秒前一秒搜索TIMESTAMP值,则使用'YYYY-MM-DD hh:mm:ss'值进行比较,可能会得到异常结果。下面的示例演示了这一点。它将会话时区更改为 UTC,这样内部TIMESTAMP值(在 UTC)和显示值(应用了时区校正)之间就没有区别了。

mysql> CREATE TABLE t1 (
         a INT,
         ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
         PRIMARY KEY (ts)
       );
Query OK, 0 rows affected (0.01 sec)

mysql> -- 改为 UTC
mysql> SET time_zone = '+00:00';
Query OK, 0 rows affected (0.00 sec)

mysql> -- 模拟 NOW() = '2008-12-31 23:59:59'
mysql> SET timestamp = 1230767999;
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO t1 (a) VALUES (1);
Query OK, 1 row affected (0.00 sec)

mysql> -- 模拟 NOW() = '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)

mysql> -- 值内部不同,但显示相同
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 |
+------+---------------------+--------------------+
2 rows in set (0.00 sec)

mysql> -- 只有非跳跃值匹配
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> -- 秒=60 的跳跃值无效
mysql> SELECT * FROM t1 WHERE ts = '2008-12-31 23:59:60';
Empty set, 2 warnings (0.00 sec)

为了解决这个问题,你可以使用一个基于 UTC 值的比较,该值实际上存储在列中,并应用了跳秒校正:

mysql> -- 选择使用 UNIX_TIMESTAMP 函数返回跳跃值
mysql> SELECT * FROM t1 WHERE UNIX_TIMESTAMP(ts) = 1230768000;
+------+---------------------+
| a    | ts                  |
+------+---------------------+
|    2 | 2008-12-31 23:59:59 |
+------+---------------------+
1 row in set (0.00 sec)
上一篇:PHP 获取 今日,昨日,上周,本月,某年时间


下一篇:基于springboot的ShardingSphere5.X的分库分表的解决方案之自动时间段分片算法解决方案(二十四)