5.1.13 MySQL 服务器时区支持

本节介绍了 MySQL 维护的时区设置,如何加载命名时间支持所需的系统 table,如何在时区更改时保持最新状态以及如何启用 leap 秒支持。

有关复制设置中的时区设置的信息,请参阅第 16.4.1.15 节,“复制和系统功能”第 16.4.1.31 节,“复制和时区”

时区变量

MySQL Server 维护几个时区设置:

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

要在启动时为 MySQL Server 明确指定系统时区,请在启动mysqld之前设置TZ环境变量。如果使用mysqld_safe启动服务器,则其--timezone选项提供了另一种设置系统时区的方法。 TZ--timezone的允许值取决于系统。请查阅 os 文档,以了解可以接受的值。

  • 服务器当前时区。全局time_zone系统变量指示服务器当前正在运行的时区。time_zone的初始值为'SYSTEM',指示服务器时区与系统时区相同。

Note

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

初始全局服务器时区值可以在启动时通过命令行上的--default-time-zone选项明确指定,或者可以在选项文件中使用以下行:

default-time-zone='timezone'

如果具有SUPER特权,则可以使用以下语句在运行时设置全局服务器时区值:

SET GLOBAL time_zone = timezone;
  • 每会话时区。每个连接的 Client 端都有自己的会话时区设置,由会话time_zone变量指定。最初,会话变量从全局time_zone变量获取其值,但是 Client 端可以使用以下语句更改其自己的时区:
SET time_zone = timezone;

会话时区设置会影响对时区敏感的时间值的显示和存储。这包括由NOW()CURTIME()之类的函数显示的值,以及在TIMESTAMP列中存储和检索的值。 TIMESTAMP列的值从会话时区转换为 UTC 以进行存储,并从 UTC 转换为会话时区以进行检索。

会话时区设置不会影响诸如UTC_TIMESTAMP()之类的函数显示的值或DATETIMEDATETIME列中的值。这些数据类型中的值也不会存储在 UTC 中;仅当从TIMESTAMP值转换时,时区才适用。如果您想要DATETIMEDATETIME值的语言环境特定的算术,请将它们转换为 UTC,执行该算术,然后再转换回去。

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

SELECT @@GLOBAL.time_zone, @@SESSION.time_zone;
  • timezone *值可以多种格式给出,都不区分大小写:
  • 作为值'SYSTEM',指示服务器时区与系统时区相同。

  • 作为字符串,table 示形式为[H]H:MM的 UTC 偏移量,以+-作为前缀,例如'+10:00''-6:00''+05:30'。前导零可用于小于 10 的小时值;在这种情况下,MySQL 在存储和检索该值时会加上前导零。 MySQL 将'-00:00''-0:00'转换为'+00:00'

时区偏移量必须在'-12:59''+13:00'(包括两端)范围内。

  • 作为命名时区,例如'Europe/Helsinki''US/Eastern''MET'。只有在mysql数据库中的时区信息 table 已创建并填充后,才能使用命名时区。

填充时区 table

mysql系统数据库中存在几个 table 来存储时区信息(请参见第 5.3 节“ mysql 系统数据库”)。 MySQL 安装过程会创建时区 table,但不会加载它们。为此,请按照以下说明进行操作。

Note

加载时区信息不一定是一次性操作,因为该信息会偶尔更改。当发生此类更改时,使用旧规则的应用程序将变得过时,您可能会发现有必要重新加载时区 table 以使 MySQL 服务器使用的信息保持最新。参见与时区变化保持同步

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

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

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

此处显示的mysql命令假定您使用诸如root之类的帐户连接到服务器,该帐户具有修改mysql系统数据库中的 table 的特权。根据需要调整连接参数。

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

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

  • 要加载与时区名称* tz_name 对应的单个时区文件 tz_file *,请像这样调用mysql_tzinfo_to_sql
mysql_tzinfo_to_sql tz_file tz_name | mysql -u root -p mysql

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

  • 如果您的时区必须考虑 leap 秒,请像这样初始化 leap 秒信息,其中* tz_file *是您的时区文件的名称:
mysql_tzinfo_to_sql --leap tz_file | mysql -u root -p mysql

运行mysql_tzinfo_to_sql后,请重新启动服务器,以使其不再 continue 使用任何以前缓存的时区数据。

如果您的系统没有 zoneinfo 数据库(例如 Windows),则可以使用包含 SQL 语句的软件包,该软件包可从 MySQL 开发人员专区下载:

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

Warning

如果您的系统具有 zoneinfo 数据库,请不要使用可下载的时区软件包。请改用mysql_tzinfo_to_sqlUtil。否则,可能会导致 MySQL 与系统上其他应用程序之间在日期时间处理上有所不同。

要使用已下载的 SQL 语句时区程序包,请对其进行解压缩,然后将解压缩的文件内容加载到时区 table 中:

mysql -u root -p mysql < file_name

然后重新启动服务器。

Warning

请勿使用包含MyISAM个 table 格的可下载时区软件包。这适用于较旧的 MySQL 版本。 MySQL 5.7 和更高版本使用InnoDB作为时区 table。尝试用MyISAMtable 替换它们会引起问题。

与时区变化保持同步

当时区规则更改时,使用旧规则的应用程序将过时。要保持最新状态,必须确保系统使用了当前时区信息。对于 MySQL,保持最新状态需要考虑多个因素:

  • 如果将其时区设置为SYSTEM,则 os 时间会影响 MySQL 服务器使用的时间值。确保您的 os 使用最新的时区信息。对于大多数 os,最新的更新或 Service Pack 可为系统做好时间更改的准备。请在您的 os 供应商的网站上查看有关时间更改的更新。

  • 如果将系统的/etc/localtime时区文件替换为使用不同于mysqld启动时生效的规则的版本,请重新启动mysqld,以使其使用更新的规则。否则,mysqld可能不会注意到系统何时更改时间。

  • 如果在 MySQL 中使用命名时区,请确保mysql数据库中的时区 table 是最新的:

  • 如果您的系统具有自己的 zoneinfo 数据库,则每当 zoneinfo 数据库更新时,都重新加载 MySQL 时区 table。

    • 对于没有自己的 zoneinfo 数据库的系统,请检查 MySQL 开发人员区域以获取更新。当有新更新可用时,请下载并使用它来替换当前时区 table 的内容。

有关这两种方法的说明,请参见填充时区 tablemysqld会缓存它查找的时区信息,因此在更新时区 table 之后,请重新启动mysqld以确保它不会 continue 提供过时的时区数据。

如果不确定命名时区是否可用,用作服务器的时区设置还是由设置自己的时区的 Client 端使用,请检查时区 table 是否为空。以下查询确定包含时区名称的 table 是否具有任何行:

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

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

要检查您的 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;

这两个时间值指示 DST 更改发生的时间,并且使用命名时区要求使用时区 table。理想的结果是两个查询返回相同的结果(Importing 时间,在“美国/中央”时区中转换为等效值)。

在更新时区 table 之前,您会看到如下错误结果:

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

更新 table 后,您应该看到正确的结果:

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

时区飞跃秒支持

返回的 second 秒值的时间部分以:59:59结尾。这意味着诸如NOW()之类的函数可以在 the 秒内连续两三秒返回相同的值。仍将具有以:59:60:59:61结尾的时间部分的 Literals 时间值视为无效。

如果有必要在 the 秒之前一秒钟搜索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> -- change to UTC
mysql> SET time_zone = '+00:00';
Query OK, 0 rows affected (0.00 sec)

mysql> -- Simulate 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> -- Simulate 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> -- 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 |
+------+---------------------+--------------------+
2 rows in set (0.00 sec)

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> -- the leap value with seconds=60 is invalid
mysql> SELECT * FROM t1 WHERE ts = '2008-12-31 23:59:60';
Empty set, 2 warnings (0.00 sec)

要解决此问题,您可以基于实际存储在列中的 UTC 值进行比较,并应用 the 秒校正:

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 |
+------+---------------------+
1 row in set (0.00 sec)